Merge branch 'refactor-multiple-protocols'

This commit is contained in:
Davide De Rosa 2021-09-20 20:21:28 +02:00
commit d107e485d1
14 changed files with 553 additions and 225 deletions

View File

@ -43,16 +43,19 @@ custom_categories:
- NETCPSocket - NETCPSocket
- NETunnelInterface - NETunnelInterface
- NEUDPSocket - NEUDPSocket
- NSNotification
- name: Manager - name: Manager
children: children:
- VPN - VPN
- VPNProvider
- MockVPNProvider
- VPNConfiguration - VPNConfiguration
- NetworkExtensionVPNConfiguration - VPNProvider
- VPNProviderIPC
- VPNStatus - VPNStatus
- NSNotification - NetworkExtensionLocator
- NetworkExtensionNativeLocator
- NetworkExtensionTunnelLocator
- NetworkExtensionVPNConfiguration
- NetworkExtensionVPNProvider
- MockVPNProvider
- name: Protocols/OpenVPN - name: Protocols/OpenVPN
children: children:
- OpenVPN - OpenVPN

View File

@ -164,8 +164,6 @@ This subspec includes convenient classes to control the VPN tunnel from your app
- `MockVPNProvider` (default, useful to test on simulator) - `MockVPNProvider` (default, useful to test on simulator)
- `OpenVPNProvider` - `OpenVPNProvider`
Set `VPN.shared` to either of them at app launch time.
### Protocols/OpenVPN ### Protocols/OpenVPN
Here you will find the low-level entities on top of which an OpenVPN connection is established. Code is mixed Swift and Obj-C, most of it is not exposed to consumers. The module depends on OpenSSL. Here you will find the low-level entities on top of which an OpenVPN connection is established. Code is mixed Swift and Obj-C, most of it is not exposed to consumers. The module depends on OpenSSL.

View File

@ -1,7 +1,7 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = "TunnelKit" s.name = "TunnelKit"
s.version = "3.4.0" s.version = "3.5.0"
s.summary = "Non-official OpenVPN client for Apple platforms." s.summary = "VPN client library for Apple platforms."
s.homepage = "https://github.com/passepartoutvpn/tunnelkit" s.homepage = "https://github.com/passepartoutvpn/tunnelkit"
s.license = { :type => "GPLv3", :file => "LICENSE" } s.license = { :type => "GPLv3", :file => "LICENSE" }

View File

