-
Notifications
You must be signed in to change notification settings - Fork 85
/
Copy pathPacketTunnelProvider.swift
534 lines (459 loc) · 19.3 KB
/
PacketTunnelProvider.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
//
// PacketTunnelProvider.swift
// LockdownTunnel
//
// Copyright © 2019 Confirmed Inc. All rights reserved.
//
import NetworkExtension
import NEKit
import Dnscryptproxy
import Network
import PromiseKit
import CocoaLumberjack
var latestBlockedDomains = getAllBlockedDomains()
class PacketTunnelProvider: NEPacketTunnelProvider {
let dnsServerAddress = "127.0.0.1"
var dns: DNSCryptThread?
let proxyServerAddress = "127.0.0.1"
let proxyServerPort: UInt16 = 9090
var proxyServer: GCDHTTPProxyServer?
let monitor = NWPathMonitor()
let fileManager = FileManager.default
let groupContainer = "group.com.confirmed"
let lastReachabilityKillKey = "lastReachabilityKillTime"
private var token: NSObjectProtocol?
private let center = NotificationCenter.default
private var proxyError: Error?
func log(_ str: String) {
PacketTunnelProviderLogs.log(str)
NSLog("ptplog - " + str)
}
override func cancelTunnelWithError(_ error: Error?) {
self.log("===== ERROR - cancelTunnelWithError \(error?.localizedDescription ?? "")")
}
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
log("+++++ startTunnel NEW")
// reachability check
monitor.pathUpdateHandler = { [weak self] path in
self?.pathUpdateHandler(path: path)
}
log("Calling setTunnelNetworkSettings")
initializeDns();
initializeProxy();
setupObserverDNSCryptProxyReady(completionHandler: completionHandler)
startDns();
proxyError = startProxy()
}
private func setupObserverDNSCryptProxyReady(completionHandler: @escaping (Error?) -> Void) {
token = center.addObserver(
forName: Notification.Name(kDNSCryptProxyReady),
object: nil,
queue: .main
) { [weak self] notification in
self?.dnsCryptProxyReady(completionHandler: completionHandler)
}
}
private func dnsCryptProxyReady(completionHandler: @escaping (Error?) -> Void) {
log("Found available resolvers, tell iOS we are ready")
if let token {
center.removeObserver(token)
}
updateTunnelSetting(completionHandler: completionHandler)
let queue = DispatchQueue(label: "Monitor")
monitor.start(queue: queue)
}
private func updateTunnelSetting(completionHandler: @escaping (Error?) -> Void) {
let networkSettings = getNetworkSettings();
self.setTunnelNetworkSettings(networkSettings) { [weak self] error in
guard let self else { return }
if let error {
self.log("ERROR - StartTunnel \(error.localizedDescription)")
completionHandler(error);
} else {
self.log("No error on setTunnelNetworkSettings, starting dns and proxy")
if let proxyError = self.proxyError {
self.log("ERROR - Failed to start proxy: \(proxyError)")
completionHandler(proxyError)
}
else {
self.log("SUCCESS - startTunnel")
completionHandler(nil)
}
self.proxyError = nil
}
}
}
private func refreshServers() {
stopProxyServer()
dns?.closeIdleConnections()
dns?.refreshServersInfo()
initializeProxy()
_ = startProxy()
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
self.log("+++++ stopTunnel with reason: \(reason)")
monitor.cancel()
stopProxyServer()
stopDnsServer()
self.log("stopTunnel completionHandler, exit")
completionHandler();
exit(EXIT_SUCCESS);
}
// override func wake() {
// log("===== wake")
// flushBlockLog(log: log)
// log("wake setting tunnel network settings to nil")
// self.setTunnelNetworkSettings(nil, completionHandler: { error in
// if (error != nil) {
// self.log("error setting tunnelnetworksettings to nil: \(error)")
// }
// self.log("wake calling reactivate tunnel")
// self.reactivateTunnel()
// })
// }
func getNetworkSettings() -> NEPacketTunnelNetworkSettings {
log("===== getNetworkSettings")
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: dnsServerAddress)
networkSettings.mtu = 1500
let proxySettings = NEProxySettings()
proxySettings.httpEnabled = true;
proxySettings.httpServer = NEProxyServer(address: proxyServerAddress, port: Int(proxyServerPort))
proxySettings.httpsEnabled = true;
proxySettings.httpsServer = NEProxyServer(address: proxyServerAddress, port: Int(proxyServerPort))
proxySettings.excludeSimpleHostnames = false;
proxySettings.exceptionList = []
proxySettings.matchDomains = getAllWhitelistedDomains()
networkSettings.proxySettings = proxySettings;
let dnsSettings = NEDNSSettings(servers: [dnsServerAddress])
dnsSettings.matchDomains = [""];
networkSettings.dnsSettings = dnsSettings;
return networkSettings;
}
func initializeAndReturnConfigPath() -> String {
log("===== initializeAndReturnConfigPath")
let configFile = Bundle.main.url(forResource: "dnscrypt-proxy", withExtension: "toml")
let sharedDir = fileManager.containerURL(forSecurityApplicationGroupIdentifier: groupContainer)
// remove blocklist if it exists
let newBlocklistFile = sharedDir!.appendingPathComponent("blocklist.txt")
if fileManager.fileExists(atPath: newBlocklistFile.path) {
log("blocklist.txt exists")
do {
try fileManager.removeItem(atPath: newBlocklistFile.path)
log("removed old blocklist.txt")
} catch {
log("ERROR - couldnt remove old blocklist.txt: \(error)")
}
}
// generate text for new blocklist
let blockedDomainsArray = getAllBlockedDomains()
var blockedDomains: String = testFirewallDomain
for blockedDomain in blockedDomainsArray {
blockedDomains = blockedDomains + "\n" + blockedDomain
}
// copy blocklist file into shared dir
do {
try blockedDomains.write(to: newBlocklistFile, atomically: false, encoding: .utf8)
log("wrote content to blocklist.txt")
}
catch {
log("ERROR - couldnt write content to blocklist.txt: \(error)")
}
// clear prefix suffix files
let prefixFile = sharedDir!.appendingPathComponent("blacklist.txt.prefixes")
let suffixFile = sharedDir!.appendingPathComponent("blacklist.txt.suffixes")
if fileManager.fileExists(atPath: prefixFile.path){
log("prefix file exists at: \(prefixFile.path)")
do {
try fileManager.removeItem(atPath: prefixFile.path)
log("prefix file removed at: \(prefixFile.path)")
} catch {
}
}
if fileManager.fileExists(atPath: suffixFile.path){
do {
try fileManager.removeItem(atPath: suffixFile.path)
log("suffix file removed at: \(suffixFile.path)")
} catch {
log("ERROR - error removing suffix file: \(error)")
}
}
// create new prefix/suffix files
let errorPtr: NSErrorPointer = nil
log("filling in prefix/suffix files at: \(newBlocklistFile.path)")
DnscryptproxyFillPatternlistTrees(newBlocklistFile.path, errorPtr)
if let error = errorPtr?.pointee {
log("ERROR - filling in prefix/suffix files: \(error)")
}
// read config file template
var configFileText = ""
do {
configFileText = try String(contentsOf: configFile!, encoding: .utf8)
log("Read config file template")
}
catch {
log("ERROR - couldn't read config file template text at: \(configFile!.path)")
}
// replace BLOCKLIST_FILE_HERE and BLOCKLIST_LOG_HERE with urls of blocklist file/log
let replacedConfig = configFileText.replacingOccurrences(of: "BLOCKLIST_FILE_HERE", with: "\(newBlocklistFile.path)").replacingOccurrences(of: "BLOCKLIST_LOG_HERE", with: "\(sharedDir!.appendingPathComponent("blocklist.log").path)")
// write replaced string to new file
let replacedConfigURL = sharedDir!.appendingPathComponent("replaced-config.toml")
log("replaced config file url: \(replacedConfigURL.path)")
do {
try replacedConfig.write(to: replacedConfigURL, atomically: false, encoding: .utf8)
log("replaced config written")
}
catch {
log("ERROR - couldn't write replaced config: \(error)")
}
log("returning replacedConfigURL \(replacedConfigURL)")
return replacedConfigURL.path
}
func initializeDns() {
log("===== initialize DNS server")
// stopDnsServer()
log("initializing DNSCryptThread")
dns = DNSCryptThread(arguments: [initializeAndReturnConfigPath()]);
}
func initializeProxy() {
log("===== initialize proxy server")
// stopProxyServer()
log("initializing GCDHTTPProxyServer")
proxyServer = GCDHTTPProxyServer(address: IPAddress(fromString: self.proxyServerAddress), port: Port(port: self.proxyServerPort))
}
func startProxy() -> Error? {
log("===== startProxy")
do {
try self.proxyServer?.start()
log("started proxyServer")
return nil
} catch {
log("ERROR - couldnt start proxyServer")
return error
}
}
func startDns() {
log("===== startDns")
dns?.start()
}
func stopDnsServer() {
log("===== stopDnsServer")
guard let dns else { return }
log("dns is not nil")
log("dns closing idle connections")
dns.closeIdleConnections()
log("dns stopApp")
dns.stopApp()
log("dns set to nil")
self.dns = nil
}
func stopProxyServer() {
log("===== stopProxyServer")
guard let proxyServer else { return }
log("proxyServer is not nil")
log("proxyServer stop")
proxyServer.stop()
log("proxyServer nil")
self.proxyServer = nil
}
// func reactivateTunnel() {
// log("===== reactivateTunnel, reasserting true")
// reasserting = true
//
// let networkSettings = getNetworkSettings()
//
// self.setTunnelNetworkSettings(networkSettings) { [weak self] error in
// guard let self else { return }
// if let error {
// self.log("ERROR - reactivateTunnel setTunnelNetworkSettings: \(error.localizedDescription)")
// }
// self.log("reactivateTunnel setTunnelNetworkSettings complete, reasserting false")
// self.reasserting = false
//
// self._dns.closeIdleConnections()
// self.log("closed idle connections")
//
// self.log("||||| reactivate AFTER - checking availability to apple.com")
// self.checkNetworkConnection { [weak self] success in
// guard let self else { return }
// self.log("ReactivateTunnel checkNetworkConnection result: \(success)")
// }
// }
//
// startDns()
// }
// MARK: - reachability
func pathUpdateHandler(path: Network.NWPath) {
log("REACHABILITY - Connected: \(path.status == .satisfied) - NWPATH: \(path.debugDescription)")
if path.usesInterfaceType(.wifi) {
log("REACHABILITY - have connection to wifi")
}
if path.usesInterfaceType(.cellular) {
log("REACHABILITY - have connection to cellular")
}
let servers = Resolver().getservers().map(Resolver.getnameinfo)
log("REACHABILITY DNS Servers: \(servers)")
log("reachability testing network")
// self.checkNetworkConnection { [weak self] success in
// guard let self else { return }
// self.log("reachability network check result: \(success)")
// if( success == false ) {
// self.log("ERROR - network check failed, killing PTP if not killed in the last 30 seconds")
//
// // only kill PTP if it hasnt been killed in the last 30 seconds - to avoid race conditions/infinite loop
// // TODO: maybe force VPN restart too?
// // TODO: maybe force wait a second on stopping?
// // TODO: make this smarter e.g- if PTP has been killed in the last 30 seconds, wait 10 seconds to kill it
// let timeIntervalOfLastReachabilityKill = defaults.double(forKey: self.lastReachabilityKillKey)
// let dateOfLastReachabilityKill = Date(timeIntervalSince1970: timeIntervalOfLastReachabilityKill)
// let timeSinceLastReachabilityKill = Date().timeIntervalSince(dateOfLastReachabilityKill)
// self.log("REACHABILITY kill - time since last kill: \(timeSinceLastReachabilityKill)")
// if (timeSinceLastReachabilityKill < 60) {
// self.log("REACHABILITY kill - did this < 30 seconds ago, not calling it again")
// return
// }
// else {
// // do the kill
// defaults.set(Date().timeIntervalSince1970, forKey: self.lastReachabilityKillKey)
// }
// }
// }
}
func checkNetworkConnection(callback: @escaping (Bool) -> Void, attempt: Int = 1) {
log("===== checkNetworkConnection - attempt #\(attempt)")
URLCache.shared.removeAllCachedResponses()
firstly {
try makeNetworkConnection()
}
.map { [weak self] data, response -> Void in
guard let self else { return }
try self.validateNetworkResponse(response: response)
callback(true)
}
.catch { [weak self] error in
guard let self else { return }
self.log("ERROR - failed checkNetworkConnection attempt #\(attempt): \(error)")
if attempt < 3 {
DispatchQueue.global(qos: .default).asyncAfter(deadline: DispatchTime.now() + (attempt == 1 ? 5 : 15)) {
self.checkNetworkConnection(callback: callback, attempt: attempt + 1)
}
} else {
self.log("ERROR - failed checkNetworkConnection attempt #\(attempt): \(error)")
callback(false)
}
}
}
func makeNetworkConnection() throws -> Promise<(data: Data, response: URLResponse)> {
return URLSession.shared.dataTask(.promise, with: try Client.makeGetRequest(urlString: "https://apple.com"))
}
func validateNetworkResponse(response: URLResponse?) throws {
self.log("validating checkNetworkConnection response")
if let resp = response as? HTTPURLResponse {
if (resp.statusCode >= 400 || resp.statusCode <= 0) {
self.log("response has bad status code \(resp.statusCode)")
throw "response has bad status code \(resp.statusCode)"
}
else {
self.log("response has good status code (2xx, 3xx) and no error code")
}
}
else {
throw "Invalid URL Response received: \(String(describing: response))"
}
}
}
extension PacketTunnelProvider {
#if DEBUG
static let debugLogsKey = AppGroupStorage.Key<[String]>(rawValue: "com.confirmed.packettunnelprovider.debuglogs")
func debugLog(_ string: String) {
let string = "DEBUG LOG \(PacketTunnelProviderLogs.dateFormatter.string(from: Date())) \(string)"
if var existing = AppGroupStorage.shared.read(key: PacketTunnelProvider.debugLogsKey) {
existing.append(string)
AppGroupStorage.shared.write(content: existing, key: PacketTunnelProvider.debugLogsKey)
} else {
AppGroupStorage.shared.write(content: [string], key: PacketTunnelProvider.debugLogsKey)
}
}
func flushDebugLogsToPacketTunnelProviderLogs() {
if let existing = AppGroupStorage.shared.read(key: PacketTunnelProvider.debugLogsKey) {
for entry in existing {
PacketTunnelProviderLogs.log(entry)
}
AppGroupStorage.shared.delete(forKey: PacketTunnelProvider.debugLogsKey)
}
}
#endif
}
open class Resolver {
fileprivate var state = __res_9_state()
public init() {
res_9_ninit(&state)
}
deinit {
res_9_ndestroy(&state)
}
public final func getservers() -> [res_9_sockaddr_union] {
let maxServers = 10
var servers = [res_9_sockaddr_union](repeating: res_9_sockaddr_union(), count: maxServers)
let found = Int(res_9_getservers(&state, &servers, Int32(maxServers)))
// filter is to remove the erroneous empty entry when there's no real servers
return Array(servers[0 ..< found]).filter() { $0.sin.sin_len > 0 }
}
}
extension Resolver {
public static func getnameinfo(_ s: res_9_sockaddr_union) -> String {
var s = s
var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
let sinlen = socklen_t(s.sin.sin_len)
let _ = withUnsafePointer(to: &s) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
Darwin.getnameinfo($0, sinlen,
&hostBuffer, socklen_t(hostBuffer.count),
nil, 0,
NI_NUMERICHOST)
}
}
return String(cString: hostBuffer)
}
}
extension NEProviderStopReason: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .none:
return "none"
case .userInitiated:
return "userInitiated"
case .providerFailed:
return "providerFailed"
case .noNetworkAvailable:
return "noNetworkAvailable"
case .unrecoverableNetworkChange:
return "unrecoverableNetworkChange"
case .providerDisabled:
return "providerDisabled"
case .authenticationCanceled:
return "authenticationCanceled"
case .configurationFailed:
return "configurationFailed"
case .idleTimeout:
return "idleTimeout"
case .configurationDisabled:
return "configurationDisabled"
case .configurationRemoved:
return "configurationRemoved"
case .superceded:
return "superceded"
case .userLogout:
return "userLogout"
case .userSwitch:
return "userSwitch"
case .connectionFailed:
return "connectionFailed"
case .sleep:
return "sleep"
case .appUpdate:
return "appUpdate"
case .internalError:
return "internalError"
}
}
}