1
+ import NetworkExtension
2
+ import WireGuardKit
3
+ import os
4
+
1
5
#if os(iOS)
2
- import Flutter
3
- import UIKit
6
+ import Flutter
7
+ import UIKit
4
8
#elseif os(macOS)
5
- import Cocoa
6
- import FlutterMacOS
9
+ import Cocoa
10
+ import FlutterMacOS
7
11
#else
8
- #error("Unsupported platform")
12
+ #error("Unsupported platform")
9
13
#endif
10
14
11
- import NetworkExtension
12
- import os
13
- import WireGuardKit
14
-
15
15
public class WireguardDartPlugin : NSObject , FlutterPlugin {
16
16
private var vpnManager : NETunnelProviderManager ?
17
-
18
17
var vpnStatus : NEVPNStatus {
19
18
vpnManager? . connection. status ?? NEVPNStatus . invalid
20
19
}
21
20
22
21
public static func register( with registrar: FlutterPluginRegistrar ) {
23
22
#if os(iOS)
24
- let messenger = registrar. messenger ( )
23
+ let messenger = registrar. messenger ( )
25
24
#else
26
- let messenger = registrar. messenger
25
+ let messenger = registrar. messenger
27
26
#endif
28
- let channel = FlutterMethodChannel ( name: " wireguard_dart " , binaryMessenger: messenger)
27
+ let channel = FlutterMethodChannel (
28
+ name: " wireguard_dart " , binaryMessenger: messenger)
29
29
30
30
let instance = WireguardDartPlugin ( )
31
31
registrar. addMethodCallDelegate ( instance, channel: channel)
32
32
33
- let statusChannel = FlutterEventChannel ( name: " wireguard_dart/status " , binaryMessenger: messenger)
33
+ let statusChannel = FlutterEventChannel (
34
+ name: " wireguard_dart/status " , binaryMessenger: messenger)
34
35
statusChannel. setStreamHandler ( ConnectionStatusObserver ( ) )
35
36
}
36
37
37
- public func handle( _ call: FlutterMethodCall , result: @escaping FlutterResult ) {
38
+ public func handle(
39
+ _ call: FlutterMethodCall , result: @escaping FlutterResult
40
+ ) {
38
41
switch call. method {
39
42
case " nativeInit " :
40
43
result ( " " )
41
44
case " generateKeyPair " :
42
45
let privateKey = PrivateKey ( )
43
46
let privateKeyResponse : [ String : Any ] = [
44
47
" privateKey " : privateKey. base64Key,
45
- " publicKey " : privateKey. publicKey. base64Key
48
+ " publicKey " : privateKey. publicKey. base64Key,
46
49
]
47
50
result ( privateKeyResponse)
48
51
case " setupTunnel " :
49
52
Logger . main. debug ( " handle setupTunnel " )
50
- guard let args = call. arguments as? [ String : Any ] , args [ " bundleId " ] != nil else {
51
- result ( nativeFlutterError ( message: " required argument: 'bundleId' " ) )
53
+ guard let args = call. arguments as? [ String : Any ] ,
54
+ args [ " bundleId " ] != nil
55
+ else {
56
+ result (
57
+ nativeFlutterError ( message: " required argument: 'bundleId' " )
58
+ )
52
59
return
53
60
}
54
- guard let bundleId = args [ " bundleId " ] as? String , !bundleId. isEmpty else {
55
- result ( nativeFlutterError ( message: " required argument: 'bundleId' " ) )
61
+ guard let bundleId = args [ " bundleId " ] as? String , !bundleId. isEmpty
62
+ else {
63
+ result (
64
+ nativeFlutterError ( message: " required argument: 'bundleId' " )
65
+ )
56
66
return
57
67
}
58
- guard let tunnelName = args [ " tunnelName " ] as? String , !tunnelName. isEmpty else {
59
- result ( nativeFlutterError ( message: " required argument: 'tunnelName' " ) )
68
+ guard let tunnelName = args [ " tunnelName " ] as? String ,
69
+ !tunnelName. isEmpty
70
+ else {
71
+ result (
72
+ nativeFlutterError (
73
+ message: " required argument: 'tunnelName' " ) )
60
74
return
61
75
}
62
- Logger . main. debug ( " Tunnel bundle ID: \( bundleId) , name: \( tunnelName) " )
76
+ Logger . main. debug (
77
+ " Tunnel bundle ID: \( bundleId) , name: \( tunnelName) " )
63
78
Task {
64
79
do {
65
- vpnManager = try await setupProviderManager ( bundleId: bundleId, tunnelName: tunnelName)
80
+ vpnManager = try await setupProviderManager (
81
+ bundleId: bundleId, tunnelName: tunnelName)
66
82
Logger . main. debug ( " Tunnel setup OK " )
67
83
result ( " " )
68
84
} catch {
69
85
Logger . main. error ( " Tunnel setup ERROR: \( error) " )
70
- result ( nativeFlutterError ( message: " could not setup VPN tunnel: \( error) " ) )
86
+ result (
87
+ nativeFlutterError (
88
+ message: " could not setup VPN tunnel: \( error) " ) )
71
89
return
72
90
}
73
91
}
74
92
case " connect " :
75
93
Logger . main. debug ( " handle connect " )
76
94
let cfg : String
77
95
if let args = call. arguments as? [ String : Any ] ,
78
- let argCfg = args [ " cfg " ] as? String {
96
+ let argCfg = args [ " cfg " ] as? String
97
+ {
79
98
cfg = argCfg
80
99
} else {
81
100
Logger . main. error ( " Required argument 'cfg' not provided " )
82
101
result ( nativeFlutterError ( message: " required argument: 'cfg' " ) )
83
102
return
84
103
}
85
104
guard let mgr = vpnManager else {
86
- Logger . main. error ( " Tunnel not initialized, missing 'vpnManager' " )
87
- result ( nativeFlutterError ( message: " tunnel not initialized, missing 'vpnManager' " ) )
105
+ Logger . main. error (
106
+ " Tunnel not initialized, missing 'vpnManager' " )
107
+ result (
108
+ nativeFlutterError (
109
+ message: " tunnel not initialized, missing 'vpnManager' " )
110
+ )
88
111
return
89
112
}
90
113
Logger . main. debug ( " Connection configuration: \( cfg) " )
91
114
Task {
92
115
do {
116
+ if !mgr. isEnabled {
117
+ mgr. isEnabled = true
118
+ try await mgr. saveToPreferences ( )
119
+ try await mgr. loadFromPreferences ( )
120
+
121
+ }
122
+
93
123
try mgr. connection. startVPNTunnel ( options: [
94
124
" cfg " : cfg as NSObject
95
125
] )
96
126
Logger . main. debug ( " Start VPN tunnel OK " )
97
127
result ( " " )
128
+ } catch let error as NEVPNError {
129
+ switch error. code {
130
+ case . configurationInvalid:
131
+ Logger . main. error (
132
+ " Start VPN tunnel ERROR: Configuration is invalid " )
133
+ result (
134
+ nativeFlutterError (
135
+ message:
136
+ " could not start VPN tunnel: Configuration is invalid "
137
+ ) )
138
+ case . configurationDisabled:
139
+ Logger . main. error (
140
+ " Start VPN tunnel ERROR: Configuration is disabled " )
141
+ result (
142
+ nativeFlutterError (
143
+ message:
144
+ " could not start VPN tunnel: Configuration is disabled "
145
+ ) )
146
+ case . connectionFailed:
147
+ Logger . main. error (
148
+ " Start VPN tunnel ERROR: Connection failed " )
149
+ result (
150
+ nativeFlutterError (
151
+ message:
152
+ " could not start VPN tunnel: Connection failed "
153
+ ) )
154
+ case . configurationStale:
155
+ Logger . main. error (
156
+ " Start VPN tunnel ERROR: Configuration is stale " )
157
+ result (
158
+ nativeFlutterError (
159
+ message:
160
+ " could not start VPN tunnel: Configuration is stale "
161
+ ) )
162
+ case . configurationReadWriteFailed:
163
+ Logger . main. error (
164
+ " Start VPN tunnel ERROR: Configuration read/write failed "
165
+ )
166
+ result (
167
+ nativeFlutterError (
168
+ message:
169
+ " could not start VPN tunnel: Configuration read/write failed "
170
+ ) )
171
+ case . configurationUnknown:
172
+ Logger . main. error (
173
+ " Start VPN tunnel ERROR: Configuration unknown " )
174
+ result (
175
+ nativeFlutterError (
176
+ message:
177
+ " could not start VPN tunnel: Configuration unknown "
178
+ ) )
179
+ @unknown default :
180
+ Logger . main. error (
181
+ " Start VPN tunnel ERROR: Unknown error " )
182
+ result (
183
+ nativeFlutterError (
184
+ message:
185
+ " could not start VPN tunnel: Unknown error " )
186
+ )
187
+ }
98
188
} catch {
99
189
Logger . main. error ( " Start VPN tunnel ERROR: \( error) " )
100
- result ( nativeFlutterError ( message: " could not start VPN tunnel: \( error) " ) )
190
+ result (
191
+ nativeFlutterError (
192
+ message: " could not start VPN tunnel: \( error) " ) )
101
193
}
102
194
}
103
195
case " disconnect " :
104
196
guard let mgr = vpnManager else {
105
- Logger . main. error ( " Tunnel not initialized, missing 'vpnManager' " )
106
- result ( nativeFlutterError ( message: " tunnel not initialized, missing 'vpnManager' " ) )
197
+ Logger . main. error (
198
+ " Tunnel not initialized, missing 'vpnManager' " )
199
+ result (
200
+ nativeFlutterError (
201
+ message: " tunnel not initialized, missing 'vpnManager' " )
202
+ )
107
203
return
108
204
}
109
205
Task {
@@ -112,30 +208,65 @@ public class WireguardDartPlugin: NSObject, FlutterPlugin {
112
208
result ( " " )
113
209
}
114
210
case " status " :
115
- guard vpnManager != nil else {
116
- Logger . main. error ( " Tunnel not initialized, missing 'vpnManager' " )
117
- result ( nativeFlutterError ( message: " tunnel not initialized, missing 'vpnManager' " ) )
211
+ if vpnManager != nil {
212
+ Task {
213
+ result (
214
+ ConnectionStatus . fromNEVPNStatus ( status: vpnStatus)
215
+ . string ( ) )
216
+ }
217
+ } else {
218
+ result ( ConnectionStatus . unknown. string ( ) )
219
+ }
220
+ case " checkTunnelConfiguration " :
221
+ guard let args = call. arguments as? [ String : Any ] ,
222
+ let bundleId = args [ " bundleId " ] as? String , !bundleId. isEmpty
223
+ else {
224
+ result (
225
+ nativeFlutterError ( message: " required argument: 'bundleId' " )
226
+ )
118
227
return
119
228
}
120
- Task {
121
- result ( ConnectionStatus . fromNEVPNStatus ( status: vpnStatus) . string ( ) )
229
+ guard let args = call. arguments as? [ String : Any ] ,
230
+ let tunnelName = args [ " tunnelName " ] as? String ,
231
+ !tunnelName. isEmpty
232
+ else {
233
+ result (
234
+ nativeFlutterError (
235
+ message: " required argument: 'tunnelName' " )
236
+ )
237
+ return
238
+ }
239
+ checkTunnelConfiguration ( bundleId: bundleId, tunnelName: tunnelName)
240
+ { manager in
241
+ if let vpnManager = manager {
242
+ self . vpnManager = vpnManager
243
+ Logger . main. debug ( " Tunnel is set up and existing " )
244
+ result ( true )
245
+ } else {
246
+ Logger . main. debug ( " Tunnel is not set up " )
247
+ result ( false )
248
+ }
122
249
}
250
+
123
251
default :
124
252
result ( FlutterMethodNotImplemented)
125
253
}
126
254
}
127
255
128
- func setupProviderManager( bundleId: String , tunnelName: String ) async throws -> NETunnelProviderManager {
256
+ func setupProviderManager( bundleId: String , tunnelName: String ) async throws
257
+ -> NETunnelProviderManager
258
+ {
129
259
let mgrs = try await NETunnelProviderManager . loadAllFromPreferences ( )
130
260
let existingMgr = mgrs. first ( where: {
131
- ( $0. protocolConfiguration as? NETunnelProviderProtocol ) ? . providerBundleIdentifier == bundleId
261
+ ( $0. protocolConfiguration as? NETunnelProviderProtocol ) ?
262
+ . providerBundleIdentifier == bundleId
132
263
} )
133
264
let mgr = existingMgr ?? NETunnelProviderManager ( )
134
265
135
266
mgr. localizedDescription = tunnelName
136
267
let proto = NETunnelProviderProtocol ( )
137
268
proto. providerBundleIdentifier = bundleId
138
- proto. serverAddress = " " // must be non-null
269
+ proto. serverAddress = " " // must be non-null
139
270
mgr. protocolConfiguration = proto
140
271
mgr. isEnabled = true
141
272
@@ -144,4 +275,35 @@ public class WireguardDartPlugin: NSObject, FlutterPlugin {
144
275
145
276
return mgr
146
277
}
278
+
279
+ func isVpnManagerConfigured( bundleId: String , tunnelName: String )
280
+ async throws -> NETunnelProviderManager ?
281
+ {
282
+ // Load all managers from preferences
283
+ let mgrs = try await NETunnelProviderManager . loadAllFromPreferences ( )
284
+ if let existingMgr = mgrs. first ( where: {
285
+ ( $0. protocolConfiguration as? NETunnelProviderProtocol ) ?
286
+ . providerBundleIdentifier == bundleId
287
+ } ) {
288
+ return existingMgr
289
+ }
290
+ return nil
291
+ }
292
+
293
+ func checkTunnelConfiguration(
294
+ bundleId: String , tunnelName: String ,
295
+ result: @escaping ( NETunnelProviderManager ? ) -> Void
296
+ ) {
297
+ Task {
298
+ do {
299
+ let mgr = try await isVpnManagerConfigured (
300
+ bundleId: bundleId, tunnelName: tunnelName)
301
+ result ( mgr)
302
+ } catch {
303
+ Logger . main. error (
304
+ " Error checking tunnel configuration: \( error) " )
305
+ result ( nil )
306
+ }
307
+ }
308
+ }
147
309
}
0 commit comments