@ -190,6 +190,14 @@
0E94E8EB25BACEBD0040BC30 /* DNSProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E94E8E925BACEBD0040BC30 /* DNSProtocol.swift */; }; 0E94E8EB25BACEBD0040BC30 /* DNSProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E94E8E925BACEBD0040BC30 /* DNSProtocol.swift */; };
0EA82A282190B220007960EB /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E3251C51F95770D00C108D9 /* TunnelKit.framework */; }; 0EA82A282190B220007960EB /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E3251C51F95770D00C108D9 /* TunnelKit.framework */; };
0EA82A3E2190B2BC007960EB /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; }; 0EA82A3E2190B2BC007960EB /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; };
0EA9F28926D69FAE00E806E1 /* NetworkExtensionVPNConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9F28826D69FAE00E806E1 /* NetworkExtensionVPNConfiguration.swift */; };
0EA9F28A26D69FAE00E806E1 /* NetworkExtensionVPNConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9F28826D69FAE00E806E1 /* NetworkExtensionVPNConfiguration.swift */; };
0EA9F28C26D6A1EE00E806E1 /* NetworkExtensionVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9F28B26D6A1EE00E806E1 /* NetworkExtensionVPNProvider.swift */; };
0EA9F28D26D6A1EE00E806E1 /* NetworkExtensionVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9F28B26D6A1EE00E806E1 /* NetworkExtensionVPNProvider.swift */; };
0EA9F28F26D6AC6D00E806E1 /* VPNProviderIPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9F28E26D6AC6D00E806E1 /* VPNProviderIPC.swift */; };
0EA9F29026D6AC6D00E806E1 /* VPNProviderIPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9F28E26D6AC6D00E806E1 /* VPNProviderIPC.swift */; };
0EA9F29526D6CA7E00E806E1 /* NetworkExtensionLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9F29426D6CA7E00E806E1 /* NetworkExtensionLocator.swift */; };
0EA9F29626D6CA7E00E806E1 /* NetworkExtensionLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9F29426D6CA7E00E806E1 /* NetworkExtensionLocator.swift */; };
0EAC57372494277A00D0FCE0 /* OpenVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57312494277A00D0FCE0 /* OpenVPNProvider.swift */; }; 0EAC57372494277A00D0FCE0 /* OpenVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57312494277A00D0FCE0 /* OpenVPNProvider.swift */; };
0EAC57382494277A00D0FCE0 /* OpenVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57312494277A00D0FCE0 /* OpenVPNProvider.swift */; }; 0EAC57382494277A00D0FCE0 /* OpenVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57312494277A00D0FCE0 /* OpenVPNProvider.swift */; };
0EAC57392494277A00D0FCE0 /* MockVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */; }; 0EAC57392494277A00D0FCE0 /* MockVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */; };
@ -571,6 +579,10 @@
0E85A25B202CCA3D0059E9F9 /* Host.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Host.entitlements; sourceTree = "<group>"; }; 0E85A25B202CCA3D0059E9F9 /* Host.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Host.entitlements; sourceTree = "<group>"; };
0E94E8E925BACEBD0040BC30 /* DNSProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSProtocol.swift; sourceTree = "<group>"; }; 0E94E8E925BACEBD0040BC30 /* DNSProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSProtocol.swift; sourceTree = "<group>"; };
0EA82A232190B220007960EB /* TunnelKitTests-macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TunnelKitTests-macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0EA82A232190B220007960EB /* TunnelKitTests-macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TunnelKitTests-macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
0EA9F28826D69FAE00E806E1 /* NetworkExtensionVPNConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkExtensionVPNConfiguration.swift; sourceTree = "<group>"; };
0EA9F28B26D6A1EE00E806E1 /* NetworkExtensionVPNProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkExtensionVPNProvider.swift; sourceTree = "<group>"; };
0EA9F28E26D6AC6D00E806E1 /* VPNProviderIPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNProviderIPC.swift; sourceTree = "<group>"; };
0EA9F29426D6CA7E00E806E1 /* NetworkExtensionLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkExtensionLocator.swift; sourceTree = "<group>"; };
0EAC57312494277A00D0FCE0 /* OpenVPNProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenVPNProvider.swift; sourceTree = "<group>"; }; 0EAC57312494277A00D0FCE0 /* OpenVPNProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenVPNProvider.swift; sourceTree = "<group>"; };
0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockVPNProvider.swift; sourceTree = "<group>"; }; 0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockVPNProvider.swift; sourceTree = "<group>"; };
0EAC57332494277A00D0FCE0 /* VPNStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNStatus.swift; sourceTree = "<group>"; }; 0EAC57332494277A00D0FCE0 /* VPNStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNStatus.swift; sourceTree = "<group>"; };
@ -1033,9 +1045,13 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */, 0EAC57322494277A00D0FCE0 /* MockVPNProvider.swift */,
0EA9F29426D6CA7E00E806E1 /* NetworkExtensionLocator.swift */,
0EA9F28826D69FAE00E806E1 /* NetworkExtensionVPNConfiguration.swift */,
0EA9F28B26D6A1EE00E806E1 /* NetworkExtensionVPNProvider.swift */,
0EAC57362494277A00D0FCE0 /* VPN.swift */, 0EAC57362494277A00D0FCE0 /* VPN.swift */,
0EAC57342494277A00D0FCE0 /* VPNConfiguration.swift */, 0EAC57342494277A00D0FCE0 /* VPNConfiguration.swift */,
0EAC57352494277A00D0FCE0 /* VPNProvider.swift */, 0EAC57352494277A00D0FCE0 /* VPNProvider.swift */,
0EA9F28E26D6AC6D00E806E1 /* VPNProviderIPC.swift */,
0EAC57332494277A00D0FCE0 /* VPNStatus.swift */, 0EAC57332494277A00D0FCE0 /* VPNStatus.swift */,
); );
path = Manager; path = Manager;
@ -1989,6 +2005,7 @@
0E23B3DE229749C600304C30 /* LinkProducer.swift in Sources */, 0E23B3DE229749C600304C30 /* LinkProducer.swift in Sources */,
0E23B42722982AF800304C30 /* CryptoAEAD.m in Sources */, 0E23B42722982AF800304C30 /* CryptoAEAD.m in Sources */,
0EE2F9F222918DA100F56F49 /* NETunnelInterface.swift in Sources */, 0EE2F9F222918DA100F56F49 /* NETunnelInterface.swift in Sources */,
0EA9F28C26D6A1EE00E806E1 /* NetworkExtensionVPNProvider.swift in Sources */,
0E23B46722982AF800304C30 /* Authenticator.swift in Sources */, 0E23B46722982AF800304C30 /* Authenticator.swift in Sources */,
0E23B43922982AF800304C30 /* OpenVPNError.swift in Sources */, 0E23B43922982AF800304C30 /* OpenVPNError.swift in Sources */,
0EAC573F2494277A00D0FCE0 /* VPNProvider.swift in Sources */, 0EAC573F2494277A00D0FCE0 /* VPNProvider.swift in Sources */,
@ -2027,6 +2044,7 @@
0E23B43522982AF800304C30 /* CoreConfiguration+OpenVPN.swift in Sources */, 0E23B43522982AF800304C30 /* CoreConfiguration+OpenVPN.swift in Sources */,
0EFEB46D2006D3C800F81029 /* Data+Manipulation.swift in Sources */, 0EFEB46D2006D3C800F81029 /* Data+Manipulation.swift in Sources */,
0E23B46B22982AF800304C30 /* CryptoCBC.m in Sources */, 0E23B46B22982AF800304C30 /* CryptoCBC.m in Sources */,
0EA9F28926D69FAE00E806E1 /* NetworkExtensionVPNConfiguration.swift in Sources */,
0E23B46522982AF800304C30 /* EncryptionBridge.swift in Sources */, 0E23B46522982AF800304C30 /* EncryptionBridge.swift in Sources */,
0EE2F9F422918DA100F56F49 /* NWTCPConnectionState+Description.swift in Sources */, 0EE2F9F422918DA100F56F49 /* NWTCPConnectionState+Description.swift in Sources */,
0EFEB4742006D3C800F81029 /* CoreConfiguration.swift in Sources */, 0EFEB4742006D3C800F81029 /* CoreConfiguration.swift in Sources */,
@ -2049,6 +2067,7 @@
0E23B43722982AF800304C30 /* TLSBox.m in Sources */, 0E23B43722982AF800304C30 /* TLSBox.m in Sources */,
0EFEB4702006D3C800F81029 /* Allocation.m in Sources */, 0EFEB4702006D3C800F81029 /* Allocation.m in Sources */,
0EE2FA0A22918DA100F56F49 /* InterfaceObserver.swift in Sources */, 0EE2FA0A22918DA100F56F49 /* InterfaceObserver.swift in Sources */,
0EA9F28F26D6AC6D00E806E1 /* VPNProviderIPC.swift in Sources */,
0E23B43F22982AF800304C30 /* OpenVPNSession.swift in Sources */, 0E23B43F22982AF800304C30 /* OpenVPNSession.swift in Sources */,
0E23B44722982AF800304C30 /* OpenVPN.swift in Sources */, 0E23B44722982AF800304C30 /* OpenVPN.swift in Sources */,
0ECC60D82254981A0020BEAC /* ConfigurationError.swift in Sources */, 0ECC60D82254981A0020BEAC /* ConfigurationError.swift in Sources */,
@ -2061,6 +2080,7 @@
0E23B45922982AF800304C30 /* OpenVPNTunnelProvider.swift in Sources */, 0E23B45922982AF800304C30 /* OpenVPNTunnelProvider.swift in Sources */,
0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */, 0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */,
0EAC57372494277A00D0FCE0 /* OpenVPNProvider.swift in Sources */, 0EAC57372494277A00D0FCE0 /* OpenVPNProvider.swift in Sources */,
0EA9F29526D6CA7E00E806E1 /* NetworkExtensionLocator.swift in Sources */,
0E23B47322982AF800304C30 /* MSS.m in Sources */, 0E23B47322982AF800304C30 /* MSS.m in Sources */,
0EAC573D2494277A00D0FCE0 /* VPNConfiguration.swift in Sources */, 0EAC573D2494277A00D0FCE0 /* VPNConfiguration.swift in Sources */,
0E23B45D22982AF800304C30 /* ProtocolMacros.swift in Sources */, 0E23B45D22982AF800304C30 /* ProtocolMacros.swift in Sources */,
@ -2079,6 +2099,7 @@
0E23B3DF229749C600304C30 /* LinkProducer.swift in Sources */, 0E23B3DF229749C600304C30 /* LinkProducer.swift in Sources */,
0E23B42822982AF800304C30 /* CryptoAEAD.m in Sources */, 0E23B42822982AF800304C30 /* CryptoAEAD.m in Sources */,
0EE2F9F322918DA100F56F49 /* NETunnelInterface.swift in Sources */, 0EE2F9F322918DA100F56F49 /* NETunnelInterface.swift in Sources */,
0EA9F28D26D6A1EE00E806E1 /* NetworkExtensionVPNProvider.swift in Sources */,
0E23B46822982AF800304C30 /* Authenticator.swift in Sources */, 0E23B46822982AF800304C30 /* Authenticator.swift in Sources */,
0E23B43A22982AF800304C30 /* OpenVPNError.swift in Sources */, 0E23B43A22982AF800304C30 /* OpenVPNError.swift in Sources */,
0EAC57402494277A00D0FCE0 /* VPNProvider.swift in Sources */, 0EAC57402494277A00D0FCE0 /* VPNProvider.swift in Sources */,
@ -2117,6 +2138,7 @@
0E23B43622982AF800304C30 /* CoreConfiguration+OpenVPN.swift in Sources */, 0E23B43622982AF800304C30 /* CoreConfiguration+OpenVPN.swift in Sources */,
0EFB902C22788512006405E4 /* RoutingTable.m in Sources */, 0EFB902C22788512006405E4 /* RoutingTable.m in Sources */,
0E23B46C22982AF800304C30 /* CryptoCBC.m in Sources */, 0E23B46C22982AF800304C30 /* CryptoCBC.m in Sources */,
0EA9F28A26D69FAE00E806E1 /* NetworkExtensionVPNConfiguration.swift in Sources */,
0E23B46622982AF800304C30 /* EncryptionBridge.swift in Sources */, 0E23B46622982AF800304C30 /* EncryptionBridge.swift in Sources */,
0EFEB4952006D7F300F81029 /* SecureRandom.swift in Sources */, 0EFEB4952006D7F300F81029 /* SecureRandom.swift in Sources */,
0EE2F9F522918DA100F56F49 /* NWTCPConnectionState+Description.swift in Sources */, 0EE2F9F522918DA100F56F49 /* NWTCPConnectionState+Description.swift in Sources */,
@ -2139,6 +2161,7 @@
0E23B43822982AF800304C30 /* TLSBox.m in Sources */, 0E23B43822982AF800304C30 /* TLSBox.m in Sources */,
0EFEB4902006D7F300F81029 /* TunnelInterface.swift in Sources */, 0EFEB4902006D7F300F81029 /* TunnelInterface.swift in Sources */,
0EFEB49E2006D7F300F81029 /* Allocation.m in Sources */, 0EFEB49E2006D7F300F81029 /* Allocation.m in Sources */,
0EA9F29026D6AC6D00E806E1 /* VPNProviderIPC.swift in Sources */,
0E23B44022982AF800304C30 /* OpenVPNSession.swift in Sources */, 0E23B44022982AF800304C30 /* OpenVPNSession.swift in Sources */,
0E23B44822982AF800304C30 /* OpenVPN.swift in Sources */, 0E23B44822982AF800304C30 /* OpenVPN.swift in Sources */,
0ECC60D92254981A0020BEAC /* ConfigurationError.swift in Sources */, 0ECC60D92254981A0020BEAC /* ConfigurationError.swift in Sources */,
@ -2151,6 +2174,7 @@
0E23B45A22982AF800304C30 /* OpenVPNTunnelProvider.swift in Sources */, 0E23B45A22982AF800304C30 /* OpenVPNTunnelProvider.swift in Sources */,
0E011F7B2196D93600BA59EE /* SocketType.swift in Sources */, 0E011F7B2196D93600BA59EE /* SocketType.swift in Sources */,
0EAC57382494277A00D0FCE0 /* OpenVPNProvider.swift in Sources */, 0EAC57382494277A00D0FCE0 /* OpenVPNProvider.swift in Sources */,
0EA9F29626D6CA7E00E806E1 /* NetworkExtensionLocator.swift in Sources */,
0E23B47422982AF800304C30 /* MSS.m in Sources */, 0E23B47422982AF800304C30 /* MSS.m in Sources */,
0EAC573E2494277A00D0FCE0 /* VPNConfiguration.swift in Sources */, 0EAC573E2494277A00D0FCE0 /* VPNConfiguration.swift in Sources */,
0E23B45E22982AF800304C30 /* ProtocolMacros.swift in Sources */, 0E23B45E22982AF800304C30 /* ProtocolMacros.swift in Sources */,

