From 580792420228bdc8970016445bfe9628e34b601f Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Fri, 12 Jun 2020 23:08:25 +0200 Subject: [PATCH] Create "Manager" subspec - Move VPN helpers from Passepartout - Initialize VPN.shared explicitly - Expose internal *VPNConfiguration constructors --- TunnelKit.podspec | 7 + TunnelKit.xcodeproj/project.pbxproj | 44 +++ .../Sources/Manager/MockVPNProvider.swift | 85 +++++ .../Sources/Manager/StandardVPNProvider.swift | 316 ++++++++++++++++++ TunnelKit/Sources/Manager/VPN.swift | 30 ++ .../Sources/Manager/VPNConfiguration.swift | 41 +++ TunnelKit/Sources/Manager/VPNProvider.swift | 60 ++++ TunnelKit/Sources/Manager/VPNStatus.swift | 36 ++ 8 files changed, 619 insertions(+) create mode 100644 TunnelKit/Sources/Manager/MockVPNProvider.swift create mode 100644 TunnelKit/Sources/Manager/StandardVPNProvider.swift create mode 100644 TunnelKit/Sources/Manager/VPN.swift create mode 100644 TunnelKit/Sources/Manager/VPNConfiguration.swift create mode 100644 TunnelKit/Sources/Manager/VPNProvider.swift create mode 100644 TunnelKit/Sources/Manager/VPNStatus.swift diff --git a/TunnelKit.podspec b/TunnelKit.podspec index d7849d2..0c3262f 100644 --- a/TunnelKit.podspec +++ b/TunnelKit.podspec @@ -33,6 +33,13 @@ Pod::Spec.new do |s| p.dependency "TunnelKit/Core" end + s.subspec "Manager" do |p| + p.source_files = "TunnelKit/Sources/Manager/**/*.swift" + p.frameworks = "NetworkExtension" + + p.dependency "SwiftyBeaver" + end + s.subspec "Protocols" do |t| t.subspec "OpenVPN" do |p| p.source_files = "TunnelKit/Sources/Protocols/OpenVPN/**/*.{h,m,swift}" diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index 6cd6361..91dee2f 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -168,6 +168,18 @@ 0E7F3F6B246ABA0F006BE77F /* IPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F3F69246ABA0F006BE77F /* IPHeader.swift */; }; 0EA82A282190B220007960EB /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E3251C51F95770D00C108D9 /* TunnelKit.framework */; }; 0EA82A3E2190B2BC007960EB /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; }; + 0EAC57372494277A00D0FCE0 /* StandardVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57312494277A00D0FCE0 /* StandardVPNProvider.swift */; }; + 0EAC57382494277A00D0FCE0 /* StandardVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57312494277A00D0FCE0 /* StandardVPNProvider.swift */; }; + 0EAC57392494277A00D0FCE0 /* MockVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */; }; + 0EAC573A2494277A00D0FCE0 /* MockVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */; }; + 0EAC573B2494277A00D0FCE0 /* VPNStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57332494277A00D0FCE0 /* VPNStatus.swift */; }; + 0EAC573C2494277A00D0FCE0 /* VPNStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57332494277A00D0FCE0 /* VPNStatus.swift */; }; + 0EAC573D2494277A00D0FCE0 /* VPNConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57342494277A00D0FCE0 /* VPNConfiguration.swift */; }; + 0EAC573E2494277A00D0FCE0 /* VPNConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57342494277A00D0FCE0 /* VPNConfiguration.swift */; }; + 0EAC573F2494277A00D0FCE0 /* VPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57352494277A00D0FCE0 /* VPNProvider.swift */; }; + 0EAC57402494277A00D0FCE0 /* VPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57352494277A00D0FCE0 /* VPNProvider.swift */; }; + 0EAC57412494277A00D0FCE0 /* VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57362494277A00D0FCE0 /* VPN.swift */; }; + 0EAC57422494277A00D0FCE0 /* VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57362494277A00D0FCE0 /* VPN.swift */; }; 0ECAF84A246697DA00D8266A /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E17D7F91F730D9F009EE129 /* TunnelKit.framework */; }; 0ECAF84B246697DA00D8266A /* TunnelKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0E17D7F91F730D9F009EE129 /* TunnelKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 0ECC60D82254981A0020BEAC /* ConfigurationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D72254981A0020BEAC /* ConfigurationError.swift */; }; @@ -424,6 +436,12 @@ 0E85A25B202CCA3D0059E9F9 /* TunnelKitHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TunnelKitHost.entitlements; sourceTree = ""; }; 0EA82A232190B220007960EB /* TunnelKitTests-macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TunnelKitTests-macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0EA82A272190B220007960EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0EAC57312494277A00D0FCE0 /* StandardVPNProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardVPNProvider.swift; sourceTree = ""; }; + 0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockVPNProvider.swift; sourceTree = ""; }; + 0EAC57332494277A00D0FCE0 /* VPNStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNStatus.swift; sourceTree = ""; }; + 0EAC57342494277A00D0FCE0 /* VPNConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNConfiguration.swift; sourceTree = ""; }; + 0EAC57352494277A00D0FCE0 /* VPNProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNProvider.swift; sourceTree = ""; }; + 0EAC57362494277A00D0FCE0 /* VPN.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPN.swift; sourceTree = ""; }; 0EB03E0E2290CF52006D03A0 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 0ECC60D72254981A0020BEAC /* ConfigurationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationError.swift; sourceTree = ""; }; 0ECEB1132252C8E900E9E551 /* tunnelbear.enc.8.ovpn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.8.ovpn; sourceTree = ""; }; @@ -618,6 +636,7 @@ 0EE2F9DD22918DA100F56F49 /* AppExtension */, 0EFEB4292006D3C800F81029 /* Core */, 0E23B41922982AF800304C30 /* Extra */, + 0EAC57302494277A00D0FCE0 /* Manager */, 0E23B3E022982AF800304C30 /* Protocols */, ); path = Sources; @@ -800,6 +819,19 @@ path = "TunnelKitTests-iOS"; sourceTree = ""; }; + 0EAC57302494277A00D0FCE0 /* Manager */ = { + isa = PBXGroup; + children = ( + 0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */, + 0EAC57312494277A00D0FCE0 /* StandardVPNProvider.swift */, + 0EAC57362494277A00D0FCE0 /* VPN.swift */, + 0EAC57342494277A00D0FCE0 /* VPNConfiguration.swift */, + 0EAC57352494277A00D0FCE0 /* VPNProvider.swift */, + 0EAC57332494277A00D0FCE0 /* VPNStatus.swift */, + ); + path = Manager; + sourceTree = ""; + }; 0EE2F9DD22918DA100F56F49 /* AppExtension */ = { isa = PBXGroup; children = ( @@ -1410,12 +1442,14 @@ files = ( 0EE2F974229163C900F56F49 /* Proxy.swift in Sources */, 0EFEB4732006D3C800F81029 /* LinkInterface.swift in Sources */, + 0EAC573B2494277A00D0FCE0 /* VPNStatus.swift in Sources */, 0E23B48922982AF800304C30 /* PacketStream.m in Sources */, 0E23B3DE229749C600304C30 /* LinkProducer.swift in Sources */, 0E23B42722982AF800304C30 /* CryptoAEAD.m in Sources */, 0EE2F9F222918DA100F56F49 /* NETunnelInterface.swift in Sources */, 0E23B46722982AF800304C30 /* Authenticator.swift in Sources */, 0E23B43922982AF800304C30 /* OpenVPNError.swift in Sources */, + 0EAC573F2494277A00D0FCE0 /* VPNProvider.swift in Sources */, 0E23B44F22982AF800304C30 /* OpenVPNTunnelProvider+Interaction.swift in Sources */, 0E23B46D22982AF800304C30 /* StaticKey.swift in Sources */, 0E23B49F22982AF800304C30 /* minilzo.c in Sources */, @@ -1432,6 +1466,7 @@ 0E12B29E21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift in Sources */, 0EE2F9F822918DA100F56F49 /* NWUDPSessionState+Description.swift in Sources */, 0EE2F9AC2291853D00F56F49 /* Session.swift in Sources */, + 0EAC57412494277A00D0FCE0 /* VPN.swift in Sources */, 0E23B48122982AF800304C30 /* ControlPacket.m in Sources */, 0EFEB4622006D3C800F81029 /* SecureRandom.swift in Sources */, 0E011F7D2196D97200BA59EE /* EndpointProtocol.swift in Sources */, @@ -1479,9 +1514,12 @@ 0E23B45722982AF800304C30 /* NETCPLink.swift in Sources */, 0E23B45322982AF800304C30 /* ConnectionStrategy.swift in Sources */, 0E011F7A2196D93600BA59EE /* SocketType.swift in Sources */, + 0EAC57392494277A00D0FCE0 /* MockVPNProvider.swift in Sources */, 0E23B45922982AF800304C30 /* OpenVPNTunnelProvider.swift in Sources */, 0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */, + 0EAC57372494277A00D0FCE0 /* StandardVPNProvider.swift in Sources */, 0E23B47322982AF800304C30 /* MSS.m in Sources */, + 0EAC573D2494277A00D0FCE0 /* VPNConfiguration.swift in Sources */, 0E23B45D22982AF800304C30 /* ProtocolMacros.swift in Sources */, 0EE2FA0022918DA100F56F49 /* MemoryDestination.swift in Sources */, ); @@ -1493,12 +1531,14 @@ files = ( 0EE2F975229163C900F56F49 /* Proxy.swift in Sources */, 0EFEB4A12006D7F300F81029 /* LinkInterface.swift in Sources */, + 0EAC573C2494277A00D0FCE0 /* VPNStatus.swift in Sources */, 0E23B48A22982AF800304C30 /* PacketStream.m in Sources */, 0E23B3DF229749C600304C30 /* LinkProducer.swift in Sources */, 0E23B42822982AF800304C30 /* CryptoAEAD.m in Sources */, 0EE2F9F322918DA100F56F49 /* NETunnelInterface.swift in Sources */, 0E23B46822982AF800304C30 /* Authenticator.swift in Sources */, 0E23B43A22982AF800304C30 /* OpenVPNError.swift in Sources */, + 0EAC57402494277A00D0FCE0 /* VPNProvider.swift in Sources */, 0E23B45022982AF800304C30 /* OpenVPNTunnelProvider+Interaction.swift in Sources */, 0E23B46E22982AF800304C30 /* StaticKey.swift in Sources */, 0E23B4A022982AF800304C30 /* minilzo.c in Sources */, @@ -1515,6 +1555,7 @@ 0E12B29F21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift in Sources */, 0EE2F9F922918DA100F56F49 /* NWUDPSessionState+Description.swift in Sources */, 0EE2F9AD2291853D00F56F49 /* Session.swift in Sources */, + 0EAC57422494277A00D0FCE0 /* VPN.swift in Sources */, 0E23B48222982AF800304C30 /* ControlPacket.m in Sources */, 0E011F7E2196D97200BA59EE /* EndpointProtocol.swift in Sources */, 0EE2F9F122918DA100F56F49 /* NETCPSocket.swift in Sources */, @@ -1562,9 +1603,12 @@ 0E23B45822982AF800304C30 /* NETCPLink.swift in Sources */, 0E23B45422982AF800304C30 /* ConnectionStrategy.swift in Sources */, 0EFEB49D2006D7F300F81029 /* IOInterface.swift in Sources */, + 0EAC573A2494277A00D0FCE0 /* MockVPNProvider.swift in Sources */, 0E23B45A22982AF800304C30 /* OpenVPNTunnelProvider.swift in Sources */, 0E011F7B2196D93600BA59EE /* SocketType.swift in Sources */, + 0EAC57382494277A00D0FCE0 /* StandardVPNProvider.swift in Sources */, 0E23B47422982AF800304C30 /* MSS.m in Sources */, + 0EAC573E2494277A00D0FCE0 /* VPNConfiguration.swift in Sources */, 0E23B45E22982AF800304C30 /* ProtocolMacros.swift in Sources */, 0EE2FA0122918DA100F56F49 /* MemoryDestination.swift in Sources */, ); diff --git a/TunnelKit/Sources/Manager/MockVPNProvider.swift b/TunnelKit/Sources/Manager/MockVPNProvider.swift new file mode 100644 index 0000000..3e8dd00 --- /dev/null +++ b/TunnelKit/Sources/Manager/MockVPNProvider.swift @@ -0,0 +1,85 @@ +// +// MockVPNProvider.swift +// TunnelKit +// +// Created by Davide De Rosa on 6/15/18. +// Copyright (c) 2020 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +import Foundation + +public class MockVPNProvider: VPNProvider { + public let isPrepared: Bool = true + + public private(set) var isEnabled: Bool = false + + public private(set) var status: VPNStatus = .disconnected + + public func prepare(completionHandler: (() -> Void)?) { + NotificationCenter.default.post(name: .VPNDidPrepare, object: nil) + completionHandler?() + } + + public func install(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) { + isEnabled = true + completionHandler?(nil) + } + + public func connect(completionHandler: ((Error?) -> Void)?) { + isEnabled = true + status = .connected + NotificationCenter.default.post(name: .VPNDidChangeStatus, object: self) + completionHandler?(nil) + } + + public func disconnect(completionHandler: ((Error?) -> Void)?) { + isEnabled = false + status = .disconnected + NotificationCenter.default.post(name: .VPNDidChangeStatus, object: self) + completionHandler?(nil) + } + + public func reconnect(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) { + isEnabled = true + status = .connected + NotificationCenter.default.post(name: .VPNDidChangeStatus, object: self) + completionHandler?(nil) + } + + public func uninstall(completionHandler: (() -> Void)?) { + isEnabled = false + status = .disconnected + NotificationCenter.default.post(name: .VPNDidChangeStatus, object: self) + completionHandler?() + } + + public func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void) { + let log = [String](repeating: "lorem ipsum", count: 1000).joined(separator: " ") + completionHandler(log) + } + + public func requestBytesCount(completionHandler: @escaping ((UInt, UInt)?) -> Void) { + completionHandler((0, 0)) + } + + public func requestServerConfiguration(completionHandler: @escaping (Any?) -> Void) { + completionHandler(nil) + } +} diff --git a/TunnelKit/Sources/Manager/StandardVPNProvider.swift b/TunnelKit/Sources/Manager/StandardVPNProvider.swift new file mode 100644 index 0000000..518f015 --- /dev/null +++ b/TunnelKit/Sources/Manager/StandardVPNProvider.swift @@ -0,0 +1,316 @@ +// +// StandardVPNProvider.swift +// TunnelKit +// +// Created by Davide De Rosa on 6/15/18. +// Copyright (c) 2020 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +import Foundation +import NetworkExtension + +public class StandardVPNProvider: VPNProvider { + private let bundleIdentifier: String + + private var manager: NETunnelProviderManager? + + private var lastNotifiedStatus: VPNStatus? + + public init(bundleIdentifier: String) { + self.bundleIdentifier = bundleIdentifier + + let nc = NotificationCenter.default + nc.addObserver(self, selector: #selector(vpnDidUpdate(_:)), name: .NEVPNStatusDidChange, object: nil) + nc.addObserver(self, selector: #selector(vpnDidReinstall(_:)), name: .NEVPNConfigurationChange, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: VPNProvider + + public var isPrepared: Bool { + return manager != nil + } + + public var isEnabled: Bool { + guard let manager = manager else { + return false + } + return manager.isEnabled && manager.isOnDemandEnabled + } + + public var status: VPNStatus { + guard let neStatus = manager?.connection.status else { + return .disconnected + } + switch neStatus { + case .connected: + return .connected + + case .connecting, .reasserting: + return .connecting + + case .disconnecting: + return .disconnecting + + case .disconnected, .invalid: + return .disconnected + + @unknown default: + return .disconnected + } + } + + public func prepare(completionHandler: (() -> Void)?) { + find(with: bundleIdentifier) { + self.manager = $0 + NotificationCenter.default.post(name: .VPNDidPrepare, object: nil) + completionHandler?() + } + } + + public func install(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) { + guard let configuration = configuration as? NetworkExtensionVPNConfiguration else { + fatalError("Not a NetworkExtensionVPNConfiguration") + } + find(with: bundleIdentifier) { + guard let manager = $0 else { + completionHandler?(nil) + return + } + self.manager = manager + manager.protocolConfiguration = configuration.protocolConfiguration + manager.onDemandRules = configuration.onDemandRules + manager.isOnDemandEnabled = true + manager.isEnabled = true + manager.saveToPreferences { (error) in + guard error == nil else { + manager.isOnDemandEnabled = false + manager.isEnabled = false + completionHandler?(error) + return + } + manager.loadFromPreferences { (error) in + completionHandler?(error) + } + } + } + } + + public func connect(completionHandler: ((Error?) -> Void)?) { + do { + try manager?.connection.startVPNTunnel() + completionHandler?(nil) + } catch let e { + completionHandler?(e) + } + } + + public func disconnect(completionHandler: ((Error?) -> Void)?) { + guard let manager = manager else { + completionHandler?(nil) + return + } + manager.connection.stopVPNTunnel() + manager.isOnDemandEnabled = false + manager.isEnabled = false + manager.saveToPreferences(completionHandler: completionHandler) + } + + public func reconnect(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) { + guard let configuration = configuration as? NetworkExtensionVPNConfiguration else { + fatalError("Not a NetworkExtensionVPNConfiguration") + } + install(configuration: configuration) { (error) in + guard error == nil else { + completionHandler?(nil) + return + } + let connectBlock = { + self.connect(completionHandler: completionHandler) + } + if self.status != .disconnected { + self.manager?.connection.stopVPNTunnel() + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0, execute: connectBlock) + } else { + connectBlock() + } + } + } + + public func uninstall(completionHandler: (() -> Void)?) { + find(with: bundleIdentifier) { (manager) in + guard let manager = manager else { + completionHandler?() + return + } + manager.connection.stopVPNTunnel() + manager.removeFromPreferences { (error) in + self.manager = nil + completionHandler?() + } + } + } + + public func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void) { + guard status != .disconnected else { + completionHandler(fallback?() ?? "") + return + } + findAndRequestDebugLog { (recent) in + DispatchQueue.main.async { + guard let recent = recent else { + completionHandler(fallback?() ?? "") + return + } + completionHandler(recent) + } + } + } + + public func requestBytesCount(completionHandler: @escaping ((UInt, UInt)?) -> Void) { + find(with: bundleIdentifier) { + self.manager = $0 + guard let session = self.manager?.connection as? NETunnelProviderSession else { + DispatchQueue.main.async { + completionHandler(nil) + } + return + } + do { + try session.sendProviderMessage(OpenVPNTunnelProvider.Message.dataCount.data) { (data) in + guard let data = data, data.count == 16 else { + DispatchQueue.main.async { + completionHandler(nil) + } + return + } + let bytesIn: UInt = data.subdata(in: 0..<8).withUnsafeBytes { $0.load(as: UInt.self) } + let bytesOut: UInt = data.subdata(in: 8..<16).withUnsafeBytes { $0.load(as: UInt.self) } + DispatchQueue.main.async { + completionHandler((bytesIn, bytesOut)) + } + } + } catch { + DispatchQueue.main.async { + completionHandler(nil) + } + } + } + } + + public func requestServerConfiguration(completionHandler: @escaping (Any?) -> Void) { + find(with: bundleIdentifier) { + self.manager = $0 + guard let session = self.manager?.connection as? NETunnelProviderSession else { + DispatchQueue.main.async { + completionHandler(nil) + } + return + } + do { + try session.sendProviderMessage(OpenVPNTunnelProvider.Message.serverConfiguration.data) { (data) in + guard let data = data, let cfg = try? JSONDecoder().decode(OpenVPN.Configuration.self, from: data) else { + DispatchQueue.main.async { + completionHandler(nil) + } + return + } + DispatchQueue.main.async { + completionHandler(cfg) + } + } + } catch { + DispatchQueue.main.async { + completionHandler(nil) + } + } + } + } + + // MARK: Helpers + + private func find(with bundleIdentifier: String, completionHandler: @escaping (NETunnelProviderManager?) -> Void) { + NETunnelProviderManager.loadAllFromPreferences { (managers, error) in + guard error == nil else { + completionHandler(nil) + return + } + let manager = managers?.first { + guard let ptm = $0.protocolConfiguration as? NETunnelProviderProtocol else { + return false + } + return (ptm.providerBundleIdentifier == bundleIdentifier) + } + completionHandler(manager ?? NETunnelProviderManager()) + } + } + + private func findAndRequestDebugLog(completionHandler: @escaping (String?) -> Void) { + find(with: bundleIdentifier) { + self.manager = $0 + guard let session = self.manager?.connection as? NETunnelProviderSession else { + completionHandler(nil) + return + } + StandardVPNProvider.requestDebugLog(session: session, completionHandler: completionHandler) + } + } + + private static func requestDebugLog(session: NETunnelProviderSession, completionHandler: @escaping (String?) -> Void) { + do { + try session.sendProviderMessage(OpenVPNTunnelProvider.Message.requestLog.data) { (data) in + guard let data = data, !data.isEmpty else { + completionHandler(nil) + return + } + let newestLog = String(data: data, encoding: .utf8) + completionHandler(newestLog) + } + } catch { + completionHandler(nil) + } + } + + // MARK: Notifications + + @objc private func vpnDidUpdate(_ notification: Notification) { +// guard let connection = notification.object as? NETunnelProviderSession else { +// return +// } +// log.debug("VPN status did change: \(connection.status.rawValue)") + + let status = self.status + if let last = lastNotifiedStatus { + guard status != last else { + return + } + } + lastNotifiedStatus = status + + NotificationCenter.default.post(name: .VPNDidChangeStatus, object: self) + } + + @objc private func vpnDidReinstall(_ notification: Notification) { + NotificationCenter.default.post(name: .VPNDidReinstall, object: self) + } +} diff --git a/TunnelKit/Sources/Manager/VPN.swift b/TunnelKit/Sources/Manager/VPN.swift new file mode 100644 index 0000000..f336318 --- /dev/null +++ b/TunnelKit/Sources/Manager/VPN.swift @@ -0,0 +1,30 @@ +// +// VPN.swift +// TunnelKit +// +// Created by Davide De Rosa on 6/12/18. +// Copyright (c) 2020 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +import Foundation + +public class VPN { + public static var shared: VPNProvider = MockVPNProvider() +} diff --git a/TunnelKit/Sources/Manager/VPNConfiguration.swift b/TunnelKit/Sources/Manager/VPNConfiguration.swift new file mode 100644 index 0000000..58412f3 --- /dev/null +++ b/TunnelKit/Sources/Manager/VPNConfiguration.swift @@ -0,0 +1,41 @@ +// +// VPNConfiguration.swift +// TunnelKit +// +// Created by Davide De Rosa on 9/18/18. +// Copyright (c) 2020 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +import Foundation +import NetworkExtension + +public protocol VPNConfiguration { +} + +public struct NetworkExtensionVPNConfiguration: VPNConfiguration { + public let protocolConfiguration: NETunnelProviderProtocol + + public let onDemandRules: [NEOnDemandRule] + + public init(protocolConfiguration: NETunnelProviderProtocol, onDemandRules: [NEOnDemandRule]) { + self.protocolConfiguration = protocolConfiguration + self.onDemandRules = onDemandRules + } +} diff --git a/TunnelKit/Sources/Manager/VPNProvider.swift b/TunnelKit/Sources/Manager/VPNProvider.swift new file mode 100644 index 0000000..c0f4167 --- /dev/null +++ b/TunnelKit/Sources/Manager/VPNProvider.swift @@ -0,0 +1,60 @@ +// +// VPNProvider.swift +// TunnelKit +// +// Created by Davide De Rosa on 9/6/18. +// Copyright (c) 2020 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +import Foundation + +public protocol VPNProvider: class { + var isPrepared: Bool { get } + + var isEnabled: Bool { get } + + var status: VPNStatus { get } + + func prepare(completionHandler: (() -> Void)?) + + func install(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) + + func connect(completionHandler: ((Error?) -> Void)?) + + func disconnect(completionHandler: ((Error?) -> Void)?) + + func reconnect(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) + + func uninstall(completionHandler: (() -> Void)?) + + func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void) + + func requestBytesCount(completionHandler: @escaping ((UInt, UInt)?) -> Void) + + func requestServerConfiguration(completionHandler: @escaping (Any?) -> Void) +} + +public extension Notification.Name { + static let VPNDidPrepare = Notification.Name("VPNDidPrepare") + + static let VPNDidChangeStatus = Notification.Name("VPNDidChangeStatus") + + static let VPNDidReinstall = Notification.Name("VPNDidReinstall") +} diff --git a/TunnelKit/Sources/Manager/VPNStatus.swift b/TunnelKit/Sources/Manager/VPNStatus.swift new file mode 100644 index 0000000..7f46a74 --- /dev/null +++ b/TunnelKit/Sources/Manager/VPNStatus.swift @@ -0,0 +1,36 @@ +// +// VPNStatus.swift +// TunnelKit +// +// Created by Davide De Rosa on 9/18/18. +// Copyright (c) 2020 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +import Foundation + +public enum VPNStatus { + case connected + + case connecting + + case disconnected + + case disconnecting +}