@@ -4,16 +4,203 @@ 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
+ 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 = apiToken
117
+ req. coderURL = server. 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) " )
19
206
}
0 commit comments