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