Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dc56a96

Browse files
committedJan 23, 2025··
XPC fixes
1 parent a5305cd commit dc56a96

File tree

8 files changed

+130
-56
lines changed

8 files changed

+130
-56
lines changed
 

‎Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
/* Begin PBXBuildFile section */
1010
3B0916A92D41CFD50064DEA8 /* VPNXPC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0916A12D41CFD50064DEA8 /* VPNXPC.framework */; };
11-
3B0916AA2D41CFD50064DEA8 /* VPNXPC.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0916A12D41CFD50064DEA8 /* VPNXPC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1211
961679332CFF117300B2B6DF /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 961679322CFF117300B2B6DF /* NetworkExtension.framework */; };
1312
9616793D2CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 961679302CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
1413
AA2C690F2D34F6920059AFAF /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = AA2C690E2D34F6920059AFAF /* LaunchAtLogin */; };
@@ -137,17 +136,6 @@
137136
name = "Embed XPC Services";
138137
runOnlyForDeploymentPostprocessing = 0;
139138
};
140-
3B0916872D41C8010064DEA8 /* Embed Frameworks */ = {
141-
isa = PBXCopyFilesBuildPhase;
142-
buildActionMask = 2147483647;
143-
dstPath = "";
144-
dstSubfolderSpec = 10;
145-
files = (
146-
3B0916AA2D41CFD50064DEA8 /* VPNXPC.framework in Embed Frameworks */,
147-
);
148-
name = "Embed Frameworks";
149-
runOnlyForDeploymentPostprocessing = 0;
150-
};
151139
961679422CFF117300B2B6DF /* Embed System Extensions */ = {
152140
isa = PBXCopyFilesBuildPhase;
153141
buildActionMask = 2147483647;
@@ -1015,7 +1003,7 @@
10151003
"@executable_path/../Frameworks",
10161004
"@loader_path/Frameworks",
10171005
);
1018-
MACOSX_DEPLOYMENT_TARGET = 14.6;
1006+
MACOSX_DEPLOYMENT_TARGET = 14.0;
10191007
MARKETING_VERSION = 1.0;
10201008
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
10211009
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
@@ -1054,7 +1042,7 @@
10541042
"@executable_path/../Frameworks",
10551043
"@loader_path/Frameworks",
10561044
);
1057-
MACOSX_DEPLOYMENT_TARGET = 14.6;
1045+
MACOSX_DEPLOYMENT_TARGET = 14.0;
10581046
MARKETING_VERSION = 1.0;
10591047
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
10601048
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
@@ -1207,6 +1195,7 @@
12071195
ENABLE_HARDENED_RUNTIME = YES;
12081196
ENABLE_PREVIEWS = YES;
12091197
GENERATE_INFOPLIST_FILE = YES;
1198+
INFOPLIST_FILE = "Coder Desktop/Info.plist";
12101199
INFOPLIST_KEY_LSUIElement = YES;
12111200
INFOPLIST_KEY_NSHumanReadableCopyright = "";
12121201
LD_RUNPATH_SEARCH_PATHS = (
@@ -1239,6 +1228,7 @@
12391228
ENABLE_HARDENED_RUNTIME = YES;
12401229
ENABLE_PREVIEWS = YES;
12411230
GENERATE_INFOPLIST_FILE = YES;
1231+
INFOPLIST_FILE = "Coder Desktop/Info.plist";
12421232
INFOPLIST_KEY_LSUIElement = YES;
12431233
INFOPLIST_KEY_NSHumanReadableCopyright = "";
12441234
LD_RUNPATH_SEARCH_PATHS = (
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>NetworkExtension</key>
6+
<dict>
7+
<key>NEMachServiceName</key>
8+
<string>$(TeamIdentifierPrefix)com.coder.Coder-Desktop.VPN</string>
9+
</dict>
10+
</dict>
11+
</plist>

‎Coder Desktop/Coder Desktop/VPNService.swift

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import NetworkExtension
22
import os
33
import SwiftUI
4+
import VPNLib
45
import VPNXPC
56

67
@MainActor
@@ -42,7 +43,7 @@ enum VPNServiceError: Error, Equatable {
4243
}
4344

4445
@MainActor
45-
final class CoderVPNService: NSObject, VPNService {
46+
final class CoderVPNService: NSObject, VPNService, @preconcurrency VPNXPCClientCallbackProtocol {
4647
var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "vpn")
4748
var xpcConn: NSXPCConnection
4849
@Published var tunnelState: VPNServiceState = .disabled
@@ -66,11 +67,19 @@ final class CoderVPNService: NSObject, VPNService {
6667
var systemExtnDelegate: SystemExtensionDelegate<CoderVPNService>?
6768

6869
override init() {
69-
xpcConn = NSXPCConnection(serviceName: "com.coder.Coder-Desktop.VPNXPC")
70+
let networkExtDict = Bundle.main.object(forInfoDictionaryKey: "NetworkExtension") as? [String: Any]
71+
let machServiceName = networkExtDict?["NEMachServiceName"] as? String
72+
xpcConn = NSXPCConnection(serviceName: machServiceName!)
7073
xpcConn.remoteObjectInterface = NSXPCInterface(with: VPNXPCProtocol.self)
71-
xpcConn.resume()
74+
xpcConn.exportedInterface = NSXPCInterface(with: VPNXPCClientCallbackProtocol.self)
7275

7376
super.init()
77+
xpcConn.exportedObject = self
78+
// xpcConn.invalidationHandler = {
79+
// // self.logger.error("XPC connection invalidated.")
80+
// print("XPC connection invalidated")
81+
// }
82+
xpcConn.resume()
7483
installSystemExtension()
7584
Task {
7685
await loadNetworkExtension()
@@ -86,11 +95,6 @@ final class CoderVPNService: NSObject, VPNService {
8695
tunnelState = .connecting
8796
await enableNetworkExtension()
8897
logger.debug("network extension enabled")
89-
if let proxy = xpcConn.remoteObjectProxy as? VPNXPCProtocol {
90-
proxy.start { _ in
91-
self.tunnelState = .connected
92-
}
93-
}
9498
}
9599
defer { startTask = nil }
96100
await startTask?.value
@@ -106,9 +110,6 @@ final class CoderVPNService: NSObject, VPNService {
106110
}
107111
stopTask = Task {
108112
tunnelState = .disconnecting
109-
if let proxy = xpcConn.remoteObjectProxy as? VPNXPCProtocol {
110-
proxy.stop { _ in }
111-
}
112113
await disableNetworkExtension()
113114
logger.info("network extension stopped")
114115
tunnelState = .disabled
@@ -135,4 +136,26 @@ final class CoderVPNService: NSObject, VPNService {
135136
}
136137
}
137138
}
139+
140+
func onPeerUpdate(_ data: Data) {
141+
// TODO: handle peer update
142+
do {
143+
let msg = try Vpn_TunnelMessage(serializedBytes: data)
144+
debugPrint(msg)
145+
} catch {
146+
logger.error("failed to decode peer update \(error)")
147+
}
148+
}
149+
150+
func onStart() {
151+
logger.info("network extension reported started")
152+
tunnelState = .connected
153+
}
154+
155+
func onStop() {
156+
logger.info("network extension reported stopped")
157+
tunnelState = .disabled
158+
}
159+
160+
func onError(_: NSError) {}
138161
}

‎Coder Desktop/VPN/Manager.swift

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import CoderSDK
22
import NetworkExtension
33
import os
44
import VPNLib
5+
import VPNXPC
56

67
actor Manager {
78
let ptp: PacketTunnelProvider
@@ -10,7 +11,6 @@ actor Manager {
1011
let tunnelHandle: TunnelHandle
1112
let speaker: Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>
1213
var readLoop: Task<Void, any Error>!
13-
// TODO: XPC Speaker
1414

1515
private let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
1616
.first!.appending(path: "coder-vpn.dylib")
@@ -85,12 +85,18 @@ actor Manager {
8585
} catch {
8686
logger.error("tunnel read loop failed: \(error)")
8787
try await tunnelHandle.close()
88-
// TODO: Notify app over XPC
88+
if let connection = globalXPCListenerDelegate.getActiveConnection() {
89+
let client = connection.remoteObjectProxy as? VPNXPCClientCallbackProtocol
90+
client?.onError(error as NSError)
91+
}
8992
return
9093
}
9194
logger.info("tunnel read loop exited")
9295
try await tunnelHandle.close()
93-
// TODO: Notify app over XPC
96+
if let connection = globalXPCListenerDelegate.getActiveConnection() {
97+
let client = connection.remoteObjectProxy as? VPNXPCClientCallbackProtocol
98+
client?.onStop()
99+
}
94100
}
95101

96102
func handleMessage(_ msg: Vpn_TunnelMessage) {
@@ -100,7 +106,16 @@ actor Manager {
100106
}
101107
switch msgType {
102108
case .peerUpdate:
103-
{}() // TODO: Send over XPC
109+
if let connection = globalXPCListenerDelegate.getActiveConnection() {
110+
// We can call back to the client
111+
do {
112+
let client = connection.remoteObjectProxy as? VPNXPCClientCallbackProtocol
113+
let data = try msg.peerUpdate.serializedData()
114+
client!.onPeerUpdate(data)
115+
} catch {
116+
logger.error("failed to send peer update to client: \(error)")
117+
}
118+
}
104119
case let .log(logMsg):
105120
writeVpnLog(logMsg)
106121
case .networkSettings, .start, .stop:
@@ -165,10 +180,9 @@ actor Manager {
165180
}
166181
}
167182

168-
// TODO: Call via XPC
169183
// Retrieves the current state of all peers,
170184
// as required when starting the app whilst the network extension is already running
171-
func getPeerInfo() async throws(ManagerError) {
185+
func getPeerInfo() async throws(ManagerError) -> Vpn_PeerUpdate {
172186
logger.info("sending peer state request")
173187
let resp: Vpn_TunnelMessage
174188
do {
@@ -181,7 +195,7 @@ actor Manager {
181195
guard case .peerUpdate = resp.msg else {
182196
throw .incorrectResponse(resp)
183197
}
184-
// TODO: pass to app over XPC
198+
return resp.peerUpdate
185199
}
186200
}
187201

‎Coder Desktop/VPN/PacketTunnelProvider.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import NetworkExtension
2-
import VPNLib
32
import os
3+
import VPNLib
44

55
/* From <sys/kern_control.h> */
66
let CTLIOCGINFO: UInt = 0xC064_4E03
@@ -16,7 +16,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
1616
_ = strcpy($0, "com.apple.net.utun_control")
1717
}
1818
}
19-
for fd: Int32 in 0...1024 {
19+
for fd: Int32 in 0 ... 1024 {
2020
var addr = sockaddr_ctl()
2121
var ret: Int32 = -1
2222
var len = socklen_t(MemoryLayout.size(ofValue: addr))
@@ -57,12 +57,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
5757
manager = try await Manager(
5858
with: self,
5959
cfg: .init(
60-
apiToken: "fake-token", serverUrl: .init(string: "https://dev.coder.com")!)
60+
apiToken: "fake-token", serverUrl: .init(string: "https://dev.coder.com")!
61+
)
6162
)
6263
globalXPCListenerDelegate.vpnXPCInterface.setManager(manager)
63-
completionHandler(nil)
64+
try await manager?.startVPN()
65+
completionHandler.callAsFunction(nil)
6466
} catch {
65-
completionHandler(error)
67+
completionHandler.callAsFunction(error as NSError)
6668
logger.error("error starting manager: \(error.description, privacy: .public)")
6769
}
6870
}
@@ -77,6 +79,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
7779
completionHandler()
7880
return
7981
}
82+
83+
let managerCopy = manager
84+
Task {
85+
do throws(ManagerError) {
86+
try await managerCopy?.stopVPN()
87+
} catch {
88+
logger.error("error stopping manager: \(error.description, privacy: .public)")
89+
}
90+
}
91+
8092
manager = nil
8193
globalXPCListenerDelegate.vpnXPCInterface.setManager(nil)
8294
completionHandler()

‎Coder Desktop/VPN/VpnXPCInterface.swift

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
11
import Foundation
22
import os.log
3+
import VPNLib
34
import VPNXPC
45

5-
final class CallbackWrapper: @unchecked Sendable {
6-
private let block: (NSError?) -> Void
7-
8-
init(_ block: @escaping (NSError?) -> Void) {
9-
self.block = block
10-
}
11-
12-
func call(_ error: NSError?) {
13-
// Just forward to the original block
14-
block(error)
15-
}
16-
}
17-
186
@objc final class VPNXPCInterface: NSObject, VPNXPCProtocol, @unchecked Sendable {
197
private var manager: Manager?
208
private let managerLock = NSLock()
@@ -51,11 +39,11 @@ final class CallbackWrapper: @unchecked Sendable {
5139
do {
5240
try await manager.startVPN()
5341
await MainActor.run {
54-
safeReply.call(nil)
42+
safeReply.callAsFunction(nil)
5543
}
5644
} catch {
5745
await MainActor.run {
58-
safeReply.call(error as NSError)
46+
safeReply.callAsFunction(error as NSError)
5947
}
6048
}
6149
}
@@ -78,11 +66,11 @@ final class CallbackWrapper: @unchecked Sendable {
7866
do {
7967
try await manager.stopVPN()
8068
await MainActor.run {
81-
safeReply.call(nil)
69+
safeReply.callAsFunction(nil)
8270
}
8371
} catch {
8472
await MainActor.run {
85-
safeReply.call(error as NSError)
73+
safeReply.callAsFunction(error as NSError)
8674
}
8775
}
8876
}

‎Coder Desktop/VPN/main.swift

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,47 @@ import Foundation
22
import NetworkExtension
33
import VPNXPC
44

5-
final class XPCListenerDelegate: NSObject, NSXPCListenerDelegate, Sendable {
5+
final class XPCListenerDelegate: NSObject, NSXPCListenerDelegate, @unchecked Sendable {
66
let vpnXPCInterface = VPNXPCInterface()
7+
var activeConnection: NSXPCConnection?
8+
var connMutex: NSLock = .init()
9+
10+
func getActiveConnection() -> NSXPCConnection? {
11+
connMutex.lock()
12+
defer { connMutex.unlock() }
13+
return activeConnection
14+
}
15+
16+
func setActiveConnection(_ connection: NSXPCConnection?) {
17+
connMutex.lock()
18+
defer { connMutex.unlock() }
19+
activeConnection = connection
20+
}
721

822
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
923
newConnection.exportedInterface = NSXPCInterface(with: VPNXPCProtocol.self)
1024
newConnection.exportedObject = vpnXPCInterface
25+
newConnection.remoteObjectInterface = NSXPCInterface(with: VPNXPCClientCallbackProtocol.self)
26+
newConnection.invalidationHandler = { [weak self] in
27+
self?.setActiveConnection(nil)
28+
}
29+
setActiveConnection(newConnection)
1130

1231
newConnection.resume()
1332
return true
1433
}
1534
}
1635

36+
guard
37+
let netExt = Bundle.main.object(forInfoDictionaryKey: "NetworkExtension") as? [String: Any],
38+
let serviceName = netExt["NEMachServiceName"] as? String
39+
else {
40+
fatalError("Missing NEMachServiceName in Info.plist")
41+
}
42+
43+
print(serviceName)
1744
let globalXPCListenerDelegate = XPCListenerDelegate()
18-
let xpcListener = NSXPCListener(machServiceName: "com.coder.Coder-Desktop.VPNXPC")
45+
let xpcListener = NSXPCListener(machServiceName: serviceName)
1946
xpcListener.delegate = globalXPCListenerDelegate
2047
xpcListener.resume()
2148

‎Coder Desktop/VPNXPC/Protocol.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,12 @@ import Foundation
55
func start(with reply: @escaping (NSError?) -> Void)
66
func stop(with reply: @escaping (NSError?) -> Void)
77
}
8+
9+
@preconcurrency
10+
@objc public protocol VPNXPCClientCallbackProtocol {
11+
/// Called when the server has a status update to share
12+
func onPeerUpdate(_ data: Data)
13+
func onStart()
14+
func onStop()
15+
func onError(_ err: NSError)
16+
}

0 commit comments

Comments
 (0)
Please sign in to comment.