View File

@ -25,8 +25,15 @@
import Foundation import Foundation
/// :nodoc: /// Simulates a VPN provider.
public class MockVPNProvider: VPNProvider { public class MockVPNProvider: VPNProvider, VPNProviderIPC {
/// :nodoc:
public init() {
}
// MARK: VPNProvider
public let isPrepared: Bool = true public let isPrepared: Bool = true
public private(set) var isEnabled: Bool = false public private(set) var isEnabled: Bool = false
@ -71,6 +78,8 @@ public class MockVPNProvider: VPNProvider {
completionHandler?() completionHandler?()
} }
// MARK: VPNProviderIPC
public func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void) { public func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void) {
let log = [String](repeating: "lorem ipsum", count: 1000).joined(separator: " ") let log = [String](repeating: "lorem ipsum", count: 1000).joined(separator: " ")
completionHandler(log) completionHandler(log)

View File

@ -0,0 +1,91 @@
//
// NetworkExtensionLocator.swift
// TunnelKit
//
// Created by Davide De Rosa on 8/25/21.
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
//
import Foundation
import NetworkExtension
/// Entity able to look up a `NEVPNManager`.
public protocol NetworkExtensionLocator {
/**
Looks up the VPN manager.
- Parameter completionHandler: The completion handler with a `NEVPNManager` or an error (if not found).
*/
func lookup(completionHandler: @escaping (NEVPNManager?, Error?) -> Void)
}
/// Locator for native VPN protocols.
public class NetworkExtensionNativeLocator: NetworkExtensionLocator {
/// :nodoc:
public init() {
}
// MARK: NetworkExtensionLocator
public func lookup(completionHandler: @escaping (NEVPNManager?, Error?) -> Void) {
let manager = NEVPNManager.shared()
manager.loadFromPreferences { (error) in
guard error == nil else {
completionHandler(nil, error)
return
}
completionHandler(manager, nil)
}
}
}
/// Locator for tunnel VPN protocols.
public class NetworkExtensionTunnelLocator: NetworkExtensionLocator {
private let bundleIdentifier: String
/**
Initializes the locator with the bundle identifier of the tunnel provider.
- Parameter bundleIdentifier: The bundle identifier of the tunnel provider.
*/
public init(bundleIdentifier: String) {
self.bundleIdentifier = bundleIdentifier
}
// MARK: NetworkExtensionLocator
public func lookup(completionHandler: @escaping (NEVPNManager?, Error?) -> Void) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
guard error == nil else {
completionHandler(nil, error)
return
}
let manager = managers?.first {
guard let ptm = $0.protocolConfiguration as? NETunnelProviderProtocol else {
return false
}
return (ptm.providerBundleIdentifier == self.bundleIdentifier)
}
completionHandler(manager ?? NETunnelProviderManager(), nil)
}
}
}

