Skip to content

Commit c7dbde8

Browse files
committed
chore: run coder connect networking from launchdaemon
1 parent 1737580 commit c7dbde8

18 files changed

+467
-425
lines changed

Coder-Desktop/Coder-Desktop/HelperService.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@ extension CoderVPNService {
1212
func setupHelper() async {
1313
refreshHelperState()
1414
switch helperState {
15-
case .uninstalled, .failed:
16-
await installHelper()
17-
case .installed:
18-
uninstallHelper()
15+
case .uninstalled, .failed, .installed:
16+
await uninstallHelper()
1917
await installHelper()
2018
case .requiresApproval, .installing:
2119
break
@@ -63,10 +61,10 @@ extension CoderVPNService {
6361
helperState = .failed(.unknown(lastUnknownError?.localizedDescription ?? "Unknown"))
6462
}
6563

66-
private func uninstallHelper() {
64+
private func uninstallHelper() async {
6765
let daemon = SMAppService.daemon(plistName: plistName)
6866
do {
69-
try daemon.unregister()
67+
try await daemon.unregister()
7068
} catch let error as NSError {
7169
helperState = .failed(.init(error: error))
7270
} catch {

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

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

6262
@Published var tunnelState: VPNServiceState = .disabled {
6363
didSet {
@@ -158,10 +158,10 @@ final class CoderVPNService: NSObject, VPNService {
158158
}
159159
}
160160

161-
func onExtensionPeerUpdate(_ data: Data) {
161+
func onExtensionPeerUpdate(_ diff: Data) {
162162
logger.info("network extension peer update")
163163
do {
164-
let msg = try Vpn_PeerUpdate(serializedBytes: data)
164+
let msg = try Vpn_PeerUpdate(serializedBytes: diff)
165165
debugPrint(msg)
166166
applyPeerUpdate(with: msg)
167167
} catch {
@@ -219,16 +219,18 @@ extension CoderVPNService {
219219
break
220220
// Non-connecting -> Connecting: Establish XPC
221221
case (_, .connecting):
222-
xpc.connect()
223-
xpc.ping()
222+
// Detached to run ASAP
223+
// TODO: Switch to `Task.immediate` once stable
224+
Task.detached { try? await self.xpc.ping() }
224225
tunnelState = .connecting
225226
// Non-connected -> Connected:
226227
// - Retrieve Peers
227228
// - Run `onStart` closure
228229
case (_, .connected):
229230
onStart?()
230-
xpc.connect()
231-
xpc.getPeerState()
231+
// Detached to run ASAP
232+
// TODO: Switch to `Task.immediate` once stable
233+
Task.detached { try? await self.xpc.getPeerState() }
232234
tunnelState = .connected
233235
// Any -> Reasserting
234236
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: 67 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,112 +3,98 @@ 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
36-
Task { @MainActor in
37-
logger.error("VPN XPC connection invalidated.")
38-
self.xpc = nil
39-
self.connect()
40-
}
41-
}
42-
xpcConn.interruptionHandler = { [logger] in
43-
Task { @MainActor in
44-
logger.error("VPN XPC connection interrupted.")
45-
self.xpc = nil
46-
self.connect()
47-
}
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 = {
29+
self.logger.error("XPC connection invalidated")
30+
self.connection = nil
31+
_ = self.connect()
4832
}
49-
xpcConn.resume()
50-
}
51-
52-
func ping() {
53-
xpc?.ping {
54-
Task { @MainActor in
55-
self.logger.info("Connected to NE over XPC")
56-
}
33+
connection.interruptionHandler = {
34+
self.logger.error("XPC connection interrupted")
35+
self.connection = nil
36+
_ = self.connect()
5737
}
38+
logger.info("connecting to \(helperAppMachServiceName)")
39+
connection.resume()
40+
self.connection = connection
41+
return connection
5842
}
5943

60-
func getPeerState() {
61-
xpc?.getPeerState { data in
62-
Task { @MainActor in
63-
self.svc.onExtensionPeerState(data)
64-
}
44+
func onPeerUpdate(_ diff: Data, reply: @escaping () -> Void) {
45+
let reply = CompletionWrapper(reply)
46+
Task { @MainActor in
47+
svc.onExtensionPeerUpdate(diff)
48+
reply()
6549
}
6650
}
6751

68-
func onPeerUpdate(_ data: Data) {
52+
func onProgress(stage: ProgressStage, downloadProgress: DownloadProgress?, reply: @escaping () -> Void) {
53+
let reply = CompletionWrapper(reply)
6954
Task { @MainActor in
70-
svc.onExtensionPeerUpdate(data)
55+
svc.onProgress(stage: stage, downloadProgress: downloadProgress)
56+
reply()
7157
}
7258
}
59+
}
7360

74-
func onProgress(stage: ProgressStage, downloadProgress: DownloadProgress?) {
75-
Task { @MainActor in
76-
svc.onProgress(stage: stage, downloadProgress: downloadProgress)
61+
// These methods are called to request updatess from the Helper.
62+
extension AppXPCListener {
63+
func ping() async throws {
64+
let conn = connect()
65+
return try await withCheckedThrowingContinuation { continuation in
66+
guard let proxy = conn.remoteObjectProxyWithErrorHandler({ err in
67+
self.logger.error("failed to connect to HelperXPC \(err.localizedDescription, privacy: .public)")
68+
continuation.resume(throwing: err)
69+
}) as? HelperAppXPCInterface else {
70+
self.logger.error("failed to get proxy for HelperXPC")
71+
continuation.resume(throwing: XPCError.wrongProxyType)
72+
return
73+
}
74+
proxy.ping {
75+
self.logger.info("Connected to Helper over XPC")
76+
continuation.resume()
77+
}
7778
}
7879
}
7980

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-
}
81+
func getPeerState() async throws {
82+
let conn = connect()
83+
return try await withCheckedThrowingContinuation { continuation in
84+
guard let proxy = conn.remoteObjectProxyWithErrorHandler({ err in
85+
self.logger.error("failed to connect to HelperXPC \(err.localizedDescription, privacy: .public)")
86+
continuation.resume(throwing: err)
87+
}) as? HelperAppXPCInterface else {
88+
self.logger.error("failed to get proxy for HelperXPC")
89+
continuation.resume(throwing: XPCError.wrongProxyType)
90+
return
91+
}
92+
proxy.getPeerState { data in
93+
Task { @MainActor in
94+
self.svc.onExtensionPeerState(data)
10995
}
96+
continuation.resume()
11097
}
111-
reply(success)
11298
}
11399
}
114100
}

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