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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy