Skip to content

Commit 49d5c99

Browse files
committed
chore: run coder connect networking from launchdaemon
1 parent ebcb698 commit 49d5c99

17 files changed

+418
-392
lines changed

Coder-Desktop/Coder-Desktop/HelperService.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import os
22
import ServiceManagement
33

4-
// Whilst the GUI app installs the helper, the System Extension communicates
5-
// with it over XPC
64
@MainActor
75
class HelperService: ObservableObject {
86
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "HelperService")

Coder-Desktop/Coder-Desktop/VPN/VPNService.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ enum VPNServiceError: Error, Equatable {
5454
@MainActor
5555
final class CoderVPNService: NSObject, VPNService {
5656
var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "vpn")
57-
lazy var xpc: VPNXPCInterface = .init(vpn: self)
57+
lazy var xpc: AppXPCListener = .init(vpn: self)
5858

5959
@Published var tunnelState: VPNServiceState = .disabled {
6060
didSet {
@@ -138,10 +138,10 @@ final class CoderVPNService: NSObject, VPNService {
138138
}
139139
}
140140

141-
func onExtensionPeerUpdate(_ data: Data) {
141+
func onExtensionPeerUpdate(_ diff: Data) {
142142
logger.info("network extension peer update")
143143
do {
144-
let msg = try Vpn_PeerUpdate(serializedBytes: data)
144+
let msg = try Vpn_PeerUpdate(serializedBytes: diff)
145145
debugPrint(msg)
146146
applyPeerUpdate(with: msg)
147147
} catch {
@@ -199,16 +199,18 @@ extension CoderVPNService {
199199
break
200200
// Non-connecting -> Connecting: Establish XPC
201201
case (_, .connecting):
202-
xpc.connect()
203-
xpc.ping()
202+
// Detached to run ASAP
203+
// TODO: Switch to `Task.immediate` once stable
204+
Task.detached { try? await self.xpc.ping() }
204205
tunnelState = .connecting
205206
// Non-connected -> Connected:
206207
// - Retrieve Peers
207208
// - Run `onStart` closure
208209
case (_, .connected):
209210
onStart?()
210-
xpc.connect()
211-
xpc.getPeerState()
211+
// Detached to run ASAP
212+
// TODO: Switch to `Task.immediate` once stable
213+
Task.detached { try? await self.xpc.getPeerState() }
212214
tunnelState = .connected
213215
// Any -> Reasserting
214216
case (_, .reasserting):

Coder-Desktop/Coder-Desktop/Views/LoginForm.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,13 @@ struct LoginForm: View {
190190
@discardableResult
191191
func validateURL(_ url: String) throws(LoginError) -> URL {
192192
guard let url = URL(string: url) else {
193-
throw LoginError.invalidURL
193+
throw .invalidURL
194194
}
195195
guard url.scheme == "https" else {
196-
throw LoginError.httpsRequired
196+
throw .httpsRequired
197197
}
198198
guard url.host != nil else {
199-
throw LoginError.noHost
199+
throw .noHost
200200
}
201201
return url
202202
}

Coder-Desktop/Coder-Desktop/XPCInterface.swift

Lines changed: 63 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,112 +3,100 @@ import NetworkExtension
33
import os
44
import VPNLib
55

6-
@objc final class VPNXPCInterface: NSObject, VPNXPCClientCallbackProtocol, @unchecked Sendable {
6+
@objc final class AppXPCListener: NSObject, AppXPCInterface, @unchecked Sendable {
77
private var svc: CoderVPNService
8-
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "VPNXPCInterface")
9-
private var xpc: VPNXPCProtocol?
8+
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AppXPCListener")
9+
private var connection: NSXPCConnection?
1010

1111
init(vpn: CoderVPNService) {
1212
svc = vpn
1313
super.init()
1414
}
1515

16-
func connect() {
17-
logger.debug("VPN xpc connect called")
18-
guard xpc == nil else {
19-
logger.debug("VPN xpc already exists")
20-
return
16+
func connect() -> NSXPCConnection {
17+
if let connection {
18+
return connection
2119
}
22-
let networkExtDict = Bundle.main.object(forInfoDictionaryKey: "NetworkExtension") as? [String: Any]
23-
let machServiceName = networkExtDict?["NEMachServiceName"] as? String
24-
let xpcConn = NSXPCConnection(machServiceName: machServiceName!)
25-
xpcConn.remoteObjectInterface = NSXPCInterface(with: VPNXPCProtocol.self)
26-
xpcConn.exportedInterface = NSXPCInterface(with: VPNXPCClientCallbackProtocol.self)
27-
guard let proxy = xpcConn.remoteObjectProxy as? VPNXPCProtocol else {
28-
fatalError("invalid xpc cast")
29-
}
30-
xpc = proxy
31-
32-
logger.debug("connecting to machServiceName: \(machServiceName!)")
3320

34-
xpcConn.exportedObject = self
35-
xpcConn.invalidationHandler = { [logger] in
21+
let connection = NSXPCConnection(
22+
machServiceName: helperAppMachServiceName,
23+
options: .privileged
24+
)
25+
connection.remoteObjectInterface = NSXPCInterface(with: HelperAppXPCInterface.self)
26+
connection.exportedInterface = NSXPCInterface(with: AppXPCInterface.self)
27+
connection.exportedObject = self
28+
connection.invalidationHandler = {
3629
Task { @MainActor in
37-
logger.error("VPN XPC connection invalidated.")
38-
self.xpc = nil
39-
self.connect()
30+
self.logger.error("XPC connection invalidated")
31+
self.connection = nil
32+
_ = self.connect()
4033
}
4134
}
42-
xpcConn.interruptionHandler = { [logger] in
35+
connection.interruptionHandler = {
4336
Task { @MainActor in
44-
logger.error("VPN XPC connection interrupted.")
45-
self.xpc = nil
46-
self.connect()
37+
self.logger.error("XPC connection interrupted")
38+
self.connection = nil
39+
_ = self.connect()
4740
}
4841
}
49-
xpcConn.resume()
42+
connection.resume()
43+
self.connection = connection
44+
return connection
5045
}
5146

52-
func ping() {
53-
xpc?.ping {
54-
Task { @MainActor in
55-
self.logger.info("Connected to NE over XPC")
47+
func ping() async throws {
48+
let conn = connect()
49+
return try await withCheckedThrowingContinuation { continuation in
50+
guard let proxy = conn.remoteObjectProxyWithErrorHandler({ err in
51+
self.logger.error("failed to connect to HelperXPC \(err.localizedDescription, privacy: .public)")
52+
continuation.resume(throwing: err)
53+
}) as? HelperAppXPCInterface else {
54+
self.logger.error("failed to get proxy for HelperXPC")
55+
continuation.resume(throwing: XPCError.wrongProxyType)
56+
return
57+
}
58+
proxy.ping {
59+
Task { @MainActor in
60+
self.logger.info("Connected to Helper over XPC")
61+
continuation.resume()
62+
}
5663
}
5764
}
5865
}
5966

60-
func getPeerState() {
61-
xpc?.getPeerState { data in
62-
Task { @MainActor in
63-
self.svc.onExtensionPeerState(data)
67+
func getPeerState() async throws {
68+
let conn = connect()
69+
return try await withCheckedThrowingContinuation { continuation in
70+
guard let proxy = conn.remoteObjectProxyWithErrorHandler({ err in
71+
self.logger.error("failed to connect to HelperXPC \(err.localizedDescription, privacy: .public)")
72+
continuation.resume(throwing: err)
73+
}) as? HelperAppXPCInterface else {
74+
self.logger.error("failed to get proxy for HelperXPC")
75+
continuation.resume(throwing: XPCError.wrongProxyType)
76+
return
77+
}
78+
proxy.getPeerState { data in
79+
Task { @MainActor in
80+
self.svc.onExtensionPeerState(data)
81+
continuation.resume()
82+
}
6483
}
6584
}
6685
}
6786

68-
func onPeerUpdate(_ data: Data) {
87+
func onPeerUpdate(_ diff: Data, reply: @escaping () -> Void) {
88+
let reply = CompletionWrapper(reply)
6989
Task { @MainActor in
70-
svc.onExtensionPeerUpdate(data)
90+
svc.onExtensionPeerUpdate(diff)
91+
reply()
7192
}
7293
}
7394

74-
func onProgress(stage: ProgressStage, downloadProgress: DownloadProgress?) {
95+
func onProgress(stage: ProgressStage, downloadProgress: DownloadProgress?, reply: @escaping () -> Void) {
96+
let reply = CompletionWrapper(reply)
7597
Task { @MainActor in
7698
svc.onProgress(stage: stage, downloadProgress: downloadProgress)
77-
}
78-
}
79-
80-
// The NE has verified the dylib and knows better than Gatekeeper
81-
func removeQuarantine(path: String, reply: @escaping (Bool) -> Void) {
82-
let reply = CallbackWrapper(reply)
83-
Task { @MainActor in
84-
let prompt = """
85-
Coder Desktop wants to execute code downloaded from \
86-
\(svc.serverAddress ?? "the Coder deployment"). The code has been \
87-
verified to be signed by Coder.
88-
"""
89-
let source = """
90-
do shell script "xattr -d com.apple.quarantine \(path)" \
91-
with prompt "\(prompt)" \
92-
with administrator privileges
93-
"""
94-
let success = await withCheckedContinuation { continuation in
95-
guard let script = NSAppleScript(source: source) else {
96-
continuation.resume(returning: false)
97-
return
98-
}
99-
// Run on a background thread
100-
Task.detached {
101-
var error: NSDictionary?
102-
script.executeAndReturnError(&error)
103-
if let error {
104-
self.logger.error("AppleScript error: \(error)")
105-
continuation.resume(returning: false)
106-
} else {
107-
continuation.resume(returning: true)
108-
}
109-
}
110-
}
111-
reply(success)
99+
reply()
112100
}
113101
}
114102
}

Coder-Desktop/Coder-DesktopHelper/HelperXPCProtocol.swift

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)