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