11import NetworkExtension
22import os
33import SwiftUI
4+ import VPNLib
5+ import VPNXPC
46
57@MainActor
68protocol VPNService : ObservableObject {
@@ -43,6 +45,9 @@ enum VPNServiceError: Error, Equatable {
4345@MainActor
4446final class CoderVPNService : NSObject , VPNService {
4547 var logger = Logger ( subsystem: Bundle . main. bundleIdentifier!, category: " vpn " )
48+ lazy var xpc : VPNXPCInterface = . init( vpn: self )
49+ var terminating = false
50+
4651 @Published var tunnelState : VPNServiceState = . disabled
4752 @Published var sysExtnState : SystemExtensionState = . uninstalled
4853 @Published var neState : NetworkExtensionState = . unconfigured
@@ -71,46 +76,45 @@ final class CoderVPNService: NSObject, VPNService {
7176 }
7277 }
7378
74- var startTask : Task < Void , Never > ?
7579 func start( ) async {
76- if await startTask? . value != nil {
80+ switch tunnelState {
81+ case . disabled, . failed:
82+ break
83+ default :
7784 return
7885 }
79- startTask = Task {
80- tunnelState = . connecting
81- await enableNetworkExtension ( )
8286
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 " )
8992 }
9093
91- var stopTask: Task< Void, Never>?
9294 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+ }
102100
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
105109 }
106- defer { stopTask = nil }
107- await stopTask ? . value
110+ terminating = true
111+ await stop ( )
108112 }
109113
110114 func configureTunnelProviderProtocol( proto: NETunnelProviderProtocol ? ) {
111115 Task {
112- if proto != nil {
113- await configureNetworkExtension ( proto: proto! )
116+ if let proto {
117+ await configureNetworkExtension ( proto: proto)
114118 // this just configures the VPN, it doesn't enable it
115119 tunnelState = . disabled
116120 } else {
@@ -119,10 +123,39 @@ final class CoderVPNService: NSObject, VPNService {
119123 neState = . unconfigured
120124 tunnelState = . disabled
121125 } catch {
122- logger. error ( " failed to remoing network extension: \( error) " )
126+ logger. error ( " failed to remove network extension: \( error) " )
123127 neState = . failed( error. localizedDescription)
124128 }
125129 }
126130 }
127131 }
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+ }
128161}
0 commit comments