1
1
import NetworkExtension
2
2
import os
3
3
import SwiftUI
4
+ import VPNLib
5
+ import VPNXPC
4
6
5
7
@MainActor
6
8
protocol VPNService : ObservableObject {
@@ -43,6 +45,9 @@ enum VPNServiceError: Error, Equatable {
43
45
@MainActor
44
46
final class CoderVPNService : NSObject , VPNService {
45
47
var logger = Logger ( subsystem: Bundle . main. bundleIdentifier!, category: " vpn " )
48
+ lazy var xpc : VPNXPCInterface = . init( vpn: self )
49
+ var terminating = false
50
+
46
51
@Published var tunnelState : VPNServiceState = . disabled
47
52
@Published var sysExtnState : SystemExtensionState = . uninstalled
48
53
@Published var neState : NetworkExtensionState = . unconfigured
@@ -71,46 +76,45 @@ final class CoderVPNService: NSObject, VPNService {
71
76
}
72
77
}
73
78
74
- var startTask : Task < Void , Never > ?
75
79
func start( ) async {
76
- if await startTask? . value != nil {
80
+ switch tunnelState {
81
+ case . disabled, . failed:
82
+ break
83
+ default :
77
84
return
78
85
}
79
- startTask = Task {
80
- tunnelState = . connecting
81
- await enableNetworkExtension ( )
82
86
83
- // TODO: enable communication with the NetworkExtension to track state and agents. For
84
- // now, just pretend it worked...
85
- tunnelState = . connected
86
- }
87
- defer { startTask = nil }
88
- await startTask? . value
87
+ // this ping is somewhat load bearing since it causes xpc to init
88
+ xpc. ping ( )
89
+ tunnelState = . connecting
90
+ await enableNetworkExtension ( )
91
+ logger. debug ( " network extension enabled " )
89
92
}
90
93
91
- var stopTask: Task< Void, Never>?
92
94
func stop( ) async {
93
- // Wait for a start operation to finish first
94
- await startTask? . value
95
- guard state == . connected else { return }
96
- if await stopTask? . value != nil {
97
- return
98
- }
99
- stopTask = Task {
100
- tunnelState = . disconnecting
101
- await disableNetworkExtension ( )
95
+ guard tunnelState == . connected else { return }
96
+ tunnelState = . disconnecting
97
+ await disableNetworkExtension ( )
98
+ logger. info ( " network extension stopped " )
99
+ }
102
100
103
- // TODO: determine when the NetworkExtension is completely disconnected
104
- tunnelState = . disabled
101
+ // Instructs the service to stop the VPN and then quit once the stop event
102
+ // is read over XPC.
103
+ // MUST only be called from `NSApplicationDelegate.applicationShouldTerminate`
104
+ // MUST eventually call `NSApp.reply(toApplicationShouldTerminate: true)`
105
+ func quit( ) async {
106
+ guard tunnelState == . connected else {
107
+ NSApp . reply ( toApplicationShouldTerminate: true )
108
+ return
105
109
}
106
- defer { stopTask = nil }
107
- await stopTask ? . value
110
+ terminating = true
111
+ await stop ( )
108
112
}
109
113
110
114
func configureTunnelProviderProtocol( proto: NETunnelProviderProtocol ? ) {
111
115
Task {
112
- if proto != nil {
113
- await configureNetworkExtension ( proto: proto! )
116
+ if let proto {
117
+ await configureNetworkExtension ( proto: proto)
114
118
// this just configures the VPN, it doesn't enable it
115
119
tunnelState = . disabled
116
120
} else {
@@ -119,10 +123,39 @@ final class CoderVPNService: NSObject, VPNService {
119
123
neState = . unconfigured
120
124
tunnelState = . disabled
121
125
} catch {
122
- logger. error ( " failed to remoing network extension: \( error) " )
126
+ logger. error ( " failed to remove network extension: \( error) " )
123
127
neState = . failed( error. localizedDescription)
124
128
}
125
129
}
126
130
}
127
131
}
132
+
133
+ func onExtensionPeerUpdate( _ data: Data ) {
134
+ // TODO: handle peer update
135
+ logger. info ( " network extension peer update " )
136
+ do {
137
+ let msg = try Vpn_TunnelMessage ( serializedBytes: data)
138
+ debugPrint ( msg)
139
+ } catch {
140
+ logger. error ( " failed to decode peer update \( error) " )
141
+ }
142
+ }
143
+
144
+ func onExtensionStart( ) {
145
+ logger. info ( " network extension reported started " )
146
+ tunnelState = . connected
147
+ }
148
+
149
+ func onExtensionStop( ) {
150
+ logger. info ( " network extension reported stopped " )
151
+ tunnelState = . disabled
152
+ if terminating {
153
+ NSApp . reply ( toApplicationShouldTerminate: true )
154
+ }
155
+ }
156
+
157
+ func onExtensionError( _ error: NSError ) {
158
+ logger. error ( " network extension reported error: \( error) " )
159
+ tunnelState = . failed( . internalError( error. localizedDescription) )
160
+ }
128
161
}
0 commit comments