View File

@ -0,0 +1,47 @@
//
// NetworkExtensionVPNConfiguration.swift
// TunnelKit
//
// Created by Davide De Rosa on 8/25/21.
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
//
import Foundation
import NetworkExtension
/// A `VPNConfiguration` built on top of NetworkExtension entities.
public struct NetworkExtensionVPNConfiguration: VPNConfiguration {
/// :nodoc:
public var title: String
/// The `NEVPNProtocol` object embedding tunnel configuration.
public let protocolConfiguration: NEVPNProtocol
/// The on-demand rules to establish.
public let onDemandRules: [NEOnDemandRule]
/// :nodoc:
public init(title: String, protocolConfiguration: NEVPNProtocol, onDemandRules: [NEOnDemandRule]) {
self.title = title
self.protocolConfiguration = protocolConfiguration
self.onDemandRules = onDemandRules
}
}

View File

@ -0,0 +1,212 @@
//
// NetworkExtensionVPNProvider.swift
// TunnelKit
//
// Created by Davide De Rosa on 6/15/18.
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
//
import Foundation
import NetworkExtension
import SwiftyBeaver
private let log = SwiftyBeaver.self
/// `VPNProvider` based on the NetworkExtension framework.
public class NetworkExtensionVPNProvider: VPNProvider {
private var manager: NEVPNManager?
private let locator: NetworkExtensionLocator
private var lastNotifiedStatus: VPNStatus?
/**
Initializes a provider with a `NetworkExtensionLocator`.
- Parameter locator: A `NetworkExtensionLocator` able to locate a `NEVPNManager`.
*/
public init(locator: NetworkExtensionLocator) {
self.locator = locator
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)?) {
locator.lookup { manager, error in
self.manager = manager
NotificationCenter.default.post(name: VPN.didPrepare, object: nil)
completionHandler?()
}
}
public func install(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) {
guard let configuration = configuration as? NetworkExtensionVPNConfiguration else {
fatalError("Not a NetworkExtensionVPNConfiguration")
}
locator.lookup { manager, error in
guard let manager = manager else {
completionHandler?(error)
return
}
self.manager = manager
manager.localizedDescription = configuration.title
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?(error)
return
}
let connectBlock = {
self.connect(completionHandler: completionHandler)
}
if self.status != .disconnected {
self.manager?.connection.stopVPNTunnel()
DispatchQueue.main.asyncAfter(deadline: .now() + CoreConfiguration.reconnectionDelay, execute: connectBlock)
} else {
connectBlock()
}
}
}
public func uninstall(completionHandler: (() -> Void)?) {
locator.lookup { manager, error in
guard let manager = manager else {
completionHandler?()
return
}
manager.connection.stopVPNTunnel()
manager.removeFromPreferences { error in
self.manager = nil
completionHandler?()
}
}
}
// MARK: Helpers
func lookup(completionHandler: @escaping (NEVPNManager?, Error?) -> Void) {
locator.lookup(completionHandler: completionHandler)
}
// 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: VPN.didChangeStatus, object: self)
}
@objc private func vpnDidReinstall(_ notification: Notification) {
NotificationCenter.default.post(name: VPN.didReinstall, object: self)
}
}

