@@ -4,16 +4,203 @@ 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 {
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 {
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( ) async throws ( ManagerError) {
107+ logger. info ( " sending start rpc " )
108+ guard let tunFd = ptp. tunnelFileDescriptor else {
109+ throw . noTunnelFileDescriptor
110+ }
111+ let resp : Vpn_TunnelMessage
112+ do {
113+ resp = try await speaker. unaryRPC ( . with { msg in
114+ msg. start = . with { req in
115+ req. tunnelFileDescriptor = tunFd
116+ req. apiToken = cfg. apiToken
117+ req. coderURL = cfg. serverUrl. absoluteString
118+ }
119+ } )
120+ } catch {
121+ throw . failedRPC( error)
122+ }
123+ guard case let . start( startResp) = resp. msg else {
124+ throw . incorrectResponse( resp)
125+ }
126+ if !startResp. success {
127+ throw . errorResponse( msg: startResp. errorMessage)
128+ }
129+ // TODO: notify app over XPC
130+ }
131+
132+ // TODO: Call via XPC
133+ func stopVPN( ) async throws ( ManagerError) {
134+ logger. info ( " sending stop rpc " )
135+ let resp : Vpn_TunnelMessage
136+ do {
137+ resp = try await speaker. unaryRPC ( . with { msg in
138+ msg. stop = . init( )
139+ } )
140+ } catch {
141+ throw . failedRPC( error)
142+ }
143+ guard case let . stop( stopResp) = resp. msg else {
144+ throw . incorrectResponse( resp)
145+ }
146+ if !stopResp. success {
147+ throw . errorResponse( msg: stopResp. errorMessage)
148+ }
149+ // TODO: notify app over XPC
150+ }
151+
152+ // TODO: Call via XPC
153+ // Retrieves the current state of all peers,
154+ // as required when starting the app whilst the network extension is already running
155+ func getPeerInfo( ) async throws ( ManagerError) {
156+ logger. info ( " sending peer state request " )
157+ let resp : Vpn_TunnelMessage
158+ do {
159+ resp = try await speaker. unaryRPC ( . with { msg in
160+ msg. getPeerUpdate = . init( )
161+ } )
162+ } catch {
163+ throw . failedRPC( error)
164+ }
165+ guard case . peerUpdate = resp. msg else {
166+ throw . incorrectResponse( resp)
167+ }
168+ // TODO: pass to app over XPC
169+ }
170+ }
171+
172+ public struct ManagerConfig {
173+ let apiToken : String
174+ let serverUrl : URL
175+ }
176+
177+ enum ManagerError : Error {
178+ case download( DownloadError )
179+ case tunnelSetup( TunnelHandleError )
180+ case handshake( HandshakeError )
181+ case validation( ValidationError )
182+ case incorrectResponse( Vpn_TunnelMessage )
183+ case failedRPC( any Error )
184+ case errorResponse( msg: String )
185+ case noTunnelFileDescriptor
186+ }
187+
188+ func writeVpnLog( _ log: Vpn_Log ) {
189+ let level : OSLogType = switch log. level {
190+ case . info: . info
191+ case . debug: . debug
192+ // warn == error
193+ case . warn: . error
194+ case . error: . error
195+ // critical == fatal == fault
196+ case . critical: . fault
197+ case . fatal: . fault
198+ case . UNRECOGNIZED: . info
199+ }
200+ let logger = Logger (
201+ subsystem: " \( Bundle . main. bundleIdentifier!) .dylib " ,
202+ category: log. loggerNames. joined ( separator: " . " )
203+ )
204+ let fields = log. fields. map { " \( $0. name) : \( $0. value) " } . joined ( separator: " , " )
205+ logger. log ( level: level, " \( log. message) : \( fields) " )
19206}
0 commit comments