Skip to content

Commit e32d7de

Browse files
committed
chore: run coder connect networking from launchdaemon
1 parent 16c716d commit e32d7de

18 files changed

+465
-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-
awaitinstallHelper()
17-
case.installed:
18-
uninstallHelper()
15+
case.uninstalled,.failed,.installed:
16+
awaituninstallHelper()
1917
awaitinstallHelper()
2018
case.requiresApproval,.installing:
2119
break
@@ -63,10 +61,10 @@ extension CoderVPNService{
6361
helperState =.failed(.unknown(lastUnknownError?.localizedDescription ??"Unknown"))
6462
}
6563

66-
privatefunc uninstallHelper(){
64+
privatefunc uninstallHelper()async{
6765
letdaemon=SMAppService.daemon(plistName: plistName)
6866
do{
69-
try daemon.unregister()
67+
tryawaitdaemon.unregister()
7068
}catchlet 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
finalclassCoderVPNService:NSObject,VPNService{
5959
varlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"vpn")
60-
lazy varxpc:VPNXPCInterface=.init(vpn:self)
60+
lazy varxpc:AppXPCListener=.init(vpn:self)
6161

6262
@PublishedvartunnelState: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-
letmsg=tryVpn_PeerUpdate(serializedBytes:data)
164+
letmsg=tryVpn_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?awaitself.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?awaitself.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
@@ -192,13 +192,13 @@ struct LoginForm: View{
192192
@discardableResult
193193
func validateURL(_ url:String)throws(LoginError)->URL{
194194
guardlet url =URL(string: url)else{
195-
throwLoginError.invalidURL
195+
throw.invalidURL
196196
}
197197
guard url.scheme =="https"else{
198-
throwLoginError.httpsRequired
198+
throw.httpsRequired
199199
}
200200
guard url.host !=nilelse{
201-
throwLoginError.noHost
201+
throw.noHost
202202
}
203203
return url
204204
}

‎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-
@objcfinalclassVPNXPCInterface:NSObject,VPNXPCClientCallbackProtocol,@uncheckedSendable{
6+
@objcfinalclassAppXPCListener:NSObject,AppXPCInterface,@uncheckedSendable{
77
privatevarsvc:CoderVPNService
8-
privateletlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"VPNXPCInterface")
9-
privatevarxpc:VPNXPCProtocol?
8+
privateletlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"AppXPCListener")
9+
privatevarconnection: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 ==nilelse{
19-
logger.debug("VPN xpc already exists")
20-
return
16+
func connect()->NSXPCConnection{
17+
iflet connection {
18+
return connection
2119
}
22-
letnetworkExtDict=Bundle.main.object(forInfoDictionaryKey:"NetworkExtension")as?[String:Any]
23-
letmachServiceName=networkExtDict?["NEMachServiceName"]as?String
24-
letxpcConn=NSXPCConnection(machServiceName: machServiceName!)
25-
xpcConn.remoteObjectInterface =NSXPCInterface(with:VPNXPCProtocol.self)
26-
xpcConn.exportedInterface =NSXPCInterface(with:VPNXPCClientCallbackProtocol.self)
27-
guardlet proxy = xpcConn.remoteObjectProxy as?VPNXPCProtocolelse{
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{@MainActorin
37-
logger.error("VPN XPC connection invalidated.")
38-
self.xpc =nil
39-
self.connect()
40-
}
41-
}
42-
xpcConn.interruptionHandler ={[logger]in
43-
Task{@MainActorin
44-
logger.error("VPN XPC connection interrupted.")
45-
self.xpc =nil
46-
self.connect()
47-
}
21+
letconnection=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{@MainActorin
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-
funcgetPeerState(){
61-
xpc?.getPeerState{ data in
62-
Task{@MainActorin
63-
self.svc.onExtensionPeerState(data)
64-
}
44+
funconPeerUpdate(_ diff:Data, reply:@escaping()->Void){
45+
letreply=CompletionWrapper(reply)
46+
Task{@MainActorin
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+
letreply=CompletionWrapper(reply)
6954
Task{@MainActorin
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{@MainActorin
76-
svc.onProgress(stage: stage, downloadProgress: downloadProgress)
61+
// These methods are called to request updatess from the Helper.
62+
extensionAppXPCListener{
63+
func ping()asyncthrows{
64+
letconn=connect()
65+
returntryawaitwithCheckedThrowingContinuation{ continuation in
66+
guardlet 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?HelperAppXPCInterfaceelse{
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-
letreply=CallbackWrapper(reply)
83-
Task{@MainActorin
84-
letprompt="""
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-
letsource="""
90-
do shell script "xattr -d com.apple.quarantine \(path)"\
91-
with prompt "\(prompt)"\
92-
with administrator privileges
93-
"""
94-
letsuccess=awaitwithCheckedContinuation{ continuation in
95-
guardlet script =NSAppleScript(source: source)else{
96-
continuation.resume(returning:false)
97-
return
98-
}
99-
// Run on a background thread
100-
Task.detached{
101-
varerror:NSDictionary?
102-
script.executeAndReturnError(&error)
103-
iflet error {
104-
self.logger.error("AppleScript error: \(error)")
105-
continuation.resume(returning:false)
106-
}else{
107-
continuation.resume(returning:true)
108-
}
81+
func getPeerState()asyncthrows{
82+
letconn=connect()
83+
returntryawaitwithCheckedThrowingContinuation{ continuation in
84+
guardlet 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?HelperAppXPCInterfaceelse{
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{@MainActorin
94+
self.svc.onExtensionPeerState(data)
10995
}
96+
continuation.resume()
11097
}
111-
reply(success)
11298
}
11399
}
114100
}

0 commit comments

Comments
(0)