View File

@ -36,7 +36,4 @@ public class VPN {
/// The VPN profile did (re)install. /// The VPN profile did (re)install.
public static let didReinstall = Notification.Name("VPNDidReinstall") public static let didReinstall = Notification.Name("VPNDidReinstall")
/// A singleton `VPNProvider` instance (default is a `MockVPNProvider`). Make sure to set this on app launch.
public static var shared: VPNProvider = MockVPNProvider()
} }

View File

@ -24,7 +24,6 @@
// //
import Foundation import Foundation
import NetworkExtension
/// Generic marker for objects able to configure a `VPNProvider`. /// Generic marker for objects able to configure a `VPNProvider`.
public protocol VPNConfiguration { public protocol VPNConfiguration {
@ -32,23 +31,3 @@ public protocol VPNConfiguration {
/// The profile title in device settings. /// The profile title in device settings.
var title: String { get } var title: String { get }
} }
/// A `VPNConfiguration` built on top of NetworkExtension entities.
public struct NetworkExtensionVPNConfiguration: VPNConfiguration {
/// :nodoc:
public var title: String
/// The `NETunnelProviderProtocol` object embedding tunnel configuration.
public let protocolConfiguration: NETunnelProviderProtocol
/// The on-demand rules to establish.
public let onDemandRules: [NEOnDemandRule]
/// :nodoc:
public init(title: String, protocolConfiguration: NETunnelProviderProtocol, onDemandRules: [NEOnDemandRule]) {
self.title = title
self.protocolConfiguration = protocolConfiguration
self.onDemandRules = onDemandRules
}
}

View File

@ -82,26 +82,4 @@ public protocol VPNProvider: AnyObject {
- Parameter completionHandler: The completion handler. - Parameter completionHandler: The completion handler.
*/ */
func uninstall(completionHandler: (() -> Void)?) func uninstall(completionHandler: (() -> Void)?)
/**
Request a debug log from the VPN.
- Parameter fallback: The block resolving to a fallback `String` if no debug log is available.
- Parameter completionHandler: The completion handler with the debug log.
*/
func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void)
/**
Requests the current received/sent bytes count from the VPN.
- Parameter completionHandler: The completion handler with an optional received/sent bytes count.
*/
func requestBytesCount(completionHandler: @escaping ((UInt, UInt)?) -> Void)
/**
Requests the server configuration from the VPN.
- Parameter completionHandler: The completion handler with an optional configuration object.
*/
func requestServerConfiguration(completionHandler: @escaping (Any?) -> Void)
} }

