@@ -45,8 +45,8 @@ enum VPNServiceError: Error, Equatable {
45
45
@MainActor
46
46
final class CoderVPNService : NSObject , VPNService {
47
47
var logger = Logger ( subsystem: Bundle . main. bundleIdentifier!, category: " vpn " )
48
- // TODO: better init maybe? kinda wonky
49
- lazy var xpc = VPNXPCInterface ( vpn : self )
48
+ lazy var xpc : VPNXPCInterface = . init( vpn : self )
49
+ var terminating = false
50
50
51
51
@Published var tunnelState : VPNServiceState = . disabled
52
52
@Published var sysExtnState : SystemExtensionState = . uninstalled
@@ -76,44 +76,39 @@ final class CoderVPNService: NSObject, VPNService {
76
76
}
77
77
}
78
78
79
- var startTask : Task < Void , Never > ?
80
79
func start( ) async {
81
- if await startTask? . value != nil {
82
- return
83
- }
80
+ guard tunnelState == . disabled else { return }
84
81
// this ping is somewhat load bearing since it causes xpc to init
85
82
xpc. ping ( )
86
- startTask = Task {
87
- tunnelState = . connecting
88
- await enableNetworkExtension ( )
89
- logger. debug ( " network extension enabled " )
90
- }
91
- defer { startTask = nil }
92
- await startTask? . value
83
+ tunnelState = . connecting
84
+ await enableNetworkExtension ( )
85
+ logger. debug ( " network extension enabled " )
93
86
}
94
87
95
- var stopTask: Task< Void, Never>?
96
88
func stop( ) async {
97
- // Wait for a start operation to finish first
98
- await startTask? . value
99
- guard state == . connected else { return }
100
- if await stopTask? . value != nil {
89
+ guard tunnelState == . connected else { return }
90
+ tunnelState = . disconnecting
91
+ await disableNetworkExtension ( )
92
+ logger. info ( " network extension stopped " )
93
+ }
94
+
95
+ // Instructs the service to stop the VPN and then quit once the stop event
96
+ // is read over XPC.
97
+ // MUST only be called from `NSApplicationDelegate.applicationShouldTerminate`
98
+ // MUST eventually call `NSApp.reply(toApplicationShouldTerminate: true)`
99
+ func quit( ) async {
100
+ guard tunnelState == . connected else {
101
+ NSApp . reply ( toApplicationShouldTerminate: true )
101
102
return
102
103
}
103
- stopTask = Task {
104
- tunnelState = . disconnecting
105
- await disableNetworkExtension ( )
106
- logger. info ( " network extension stopped " )
107
- tunnelState = . disabled
108
- }
109
- defer { stopTask = nil }
110
- await stopTask? . value
104
+ terminating = true
105
+ await stop ( )
111
106
}
112
107
113
108
func configureTunnelProviderProtocol( proto: NETunnelProviderProtocol ? ) {
114
109
Task {
115
- if proto != nil {
116
- await configureNetworkExtension ( proto: proto! )
110
+ if let proto {
111
+ await configureNetworkExtension ( proto: proto)
117
112
// this just configures the VPN, it doesn't enable it
118
113
tunnelState = . disabled
119
114
} else {
@@ -122,7 +117,7 @@ final class CoderVPNService: NSObject, VPNService {
122
117
neState = . unconfigured
123
118
tunnelState = . disabled
124
119
} catch {
125
- logger. error ( " failed to remoing network extension: \( error) " )
120
+ logger. error ( " failed to remove network extension: \( error) " )
126
121
neState = . failed( error. localizedDescription)
127
122
}
128
123
}
@@ -148,9 +143,13 @@ final class CoderVPNService: NSObject, VPNService {
148
143
func onExtensionStop( ) {
149
144
logger. info ( " network extension reported stopped " )
150
145
tunnelState = . disabled
146
+ if terminating {
147
+ NSApp . reply ( toApplicationShouldTerminate: true )
148
+ }
151
149
}
152
150
153
151
func onExtensionError( _ error: NSError ) {
154
- logger. info ( " network extension reported error: \( error) " )
152
+ logger. error ( " network extension reported error: \( error) " )
153
+ tunnelState = . failed( . internalError( error. localizedDescription) )
155
154
}
156
155
}
0 commit comments