@@ -5,17 +5,186 @@ import VPNLib
55actor Manager {
66 let ptp : PacketTunnelProvider
77 let downloader : Downloader
8+ let cfg : ManagerConfig
89
9- var tunnelHandle : TunnelHandle ?
10- var speaker : Speaker < Vpn_ManagerMessage , Vpn_TunnelMessage > ?
10+ let tunnelHandle : TunnelHandle
11+ let speaker : Speaker < Vpn_ManagerMessage , Vpn_TunnelMessage >
12+ var readLoop : Task < Void , Error > !
1113 // TODO: XPC Speaker
1214
1315 private let dest = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask)
1416 . first!. appending ( path: " coder-vpn.dylib " )
1517 private let logger = Logger ( subsystem: Bundle . main. bundleIdentifier!, category: " manager " )
1618
17- init ( with: PacketTunnelProvider ) {
19+ init ( with: PacketTunnelProvider , cfg : ManagerConfig ) async throws ( ManagerError ) {
1820 ptp = with
1921 downloader = Downloader ( )
22+ self . cfg = cfg
23+ #if arch(arm64)
24+ let dylibPath = cfg. serverUrl. appending ( path: " bin/coder-vpn-arm64.dylib " )
25+ #elseif arch(x86_64)
26+ let dylibPath = cfg. serverUrl. appending ( path: " bin/coder-vpn-amd64.dylib " )
27+ #else
28+ fatalError ( " unknown architecture " )
29+ #endif
30+ do {
31+ try await downloader. download ( src: dylibPath, dest: dest)
32+ } catch {
33+ throw . download( error)
34+ }
35+ do {
36+ try tunnelHandle = TunnelHandle ( dylibPath: dest)
37+ } catch {
38+ throw . tunnelSetup( error)
39+ }
40+ speaker = await Speaker < Vpn_ManagerMessage , Vpn_TunnelMessage > (
41+ writeFD: tunnelHandle. writeHandle,
42+ readFD: tunnelHandle. readHandle
43+ )
44+ do throws ( HandshakeError) {
45+ try await speaker. handshake ( )
46+ } catch {
47+ throw . handshake( error)
48+ }
49+ readLoop = Task {
50+ do {
51+ for try await m in speaker {
52+ switch m {
53+ case let . message( msg) :
54+ handleMessage ( msg)
55+ case let . RPC( rpc) :
56+ handleRPC ( rpc)
57+ }
58+ }
59+ } catch {
60+ logger. error ( " tunnel read loop failed: \( error) " )
61+ try ? await tunnelHandle. close ( )
62+ // TODO: Notify app over XPC
63+ }
64+ }
2065 }
66+
67+ func handleMessage( _ msg: Vpn_TunnelMessage ) {
68+ guard let msgType = msg. msg else {
69+ logger. critical ( " received message with no type " )
70+ return
71+ }
72+ switch msgType {
73+ case . peerUpdate:
74+ { } ( ) // TODO: Send over XPC
75+ case let . log( logMsg) :
76+ writeVpnLog ( logMsg)
77+ case . networkSettings, . start, . stop:
78+ logger. critical ( " received unexpected message: ` \( String ( describing: msgType) ) ` " )
79+ }
80+ }
81+
82+ func handleRPC( _ rpc: RPCRequest < Vpn_ManagerMessage , Vpn_TunnelMessage > ) {
83+ guard let msgType = rpc. msg. msg else {
84+ logger. critical ( " received rpc with no type " )
85+ return
86+ }
87+ switch msgType {
88+ case let . networkSettings( ns) :
89+ let neSettings = convertNetworkSettingsRequest ( ns)
90+ ptp. setTunnelNetworkSettings ( neSettings)
91+ case . log, . peerUpdate, . start, . stop:
92+ logger. critical ( " received unexpected rpc: ` \( String ( describing: msgType) ) ` " )
93+ }
94+ }
95+
96+ // TODO: Call via XPC
97+ func startVPN( apiToken: String , server: URL ) async throws ( ManagerError) {
98+ let resp : Vpn_TunnelMessage
99+ do {
100+ resp = try await speaker. unaryRPC ( . with { msg in
101+ msg. start = . with { req in
102+ // TODO: handle nil FD
103+ req. tunnelFileDescriptor = ptp. tunnelFileDescriptor!
104+ req. apiToken = apiToken
105+ req. coderURL = server. absoluteString
106+ }
107+ } )
108+ } catch {
109+ throw . failedRPC( error)
110+ }
111+ guard case let . start( startResp) = resp. msg else {
112+ throw . incorrectResponse( resp)
113+ }
114+ if !startResp. success {
115+ throw . errorResponse( msg: startResp. errorMessage)
116+ }
117+ // TODO: notify app over XPC
118+ }
119+
120+ // TODO: Call via XPC
121+ func stopVPN( ) async throws ( ManagerError) {
122+ let resp : Vpn_TunnelMessage
123+ do {
124+ resp = try await speaker. unaryRPC ( . with { msg in
125+ msg. stop = . init( )
126+ } )
127+ } catch {
128+ throw . failedRPC( error)
129+ }
130+ guard case let . stop( stopResp) = resp. msg else {
131+ throw . incorrectResponse( resp)
132+ }
133+ if !stopResp. success {
134+ throw . errorResponse( msg: stopResp. errorMessage)
135+ }
136+ // TODO: notify app over XPC
137+ }
138+
139+ // TODO: Call via XPC
140+ // Retrieves the current state of all peers,
141+ // as required when starting the app whilst the network extension is already running
142+ func getPeerInfo( ) async throws ( ManagerError) {
143+ let resp : Vpn_TunnelMessage
144+ do {
145+ resp = try await speaker. unaryRPC ( . with { msg in
146+ msg. getPeerUpdate = . init( )
147+ } )
148+ } catch {
149+ throw . failedRPC( error)
150+ }
151+ guard case . peerUpdate = resp. msg else {
152+ throw . incorrectResponse( resp)
153+ }
154+ // TODO: pass to app over XPC
155+ }
156+ }
157+
158+ public struct ManagerConfig {
159+ let apiToken : String
160+ let serverUrl : URL
161+ }
162+
163+ enum ManagerError : Error {
164+ case download( DownloadError )
165+ case tunnelSetup( TunnelHandleError )
166+ case handshake( HandshakeError )
167+ case incorrectResponse( Vpn_TunnelMessage )
168+ case failedRPC( Error )
169+ case errorResponse( msg: String )
170+ }
171+
172+ func writeVpnLog( _ log: Vpn_Log ) {
173+ let level : OSLogType = switch log. level {
174+ case . info: . info
175+ case . debug: . debug
176+ // warn == error
177+ case . warn: . error
178+ case . error: . error
179+ // critical == fatal == fault
180+ case . critical: . fault
181+ case . fatal: . fault
182+ case . UNRECOGNIZED: . info
183+ }
184+ let logger = Logger (
185+ subsystem: " \( Bundle . main. bundleIdentifier!) .dylib " ,
186+ category: log. loggerNames. joined ( separator: " . " )
187+ )
188+ let fields = log. fields. map { " \( $0. name) : \( $0. value) " } . joined ( separator: " , " )
189+ logger. log ( level: level, " \( log. message) : \( fields) " )
21190}
0 commit comments