View File

@ -0,0 +1,52 @@
//
// VPNProviderIPC.swift
// TunnelKit
//
// Created by Davide De Rosa on 8/25/21.
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
//
import Foundation
/// Common IPC functions supported by interactive VPN providers.
public protocol VPNProviderIPC {
/**
Request a debug log from the VPN.
- Parameter fallback: The block resolving to a fallback `String` if no debug log is available.
- Parameter completionHandler: The completion handler with the debug log.
*/
func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void)
/**
Requests the current received/sent bytes count from the VPN.
- Parameter completionHandler: The completion handler with an optional received/sent bytes count.
*/
func requestBytesCount(completionHandler: @escaping ((UInt, UInt)?) -> Void)
/**
Requests the server configuration from the VPN.
- Parameter completionHandler: The completion handler with an optional configuration object.
*/
func requestServerConfiguration(completionHandler: @escaping (Any?) -> Void)
}

View File

@ -0,0 +1,74 @@
//
// NativeProvider.swift
// TunnelKit
//
// Created by Davide De Rosa on 4/11/21.
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
//
import Foundation
/// `VPNProvider` for native IPSec/IKEv2 configurations.
public class NativeProvider: VPNProvider {
private let provider: NetworkExtensionVPNProvider
/// :nodoc:
public init() {
provider = NetworkExtensionVPNProvider(locator: NetworkExtensionNativeLocator())
}
// MARK: VPNProvider
public var isPrepared: Bool {
return provider.isPrepared
}
public var isEnabled: Bool {
return provider.isEnabled
}
public var status: VPNStatus {
return provider.status
}
public func prepare(completionHandler: (() -> Void)?) {
provider.prepare(completionHandler: completionHandler)
}
public func install(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) {
provider.install(configuration: configuration, completionHandler: completionHandler)
}
public func connect(completionHandler: ((Error?) -> Void)?) {
provider.connect(completionHandler: completionHandler)
}
public func disconnect(completionHandler: ((Error?) -> Void)?) {
provider.disconnect(completionHandler: completionHandler)
}
public func reconnect(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) {
provider.reconnect(configuration: configuration, completionHandler: completionHandler)
}
public func uninstall(completionHandler: (() -> Void)?) {
provider.uninstall(completionHandler: completionHandler)
}
}

