@@ -4,16 +4,200 @@ import VPNLib
4
4
5
5
actor Manager {
6
6
let ptp : PacketTunnelProvider
7
+ let cfg : ManagerConfig
7
8
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 > !
10
12
// TODO: XPC Speaker
11
13
12
14
private let dest = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask)
13
15
. first!. appending ( path: " coder-vpn.dylib " )
14
16
private let logger = Logger ( subsystem: Bundle . main. bundleIdentifier!, category: " manager " )
15
17
16
- init ( with: PacketTunnelProvider ) {
18
+ init ( with: PacketTunnelProvider , cfg : ManagerConfig ) async throws ( ManagerError ) {
17
19
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 ( ) }
18
53
}
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) " )
19
203
}
0 commit comments