View File

@ -26,155 +26,61 @@
import Foundation import Foundation
import NetworkExtension import NetworkExtension
/// :nodoc: /// `VPNProvider` for OpenVPN protocol.
public class OpenVPNProvider: VPNProvider { public class OpenVPNProvider: VPNProvider, VPNProviderIPC {
private let bundleIdentifier: String private let provider: NetworkExtensionVPNProvider
private var manager: NETunnelProviderManager?
private var lastNotifiedStatus: VPNStatus?
/**
Initializes a provider with the bundle identifier of the `OpenVPNTunnelProvider`.
- Parameter bundleIdentifier: The bundle identifier of the `OpenVPNTunnelProvider`.
*/
public init(bundleIdentifier: String) { public init(bundleIdentifier: String) {
self.bundleIdentifier = bundleIdentifier provider = NetworkExtensionVPNProvider(locator: NetworkExtensionTunnelLocator(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 // MARK: VPNProvider
public var isPrepared: Bool { public var isPrepared: Bool {
return manager != nil return provider.isPrepared
} }
public var isEnabled: Bool { public var isEnabled: Bool {
guard let manager = manager else { return provider.isEnabled
return false
}
return manager.isEnabled && manager.isOnDemandEnabled
} }
public var status: VPNStatus { public var status: VPNStatus {
guard let neStatus = manager?.connection.status else { return provider.status
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)?) { public func prepare(completionHandler: (() -> Void)?) {
find(with: bundleIdentifier) { provider.prepare(completionHandler: completionHandler)
self.manager = $0
NotificationCenter.default.post(name: VPN.didPrepare, object: nil)
completionHandler?()
}
} }
public func install(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) { public func install(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) {
guard let configuration = configuration as? NetworkExtensionVPNConfiguration else { provider.install(configuration: configuration, completionHandler: completionHandler)
fatalError("Not a NetworkExtensionVPNConfiguration")
}
find(with: bundleIdentifier) {
guard let manager = $0 else {
completionHandler?(nil)
return
}
self.manager = manager
manager.localizedDescription = configuration.title
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)?) { public func connect(completionHandler: ((Error?) -> Void)?) {
do { provider.connect(completionHandler: completionHandler)
try manager?.connection.startVPNTunnel()
completionHandler?(nil)
} catch let e {
completionHandler?(e)
}
} }
public func disconnect(completionHandler: ((Error?) -> Void)?) { public func disconnect(completionHandler: ((Error?) -> Void)?) {
guard let manager = manager else { provider.disconnect(completionHandler: completionHandler)
completionHandler?(nil)
return
}
manager.connection.stopVPNTunnel()
manager.isOnDemandEnabled = false
manager.isEnabled = false
manager.saveToPreferences(completionHandler: completionHandler)
} }
public func reconnect(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) { public func reconnect(configuration: VPNConfiguration, completionHandler: ((Error?) -> Void)?) {
guard let configuration = configuration as? NetworkExtensionVPNConfiguration else { provider.reconnect(configuration: configuration, completionHandler: completionHandler)
fatalError("Not a NetworkExtensionVPNConfiguration")
}
install(configuration: configuration) { (error) in
guard error == nil else {
completionHandler?(error)
return
}
let connectBlock = {
self.connect(completionHandler: completionHandler)
}
if self.status != .disconnected {
self.manager?.connection.stopVPNTunnel()
DispatchQueue.main.asyncAfter(deadline: .now() + CoreConfiguration.reconnectionDelay, execute: connectBlock)
} else {
connectBlock()
}
}
} }
public func uninstall(completionHandler: (() -> Void)?) { public func uninstall(completionHandler: (() -> Void)?) {
find(with: bundleIdentifier) { (manager) in provider.uninstall(completionHandler: completionHandler)
guard let manager = manager else {
completionHandler?()
return
}
manager.connection.stopVPNTunnel()
manager.removeFromPreferences { (error) in
self.manager = nil
completionHandler?()
}
}
} }
// MARK: VPNProviderIPC
public func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void) { public func requestDebugLog(fallback: (() -> String)?, completionHandler: @escaping (String) -> Void) {
guard status != .disconnected else { guard provider.status != .disconnected else {
completionHandler(fallback?() ?? "") completionHandler(fallback?() ?? "")
return return
} }
@ -188,11 +94,10 @@ public class OpenVPNProvider: VPNProvider {
} }
} }
} }
public func requestBytesCount(completionHandler: @escaping ((UInt, UInt)?) -> Void) { public func requestBytesCount(completionHandler: @escaping ((UInt, UInt)?) -> Void) {
find(with: bundleIdentifier) { provider.lookup { manager, error in
self.manager = $0 guard let session = manager?.connection as? NETunnelProviderSession else {
guard let session = self.manager?.connection as? NETunnelProviderSession else {
DispatchQueue.main.async { DispatchQueue.main.async {
completionHandler(nil) completionHandler(nil)
} }
@ -219,11 +124,10 @@ public class OpenVPNProvider: VPNProvider {
} }
} }
} }
public func requestServerConfiguration(completionHandler: @escaping (Any?) -> Void) { public func requestServerConfiguration(completionHandler: @escaping (Any?) -> Void) {
find(with: bundleIdentifier) { provider.lookup { manager, error in
self.manager = $0 guard let session = manager?.connection as? NETunnelProviderSession else {
guard let session = self.manager?.connection as? NETunnelProviderSession else {
DispatchQueue.main.async { DispatchQueue.main.async {
completionHandler(nil) completionHandler(nil)
} }
@ -248,36 +152,19 @@ public class OpenVPNProvider: VPNProvider {
} }
} }
} }
// MARK: Helpers // 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) { private func findAndRequestDebugLog(completionHandler: @escaping (String?) -> Void) {
find(with: bundleIdentifier) { provider.lookup { manager, error in
self.manager = $0 guard let session = manager?.connection as? NETunnelProviderSession else {
guard let session = self.manager?.connection as? NETunnelProviderSession else {
completionHandler(nil) completionHandler(nil)
return return
} }
OpenVPNProvider.requestDebugLog(session: session, completionHandler: completionHandler) OpenVPNProvider.requestDebugLog(session: session, completionHandler: completionHandler)
} }
} }
private static func requestDebugLog(session: NETunnelProviderSession, completionHandler: @escaping (String?) -> Void) { private static func requestDebugLog(session: NETunnelProviderSession, completionHandler: @escaping (String?) -> Void) {
do { do {
try session.sendProviderMessage(OpenVPNTunnelProvider.Message.requestLog.data) { (data) in try session.sendProviderMessage(OpenVPNTunnelProvider.Message.requestLog.data) { (data) in
@ -292,27 +179,4 @@ public class OpenVPNProvider: VPNProvider {
completionHandler(nil) 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: VPN.didChangeStatus, object: self)
}
@objc private func vpnDidReinstall(_ notification: Notification) {
NotificationCenter.default.post(name: VPN.didReinstall, object: self)
}
} }