Add SwiftLint (#318)

This commit is contained in:
Davide De Rosa 2023-04-20 21:52:45 +02:00 committed by GitHub
parent c62fc4adaa
commit 0c77062add
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
96 changed files with 1292 additions and 1266 deletions

View File

@ -27,7 +27,7 @@ import Foundation
import TunnelKitOpenVPNAppExtension import TunnelKitOpenVPNAppExtension
class PacketTunnelProvider: OpenVPNTunnelProvider { class PacketTunnelProvider: OpenVPNTunnelProvider {
override func startTunnel(options: [String : NSObject]? = nil) async throws { override func startTunnel(options: [String: NSObject]? = nil) async throws {
dataCountInterval = 3 dataCountInterval = 3
try await super.startTunnel(options: options) try await super.startTunnel(options: options)
} }

View File

@ -31,38 +31,37 @@ private let log = SwiftyBeaver.self
@UIApplicationMain @UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate { class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let logDestination = ConsoleDestination() let logDestination = ConsoleDestination()
logDestination.minLevel = .debug logDestination.minLevel = .debug
logDestination.format = "$DHH:mm:ss$d $L $N.$F:$l - $M" logDestination.format = "$DHH:mm:ss$d $L $N.$F:$l - $M"
log.addDestination(logDestination) log.addDestination(logDestination)
// Override point for customization after application launch. // Override point for customization after application launch.
return true return true
} }
func applicationWillResignActive(_ application: UIApplication) { func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
} }
func applicationDidEnterBackground(_ application: UIApplication) { func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
} }
func applicationWillEnterForeground(_ application: UIApplication) { func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
} }
func applicationDidBecomeActive(_ application: UIApplication) { func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
} }
func applicationWillTerminate(_ application: UIApplication) { func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
} }

View File

@ -34,39 +34,39 @@ private let tunnelIdentifier = "com.algoritmico.ios.TunnelKit.Demo.OpenVPN.Tunne
class OpenVPNViewController: UIViewController { class OpenVPNViewController: UIViewController {
@IBOutlet var textUsername: UITextField! @IBOutlet var textUsername: UITextField!
@IBOutlet var textPassword: UITextField! @IBOutlet var textPassword: UITextField!
@IBOutlet var textServer: UITextField! @IBOutlet var textServer: UITextField!
@IBOutlet var textDomain: UITextField! @IBOutlet var textDomain: UITextField!
@IBOutlet var textPort: UITextField! @IBOutlet var textPort: UITextField!
@IBOutlet var switchTCP: UISwitch! @IBOutlet var switchTCP: UISwitch!
@IBOutlet var buttonConnection: UIButton! @IBOutlet var buttonConnection: UIButton!
@IBOutlet var textLog: UITextView! @IBOutlet var textLog: UITextView!
private let vpn = NetworkExtensionVPN() private let vpn = NetworkExtensionVPN()
private var vpnStatus: VPNStatus = .disconnected private var vpnStatus: VPNStatus = .disconnected
private let keychain = Keychain(group: appGroup) private let keychain = Keychain(group: appGroup)
private var cfg: OpenVPN.ProviderConfiguration? private var cfg: OpenVPN.ProviderConfiguration?
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
textServer.text = "nl-free-50" textServer.text = "nl-free-50"
textDomain.text = "protonvpn.net" textDomain.text = "protonvpn.net"
textPort.text = "80" textPort.text = "80"
switchTCP.isOn = false switchTCP.isOn = false
textUsername.text = "" textUsername.text = ""
textPassword.text = "" textPassword.text = ""
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, self,
selector: #selector(VPNStatusDidChange(notification:)), selector: #selector(VPNStatusDidChange(notification:)),
@ -86,20 +86,20 @@ class OpenVPNViewController: UIViewController {
// testFetchRef() // testFetchRef()
} }
@IBAction func connectionClicked(_ sender: Any) { @IBAction func connectionClicked(_ sender: Any) {
switch vpnStatus { switch vpnStatus {
case .disconnected: case .disconnected:
connect() connect()
case .connected, .connecting, .disconnecting: case .connected, .connecting, .disconnecting:
disconnect() disconnect()
} }
} }
@IBAction func tcpClicked(_ sender: Any) { @IBAction func tcpClicked(_ sender: Any) {
} }
func connect() { func connect() {
let server = textServer.text! let server = textServer.text!
let domain = textDomain.text! let domain = textDomain.text!
@ -136,7 +136,7 @@ class OpenVPNViewController: UIViewController {
) )
} }
} }
func disconnect() { func disconnect() {
Task { Task {
await vpn.disconnect() await vpn.disconnect()
@ -152,20 +152,20 @@ class OpenVPNViewController: UIViewController {
} }
textLog.text = try? String(contentsOf: url) textLog.text = try? String(contentsOf: url)
} }
func updateButton() { func updateButton() {
switch vpnStatus { switch vpnStatus {
case .connected, .connecting: case .connected, .connecting:
buttonConnection.setTitle("Disconnect", for: .normal) buttonConnection.setTitle("Disconnect", for: .normal)
case .disconnected: case .disconnected:
buttonConnection.setTitle("Connect", for: .normal) buttonConnection.setTitle("Connect", for: .normal)
case .disconnecting: case .disconnecting:
buttonConnection.setTitle("Disconnecting", for: .normal) buttonConnection.setTitle("Disconnecting", for: .normal)
} }
} }
@objc private func VPNStatusDidChange(notification: Notification) { @objc private func VPNStatusDidChange(notification: Notification) {
vpnStatus = notification.vpnStatus vpnStatus = notification.vpnStatus
print("VPNStatusDidChange: \(vpnStatus)") print("VPNStatusDidChange: \(vpnStatus)")

View File

@ -33,24 +33,24 @@ private let tunnelIdentifier = "com.algoritmico.ios.TunnelKit.Demo.WireGuard.Tun
class WireGuardViewController: UIViewController { class WireGuardViewController: UIViewController {
@IBOutlet var textClientPrivateKey: UITextField! @IBOutlet var textClientPrivateKey: UITextField!
@IBOutlet var textAddress: UITextField! @IBOutlet var textAddress: UITextField!
@IBOutlet var textServerPublicKey: UITextField! @IBOutlet var textServerPublicKey: UITextField!
@IBOutlet var textServerAddress: UITextField! @IBOutlet var textServerAddress: UITextField!
@IBOutlet var textServerPort: UITextField! @IBOutlet var textServerPort: UITextField!
@IBOutlet var buttonConnection: UIButton! @IBOutlet var buttonConnection: UIButton!
private let vpn = NetworkExtensionVPN() private let vpn = NetworkExtensionVPN()
private var vpnStatus: VPNStatus = .disconnected private var vpnStatus: VPNStatus = .disconnected
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
textClientPrivateKey.placeholder = "client private key" textClientPrivateKey.placeholder = "client private key"
textAddress.placeholder = "client address" textAddress.placeholder = "client address"
textServerPublicKey.placeholder = "server public key" textServerPublicKey.placeholder = "server public key"
@ -81,7 +81,7 @@ class WireGuardViewController: UIViewController {
switch vpnStatus { switch vpnStatus {
case .disconnected: case .disconnected:
connect() connect()
case .connected, .connecting, .disconnecting: case .connected, .connecting, .disconnecting:
disconnect() disconnect()
} }
@ -116,7 +116,7 @@ class WireGuardViewController: UIViewController {
) )
} }
} }
func disconnect() { func disconnect() {
Task { Task {
await vpn.disconnect() await vpn.disconnect()
@ -127,15 +127,15 @@ class WireGuardViewController: UIViewController {
switch vpnStatus { switch vpnStatus {
case .connected, .connecting: case .connected, .connecting:
buttonConnection.setTitle("Disconnect", for: .normal) buttonConnection.setTitle("Disconnect", for: .normal)
case .disconnected: case .disconnected:
buttonConnection.setTitle("Connect", for: .normal) buttonConnection.setTitle("Connect", for: .normal)
case .disconnecting: case .disconnecting:
buttonConnection.setTitle("Disconnecting", for: .normal) buttonConnection.setTitle("Disconnecting", for: .normal)
} }
} }
@objc private func VPNStatusDidChange(notification: Notification) { @objc private func VPNStatusDidChange(notification: Notification) {
vpnStatus = notification.vpnStatus vpnStatus = notification.vpnStatus
print("VPNStatusDidChange: \(notification.vpnStatus)") print("VPNStatusDidChange: \(notification.vpnStatus)")

View File

@ -31,8 +31,6 @@ private let log = SwiftyBeaver.self
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
let logDestination = ConsoleDestination() let logDestination = ConsoleDestination()
logDestination.minLevel = .debug logDestination.minLevel = .debug
@ -46,6 +44,4 @@ class AppDelegate: NSObject, NSApplicationDelegate {
// Insert code here to tear down your application // Insert code here to tear down your application
} }
} }

View File

@ -34,34 +34,34 @@ private let tunnelIdentifier = "com.algoritmico.macos.TunnelKit.Demo.OpenVPN.Tun
class OpenVPNViewController: NSViewController { class OpenVPNViewController: NSViewController {
@IBOutlet var textUsername: NSTextField! @IBOutlet var textUsername: NSTextField!
@IBOutlet var textPassword: NSTextField! @IBOutlet var textPassword: NSTextField!
@IBOutlet var textServer: NSTextField! @IBOutlet var textServer: NSTextField!
@IBOutlet var textDomain: NSTextField! @IBOutlet var textDomain: NSTextField!
@IBOutlet var textPort: NSTextField! @IBOutlet var textPort: NSTextField!
@IBOutlet var buttonConnection: NSButton! @IBOutlet var buttonConnection: NSButton!
private let vpn = NetworkExtensionVPN() private let vpn = NetworkExtensionVPN()
private var vpnStatus: VPNStatus = .disconnected private var vpnStatus: VPNStatus = .disconnected
private let keychain = Keychain(group: appGroup) private let keychain = Keychain(group: appGroup)
private var cfg: OpenVPN.ProviderConfiguration? private var cfg: OpenVPN.ProviderConfiguration?
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
textServer.stringValue = "nl-free-50" textServer.stringValue = "nl-free-50"
textDomain.stringValue = "protonvpn.net" textDomain.stringValue = "protonvpn.net"
textPort.stringValue = "80" textPort.stringValue = "80"
textUsername.stringValue = "" textUsername.stringValue = ""
textPassword.stringValue = "" textPassword.stringValue = ""
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, self,
selector: #selector(VPNStatusDidChange(notification:)), selector: #selector(VPNStatusDidChange(notification:)),
@ -78,20 +78,20 @@ class OpenVPNViewController: NSViewController {
Task { Task {
await vpn.prepare() await vpn.prepare()
} }
// testFetchRef() // testFetchRef()
} }
@IBAction func connectionClicked(_ sender: Any) { @IBAction func connectionClicked(_ sender: Any) {
switch vpnStatus { switch vpnStatus {
case .disconnected: case .disconnected:
connect() connect()
case .connected, .connecting, .disconnecting: case .connected, .connecting, .disconnecting:
disconnect() disconnect()
} }
} }
func connect() { func connect() {
let server = textServer.stringValue let server = textServer.stringValue
let domain = textDomain.stringValue let domain = textDomain.stringValue
@ -127,7 +127,7 @@ class OpenVPNViewController: NSViewController {
) )
} }
} }
func disconnect() { func disconnect() {
Task { Task {
await vpn.disconnect() await vpn.disconnect()
@ -138,15 +138,15 @@ class OpenVPNViewController: NSViewController {
switch vpnStatus { switch vpnStatus {
case .connected, .connecting: case .connected, .connecting:
buttonConnection.title = "Disconnect" buttonConnection.title = "Disconnect"
case .disconnected: case .disconnected:
buttonConnection.title = "Connect" buttonConnection.title = "Connect"
case .disconnecting: case .disconnecting:
buttonConnection.title = "Disconnecting" buttonConnection.title = "Disconnecting"
} }
} }
@objc private func VPNStatusDidChange(notification: Notification) { @objc private func VPNStatusDidChange(notification: Notification) {
vpnStatus = notification.vpnStatus vpnStatus = notification.vpnStatus
print("VPNStatusDidChange: \(vpnStatus)") print("VPNStatusDidChange: \(vpnStatus)")
@ -175,4 +175,3 @@ class OpenVPNViewController: NSViewController {
// print("\(username) -> \(fetchedPassword)") // print("\(username) -> \(fetchedPassword)")
// } // }
} }

View File

@ -33,24 +33,24 @@ private let tunnelIdentifier = "com.algoritmico.macos.TunnelKit.Demo.WireGuard.T
class WireGuardViewController: NSViewController { class WireGuardViewController: NSViewController {
@IBOutlet var textClientPrivateKey: NSTextField! @IBOutlet var textClientPrivateKey: NSTextField!
@IBOutlet var textAddress: NSTextField! @IBOutlet var textAddress: NSTextField!
@IBOutlet var textServerPublicKey: NSTextField! @IBOutlet var textServerPublicKey: NSTextField!
@IBOutlet var textServerAddress: NSTextField! @IBOutlet var textServerAddress: NSTextField!
@IBOutlet var textServerPort: NSTextField! @IBOutlet var textServerPort: NSTextField!
@IBOutlet var buttonConnection: NSButton! @IBOutlet var buttonConnection: NSButton!
private let vpn = NetworkExtensionVPN() private let vpn = NetworkExtensionVPN()
private var vpnStatus: VPNStatus = .disconnected private var vpnStatus: VPNStatus = .disconnected
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
textClientPrivateKey.placeholderString = "client private key" textClientPrivateKey.placeholderString = "client private key"
textAddress.placeholderString = "client address" textAddress.placeholderString = "client address"
textServerPublicKey.placeholderString = "server public key" textServerPublicKey.placeholderString = "server public key"
@ -81,7 +81,7 @@ class WireGuardViewController: NSViewController {
switch vpnStatus { switch vpnStatus {
case .disconnected: case .disconnected:
connect() connect()
case .connected, .connecting, .disconnecting: case .connected, .connecting, .disconnecting:
disconnect() disconnect()
} }
@ -116,7 +116,7 @@ class WireGuardViewController: NSViewController {
) )
} }
} }
func disconnect() { func disconnect() {
Task { Task {
await vpn.disconnect() await vpn.disconnect()
@ -127,15 +127,15 @@ class WireGuardViewController: NSViewController {
switch vpnStatus { switch vpnStatus {
case .connected, .connecting: case .connected, .connecting:
buttonConnection.title = "Disconnect" buttonConnection.title = "Disconnect"
case .disconnected: case .disconnected:
buttonConnection.title = "Connect" buttonConnection.title = "Connect"
case .disconnecting: case .disconnecting:
buttonConnection.title = "Disconnecting" buttonConnection.title = "Disconnecting"
} }
} }
@objc private func VPNStatusDidChange(notification: Notification) { @objc private func VPNStatusDidChange(notification: Notification) {
vpnStatus = notification.vpnStatus vpnStatus = notification.vpnStatus
print("VPNStatusDidChange: \(vpnStatus)") print("VPNStatusDidChange: \(vpnStatus)")

View File

@ -41,7 +41,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch. // Override point for customization after application launch.
return true return true
@ -69,6 +68,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
} }
} }

View File

@ -48,6 +48,4 @@ class ViewController: UIViewController {
// Dispose of any resources that can be recreated. // Dispose of any resources that can be recreated.
} }
} }

View File

@ -418,6 +418,7 @@
0E05416725A232FD00EFC5FF /* Resources */, 0E05416725A232FD00EFC5FF /* Resources */,
0E0541AD25A2343500EFC5FF /* Embed App Extensions */, 0E0541AD25A2343500EFC5FF /* Embed App Extensions */,
0E05438525A240E400EFC5FF /* Embed Frameworks */, 0E05438525A240E400EFC5FF /* Embed Frameworks */,
0EB5A56B29F1C9C8005313B3 /* SwiftLint */,
); );
buildRules = ( buildRules = (
); );
@ -463,6 +464,7 @@
0E05422A25A236EB00EFC5FF /* Resources */, 0E05422A25A236EB00EFC5FF /* Resources */,
0E05428425A239C600EFC5FF /* Embed App Extensions */, 0E05428425A239C600EFC5FF /* Embed App Extensions */,
0E05438825A240E900EFC5FF /* Embed Frameworks */, 0E05438825A240E900EFC5FF /* Embed Frameworks */,
0EB5A56A29F1C8FC005313B3 /* SwiftLint */,
); );
buildRules = ( buildRules = (
); );
@ -700,6 +702,45 @@
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
0EB5A56A29F1C8FC005313B3 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "PATH=\"/opt/homebrew/bin:${PATH}\"\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
0EB5A56B29F1C9C8005313B3 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "PATH=\"/opt/homebrew/bin:${PATH}\"\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
0E05416525A232FD00EFC5FF /* Sources */ = { 0E05416525A232FD00EFC5FF /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;

View File

@ -188,6 +188,6 @@ let package = Package(
dependencies: [ dependencies: [
"TunnelKitCore", "TunnelKitCore",
"TunnelKitLZO" "TunnelKitLZO"
]), ])
] ]
) )

View File

@ -67,10 +67,10 @@ public protocol GenericSocket {
/// The address of the remote endpoint. /// The address of the remote endpoint.
var remoteAddress: String? { get } var remoteAddress: String? { get }
/// `true` if the socket has a better path. /// `true` if the socket has a better path.
var hasBetterPath: Bool { get } var hasBetterPath: Bool { get }
/// `true` if the socket was shut down. /// `true` if the socket was shut down.
var isShutdown: Bool { get } var isShutdown: Bool { get }
@ -94,7 +94,7 @@ public protocol GenericSocket {
Shuts down the socket Shuts down the socket
**/ **/
func shutdown() func shutdown()
/** /**
Returns an upgraded socket if available (e.g. when a better path exists). Returns an upgraded socket if available (e.g. when a better path exists).

View File

@ -52,9 +52,9 @@ public class InterfaceObserver: NSObject {
public static let didDetectWifiChange = Notification.Name("InterfaceObserverDidDetectWifiChange") public static let didDetectWifiChange = Notification.Name("InterfaceObserverDidDetectWifiChange")
private var queue: DispatchQueue? private var queue: DispatchQueue?
private var timer: DispatchSourceTimer? private var timer: DispatchSourceTimer?
private var lastWifiName: String? private var lastWifiName: String?
/** /**
@ -89,7 +89,7 @@ public class InterfaceObserver: NSObject {
self.fireWifiChange(withSSID: $0) self.fireWifiChange(withSSID: $0)
} }
} }
private func fireWifiChange(withSSID ssid: String?) { private func fireWifiChange(withSSID ssid: String?) {
if ssid != lastWifiName { if ssid != lastWifiName {
if let current = ssid { if let current = ssid {

View File

@ -48,7 +48,7 @@ public class MemoryDestination: BaseDestination, CustomStringConvertible {
super.init() super.init()
asynchronously = false asynchronously = false
} }
/** /**
Starts logging. Optionally prepend an array of lines. Starts logging. Optionally prepend an array of lines.
@ -71,7 +71,7 @@ public class MemoryDestination: BaseDestination, CustomStringConvertible {
try? content.write(to: url, atomically: true, encoding: .utf8) try? content.write(to: url, atomically: true, encoding: .utf8)
} }
} }
// MARK: BaseDestination // MARK: BaseDestination
// XXX: executed in SwiftyBeaver queue. DO NOT invoke execute* here (sync in sync would crash otherwise) // XXX: executed in SwiftyBeaver queue. DO NOT invoke execute* here (sync in sync would crash otherwise)
@ -89,7 +89,7 @@ public class MemoryDestination: BaseDestination, CustomStringConvertible {
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
return executeSynchronously { return executeSynchronously {
return self.buffer.joined(separator: "\n") return self.buffer.joined(separator: "\n")

View File

@ -44,36 +44,36 @@ private let log = SwiftyBeaver.self
/// TCP implementation of a `GenericSocket` via NetworkExtension. /// TCP implementation of a `GenericSocket` via NetworkExtension.
public class NETCPSocket: NSObject, GenericSocket { public class NETCPSocket: NSObject, GenericSocket {
private static var linkContext = 0 private static var linkContext = 0
public let impl: NWTCPConnection public let impl: NWTCPConnection
public init(impl: NWTCPConnection) { public init(impl: NWTCPConnection) {
self.impl = impl self.impl = impl
isActive = false isActive = false
isShutdown = false isShutdown = false
} }
// MARK: GenericSocket // MARK: GenericSocket
private weak var queue: DispatchQueue? private weak var queue: DispatchQueue?
private var isActive: Bool private var isActive: Bool
public private(set) var isShutdown: Bool public private(set) var isShutdown: Bool
public var remoteAddress: String? { public var remoteAddress: String? {
return (impl.remoteAddress as? NWHostEndpoint)?.hostname return (impl.remoteAddress as? NWHostEndpoint)?.hostname
} }
public var hasBetterPath: Bool { public var hasBetterPath: Bool {
return impl.hasBetterPath return impl.hasBetterPath
} }
public weak var delegate: GenericSocketDelegate? public weak var delegate: GenericSocketDelegate?
public func observe(queue: DispatchQueue, activeTimeout: Int) { public func observe(queue: DispatchQueue, activeTimeout: Int) {
isActive = false isActive = false
self.queue = queue self.queue = queue
queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in
guard let _self = self else { guard let _self = self else {
@ -87,28 +87,28 @@ public class NETCPSocket: NSObject, GenericSocket {
impl.addObserver(self, forKeyPath: #keyPath(NWTCPConnection.state), options: [.initial, .new], context: &NETCPSocket.linkContext) impl.addObserver(self, forKeyPath: #keyPath(NWTCPConnection.state), options: [.initial, .new], context: &NETCPSocket.linkContext)
impl.addObserver(self, forKeyPath: #keyPath(NWTCPConnection.hasBetterPath), options: .new, context: &NETCPSocket.linkContext) impl.addObserver(self, forKeyPath: #keyPath(NWTCPConnection.hasBetterPath), options: .new, context: &NETCPSocket.linkContext)
} }
public func unobserve() { public func unobserve() {
impl.removeObserver(self, forKeyPath: #keyPath(NWTCPConnection.state), context: &NETCPSocket.linkContext) impl.removeObserver(self, forKeyPath: #keyPath(NWTCPConnection.state), context: &NETCPSocket.linkContext)
impl.removeObserver(self, forKeyPath: #keyPath(NWTCPConnection.hasBetterPath), context: &NETCPSocket.linkContext) impl.removeObserver(self, forKeyPath: #keyPath(NWTCPConnection.hasBetterPath), context: &NETCPSocket.linkContext)
} }
public func shutdown() { public func shutdown() {
impl.writeClose() impl.writeClose()
impl.cancel() impl.cancel()
} }
public func upgraded() -> GenericSocket? { public func upgraded() -> GenericSocket? {
guard impl.hasBetterPath else { guard impl.hasBetterPath else {
return nil return nil
} }
return NETCPSocket(impl: NWTCPConnection(upgradeFor: impl)) return NETCPSocket(impl: NWTCPConnection(upgradeFor: impl))
} }
// MARK: Connection KVO (any queue) // MARK: Connection KVO (any queue)
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard (context == &NETCPSocket.linkContext) else { guard context == &NETCPSocket.linkContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return return
} }
@ -119,8 +119,8 @@ public class NETCPSocket: NSObject, GenericSocket {
self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context) self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context)
} }
} }
private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
// if let keyPath = keyPath { // if let keyPath = keyPath {
// log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))") // log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))")
// } // }
@ -138,7 +138,7 @@ public class NETCPSocket: NSObject, GenericSocket {
} else { } else {
log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint.maskedDescription) -> in progress)") log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint.maskedDescription) -> in progress)")
} }
switch impl.state { switch impl.state {
case .connected: case .connected:
guard !isActive else { guard !isActive else {
@ -146,26 +146,26 @@ public class NETCPSocket: NSObject, GenericSocket {
} }
isActive = true isActive = true
delegate?.socketDidBecomeActive(self) delegate?.socketDidBecomeActive(self)
case .cancelled: case .cancelled:
isShutdown = true isShutdown = true
delegate?.socket(self, didShutdownWithFailure: false) delegate?.socket(self, didShutdownWithFailure: false)
case .disconnected: case .disconnected:
isShutdown = true isShutdown = true
delegate?.socket(self, didShutdownWithFailure: true) delegate?.socket(self, didShutdownWithFailure: true)
default: default:
break break
} }
case #keyPath(NWTCPConnection.hasBetterPath): case #keyPath(NWTCPConnection.hasBetterPath):
guard impl.hasBetterPath else { guard impl.hasBetterPath else {
break break
} }
log.debug("Socket has a better path") log.debug("Socket has a better path")
delegate?.socketHasBetterPath(self) delegate?.socketHasBetterPath(self)
default: default:
break break
} }

View File

@ -44,40 +44,40 @@ private let log = SwiftyBeaver.self
/// `TunnelInterface` implementation via NetworkExtension. /// `TunnelInterface` implementation via NetworkExtension.
public class NETunnelInterface: TunnelInterface { public class NETunnelInterface: TunnelInterface {
private weak var impl: NEPacketTunnelFlow? private weak var impl: NEPacketTunnelFlow?
public init(impl: NEPacketTunnelFlow) { public init(impl: NEPacketTunnelFlow) {
self.impl = impl self.impl = impl
} }
// MARK: TunnelInterface // MARK: TunnelInterface
public var isPersistent: Bool { public var isPersistent: Bool {
return false return false
} }
// MARK: IOInterface // MARK: IOInterface
public func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { public func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) {
loopReadPackets(queue, handler) loopReadPackets(queue, handler)
} }
private func loopReadPackets(_ queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { private func loopReadPackets(_ queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) {
// WARNING: runs in NEPacketTunnelFlow queue // WARNING: runs in NEPacketTunnelFlow queue
impl?.readPackets { [weak self] (packets, protocols) in impl?.readPackets { [weak self] (packets, _) in
queue.sync { queue.sync {
self?.loopReadPackets(queue, handler) self?.loopReadPackets(queue, handler)
handler(packets, nil) handler(packets, nil)
} }
} }
} }
public func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { public func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
let protocolNumber = IPHeader.protocolNumber(inPacket: packet) let protocolNumber = IPHeader.protocolNumber(inPacket: packet)
impl?.writePackets([packet], withProtocols: [protocolNumber]) impl?.writePackets([packet], withProtocols: [protocolNumber])
completionHandler?(nil) completionHandler?(nil)
} }
public func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { public func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) {
let protocols = packets.map { let protocols = packets.map {
IPHeader.protocolNumber(inPacket: $0) IPHeader.protocolNumber(inPacket: $0)

View File

@ -44,37 +44,37 @@ private let log = SwiftyBeaver.self
/// UDP implementation of a `GenericSocket` via NetworkExtension. /// UDP implementation of a `GenericSocket` via NetworkExtension.
public class NEUDPSocket: NSObject, GenericSocket { public class NEUDPSocket: NSObject, GenericSocket {
private static var linkContext = 0 private static var linkContext = 0
public let impl: NWUDPSession public let impl: NWUDPSession
public init(impl: NWUDPSession) { public init(impl: NWUDPSession) {
self.impl = impl self.impl = impl
isActive = false isActive = false
isShutdown = false isShutdown = false
} }
// MARK: GenericSocket // MARK: GenericSocket
private weak var queue: DispatchQueue? private weak var queue: DispatchQueue?
private var isActive: Bool private var isActive: Bool
public private(set) var isShutdown: Bool public private(set) var isShutdown: Bool
public var remoteAddress: String? { public var remoteAddress: String? {
return (impl.resolvedEndpoint as? NWHostEndpoint)?.hostname return (impl.resolvedEndpoint as? NWHostEndpoint)?.hostname
} }
public var hasBetterPath: Bool { public var hasBetterPath: Bool {
return impl.hasBetterPath return impl.hasBetterPath
} }
public weak var delegate: GenericSocketDelegate? public weak var delegate: GenericSocketDelegate?
public func observe(queue: DispatchQueue, activeTimeout: Int) { public func observe(queue: DispatchQueue, activeTimeout: Int) {
isActive = false isActive = false
self.queue = queue self.queue = queue
queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in
guard let _self = self else { guard let _self = self else {
@ -88,27 +88,27 @@ public class NEUDPSocket: NSObject, GenericSocket {
impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.state), options: [.initial, .new], context: &NEUDPSocket.linkContext) impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.state), options: [.initial, .new], context: &NEUDPSocket.linkContext)
impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), options: .new, context: &NEUDPSocket.linkContext) impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), options: .new, context: &NEUDPSocket.linkContext)
} }
public func unobserve() { public func unobserve() {
impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.state), context: &NEUDPSocket.linkContext) impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.state), context: &NEUDPSocket.linkContext)
impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), context: &NEUDPSocket.linkContext) impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), context: &NEUDPSocket.linkContext)
} }
public func shutdown() { public func shutdown() {
impl.cancel() impl.cancel()
} }
public func upgraded() -> GenericSocket? { public func upgraded() -> GenericSocket? {
guard impl.hasBetterPath else { guard impl.hasBetterPath else {
return nil return nil
} }
return NEUDPSocket(impl: NWUDPSession(upgradeFor: impl)) return NEUDPSocket(impl: NWUDPSession(upgradeFor: impl))
} }
// MARK: Connection KVO (any queue) // MARK: Connection KVO (any queue)
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard (context == &NEUDPSocket.linkContext) else { guard context == &NEUDPSocket.linkContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return return
} }
@ -119,8 +119,8 @@ public class NEUDPSocket: NSObject, GenericSocket {
self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context) self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context)
} }
} }
private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
// if let keyPath = keyPath { // if let keyPath = keyPath {
// log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))") // log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))")
// } // }
@ -138,7 +138,7 @@ public class NEUDPSocket: NSObject, GenericSocket {
} else { } else {
log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint.maskedDescription) -> in progress)") log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint.maskedDescription) -> in progress)")
} }
switch impl.state { switch impl.state {
case .ready: case .ready:
guard !isActive else { guard !isActive else {
@ -146,29 +146,29 @@ public class NEUDPSocket: NSObject, GenericSocket {
} }
isActive = true isActive = true
delegate?.socketDidBecomeActive(self) delegate?.socketDidBecomeActive(self)
case .cancelled: case .cancelled:
isShutdown = true isShutdown = true
delegate?.socket(self, didShutdownWithFailure: false) delegate?.socket(self, didShutdownWithFailure: false)
case .failed: case .failed:
isShutdown = true isShutdown = true
// if timedOut { // if timedOut {
// delegate?.socketShouldChangeProtocol(self) // delegate?.socketShouldChangeProtocol(self)
// } // }
delegate?.socket(self, didShutdownWithFailure: true) delegate?.socket(self, didShutdownWithFailure: true)
default: default:
break break
} }
case #keyPath(NWUDPSession.hasBetterPath): case #keyPath(NWUDPSession.hasBetterPath):
guard impl.hasBetterPath else { guard impl.hasBetterPath else {
break break
} }
log.debug("Socket has a better path") log.debug("Socket has a better path")
delegate?.socketHasBetterPath(self) delegate?.socketHasBetterPath(self)
default: default:
break break
} }

View File

@ -31,10 +31,10 @@ public struct BidirectionalState<T> {
/// The inbound state. /// The inbound state.
public var inbound: T public var inbound: T
/// The outbound state. /// The outbound state.
public var outbound: T public var outbound: T
/** /**
Returns current state as a pair. Returns current state as a pair.
@ -43,7 +43,7 @@ public struct BidirectionalState<T> {
public var pair: (T, T) { public var pair: (T, T) {
return (inbound, outbound) return (inbound, outbound)
} }
/** /**
Inits state with a value that will later be reused by `reset()`. Inits state with a value that will later be reused by `reset()`.
@ -54,7 +54,7 @@ public struct BidirectionalState<T> {
outbound = value outbound = value
resetValue = value resetValue = value
} }
/** /**
Resets state to the value provided with `init(withResetValue:)`. Resets state to the value provided with `init(withResetValue:)`.
*/ */

View File

@ -41,7 +41,7 @@ public class CoreConfiguration {
/// Unique identifier of the library. /// Unique identifier of the library.
public static let identifier = "com.algoritmico.TunnelKit" public static let identifier = "com.algoritmico.TunnelKit"
/// Library version as seen in `Info.plist`. /// Library version as seen in `Info.plist`.
public static let version: String = { public static let version: String = {
let bundle = Bundle(for: CoreConfiguration.self) let bundle = Bundle(for: CoreConfiguration.self)
@ -63,7 +63,7 @@ public class CoreConfiguration {
/// String representing library version. /// String representing library version.
public static var versionIdentifier: String? public static var versionIdentifier: String?
/// Enables logging of sensitive data (hardcoded to false). /// Enables logging of sensitive data (hardcoded to false).
public static let logsSensitiveData = false public static let logsSensitiveData = false
} }

View File

@ -33,10 +33,10 @@ public enum DNSProtocol: String, Codable {
/// Standard plaintext DNS (port 53). /// Standard plaintext DNS (port 53).
case plain case plain
/// DNS over HTTPS. /// DNS over HTTPS.
case https case https
/// DNS over TLS (port 853). /// DNS over TLS (port 853).
case tls case tls
} }

View File

@ -54,7 +54,7 @@ public struct DNSRecord {
/// Errors coming from `DNSResolver`. /// Errors coming from `DNSResolver`.
public enum DNSError: Error { public enum DNSError: Error {
case failure case failure
case timeout case timeout
} }
@ -95,14 +95,14 @@ public class DNSResolver {
pendingHandler = nil pendingHandler = nil
} }
} }
private static func didResolve(host: CFHost, completionHandler: @escaping (Result<[DNSRecord], DNSError>) -> Void) { private static func didResolve(host: CFHost, completionHandler: @escaping (Result<[DNSRecord], DNSError>) -> Void) {
var success: DarwinBoolean = false var success: DarwinBoolean = false
guard let rawAddresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as Array? else { guard let rawAddresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as Array? else {
completionHandler(.failure(.failure)) completionHandler(.failure(.failure))
return return
} }
var records: [DNSRecord] = [] var records: [DNSRecord] = []
for case let rawAddress as Data in rawAddresses { for case let rawAddress as Data in rawAddresses {
var ipAddress = [CChar](repeating: 0, count: Int(NI_MAXHOST)) var ipAddress = [CChar](repeating: 0, count: Int(NI_MAXHOST))
@ -152,7 +152,7 @@ public class DNSResolver {
} }
return groups.map { "\($0)" }.joined(separator: ".") return groups.map { "\($0)" }.joined(separator: ".")
} }
/** /**
Returns a numeric representation from an IPv4 address. Returns a numeric representation from an IPv4 address.

View File

@ -30,10 +30,10 @@ public struct DataCount: Equatable {
/// Received bytes count. /// Received bytes count.
public let received: UInt public let received: UInt
/// Sent bytes count. /// Sent bytes count.
public let sent: UInt public let sent: UInt
public init(_ received: UInt, _ sent: UInt) { public init(_ received: UInt, _ sent: UInt) {
self.received = received self.received = received
self.sent = sent self.sent = sent

View File

@ -28,29 +28,29 @@ import Foundation
/// Helps expressing integers in bytes, kB, MB, GB. /// Helps expressing integers in bytes, kB, MB, GB.
public enum DataUnit: UInt, CustomStringConvertible { public enum DataUnit: UInt, CustomStringConvertible {
case byte = 1 case byte = 1
case kilobyte = 1024 case kilobyte = 1024
case megabyte = 1048576 case megabyte = 1048576
case gigabyte = 1073741824 case gigabyte = 1073741824
fileprivate var showsDecimals: Bool { fileprivate var showsDecimals: Bool {
switch self { switch self {
case .byte, .kilobyte: case .byte, .kilobyte:
return false return false
case .megabyte, .gigabyte: case .megabyte, .gigabyte:
return true return true
} }
} }
fileprivate var boundary: UInt { fileprivate var boundary: UInt {
return UInt(0.1 * Double(rawValue)) return UInt(0.1 * Double(rawValue))
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
switch self { switch self {
case .byte: case .byte:
@ -82,7 +82,7 @@ extension UInt: DataUnitRepresentable {
.kilobyte, .kilobyte,
.byte .byte
] ]
public var descriptionAsDataUnit: String { public var descriptionAsDataUnit: String {
if self == 0 { if self == 0 {
return "0B" return "0B"

View File

@ -33,7 +33,7 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
private static let rx = NSRegularExpression("^([^\\s]+):(UDP[46]?|TCP[46]?):(\\d+)$") private static let rx = NSRegularExpression("^([^\\s]+):(UDP[46]?|TCP[46]?):(\\d+)$")
public let address: String public let address: String
public let proto: EndpointProtocol public let proto: EndpointProtocol
public init(_ address: String, _ proto: EndpointProtocol) { public init(_ address: String, _ proto: EndpointProtocol) {
@ -60,7 +60,7 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
public var isHostname: Bool { public var isHostname: Bool {
!isIPv4 && !isIPv6 !isIPv4 && !isIPv6
} }
public func withRandomPrefixLength(_ length: Int) throws -> Endpoint { public func withRandomPrefixLength(_ length: Int) throws -> Endpoint {
guard isHostname else { guard isHostname else {
return self return self
@ -69,9 +69,9 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
let prefixedAddress = "\(prefix.toHex()).\(address)" let prefixedAddress = "\(prefix.toHex()).\(address)"
return Endpoint(prefixedAddress, proto) return Endpoint(prefixedAddress, proto)
} }
// MARK: RawRepresentable // MARK: RawRepresentable
public init?(rawValue: String) { public init?(rawValue: String) {
let components = Self.rx.groups(in: rawValue) let components = Self.rx.groups(in: rawValue)
guard components.count == 3 else { guard components.count == 3 else {
@ -90,9 +90,9 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
public var rawValue: String { public var rawValue: String {
"\(address):\(proto.socketType.rawValue):\(proto.port)" "\(address):\(proto.socketType.rawValue):\(proto.port)"
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
"\(address.maskedDescription):\(proto.rawValue)" "\(address.maskedDescription):\(proto.rawValue)"
} }
@ -100,10 +100,10 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
/// Defines the communication protocol of an endpoint. /// Defines the communication protocol of an endpoint.
public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvertible { public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvertible {
/// The socket type. /// The socket type.
public let socketType: SocketType public let socketType: SocketType
/// The remote port. /// The remote port.
public let port: UInt16 public let port: UInt16
@ -111,9 +111,9 @@ public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvert
self.socketType = socketType self.socketType = socketType
self.port = port self.port = port
} }
// MARK: RawRepresentable // MARK: RawRepresentable
public init?(rawValue: String) { public init?(rawValue: String) {
let components = rawValue.components(separatedBy: ":") let components = rawValue.components(separatedBy: ":")
guard components.count == 2 else { guard components.count == 2 else {
@ -127,13 +127,13 @@ public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvert
} }
self.init(socketType, port) self.init(socketType, port)
} }
public var rawValue: String { public var rawValue: String {
"\(socketType.rawValue):\(port)" "\(socketType.rawValue):\(port)"
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
rawValue rawValue
} }
@ -146,7 +146,7 @@ extension EndpointProtocol: Codable {
let proto = EndpointProtocol(rawValue: rawValue) ?? EndpointProtocol(.udp, 1198) let proto = EndpointProtocol(rawValue: rawValue) ?? EndpointProtocol(.udp, 1198)
self.init(proto.socketType, proto.port) self.init(proto.socketType, proto.port)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer() var container = encoder.singleValueContainer()
try container.encode(rawValue) try container.encode(rawValue)

View File

@ -46,7 +46,7 @@ public protocol IOInterface: AnyObject {
- Parameter handler: The handler invoked whenever an array of `Data` packets is received, with an optional `Error` in case a network failure occurs. - Parameter handler: The handler invoked whenever an array of `Data` packets is received, with an optional `Error` in case a network failure occurs.
*/ */
func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void)
/** /**
Writes a packet to the interface. Writes a packet to the interface.
@ -54,7 +54,7 @@ public protocol IOInterface: AnyObject {
- Parameter completionHandler: Invoked on write completion, with an optional `Error` in case a network failure occurs. - Parameter completionHandler: Invoked on write completion, with an optional `Error` in case a network failure occurs.
*/ */
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?)
/** /**
Writes some packets to the interface. Writes some packets to the interface.

View File

@ -27,9 +27,9 @@ import Foundation
/// Helper for handling IP headers. /// Helper for handling IP headers.
public struct IPHeader { public struct IPHeader {
private static let ipV4: UInt8 = 4 private static let ipV4: UInt8 = 4
private static let ipV6: UInt8 = 6 private static let ipV6: UInt8 = 6
private static let ipV4ProtocolNumber = AF_INET as NSNumber private static let ipV4ProtocolNumber = AF_INET as NSNumber
private static let ipV6ProtocolNumber = AF_INET6 as NSNumber private static let ipV6ProtocolNumber = AF_INET6 as NSNumber

View File

@ -27,45 +27,45 @@ import Foundation
/// Encapsulates the IPv4 settings for the tunnel. /// Encapsulates the IPv4 settings for the tunnel.
public struct IPv4Settings: Codable, Equatable, CustomStringConvertible { public struct IPv4Settings: Codable, Equatable, CustomStringConvertible {
/// Represents an IPv4 route in the routing table. /// Represents an IPv4 route in the routing table.
public struct Route: Codable, Hashable, CustomStringConvertible { public struct Route: Codable, Hashable, CustomStringConvertible {
/// The destination host or subnet. /// The destination host or subnet.
public let destination: String public let destination: String
/// The address mask. /// The address mask.
public let mask: String public let mask: String
/// The address of the gateway (falls back to global gateway). /// The address of the gateway (falls back to global gateway).
public let gateway: String? public let gateway: String?
public init(_ destination: String, _ mask: String?, _ gateway: String?) { public init(_ destination: String, _ mask: String?, _ gateway: String?) {
self.destination = destination self.destination = destination
self.mask = mask ?? "255.255.255.255" self.mask = mask ?? "255.255.255.255"
self.gateway = gateway self.gateway = gateway
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
"{\(destination)/\(mask) \(gateway?.description ?? "*")}" "{\(destination)/\(mask) \(gateway?.description ?? "*")}"
} }
} }
/// The address. /// The address.
public let address: String public let address: String
/// The address mask. /// The address mask.
public let addressMask: String public let addressMask: String
/// The address of the default gateway. /// The address of the default gateway.
public let defaultGateway: String public let defaultGateway: String
/// The additional routes. /// The additional routes.
@available(*, deprecated, message: "Store routes separately") @available(*, deprecated, message: "Store routes separately")
public let routes: [Route] public let routes: [Route]
public init(address: String, addressMask: String, defaultGateway: String) { public init(address: String, addressMask: String, defaultGateway: String) {
self.address = address self.address = address
self.addressMask = addressMask self.addressMask = addressMask
@ -80,9 +80,9 @@ public struct IPv4Settings: Codable, Equatable, CustomStringConvertible {
self.defaultGateway = defaultGateway self.defaultGateway = defaultGateway
self.routes = routes self.routes = routes
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
"addr \(address) netmask \(addressMask) gw \(defaultGateway)" "addr \(address) netmask \(addressMask) gw \(defaultGateway)"
} }

View File

@ -27,45 +27,45 @@ import Foundation
/// Encapsulates the IPv6 settings for the tunnel. /// Encapsulates the IPv6 settings for the tunnel.
public struct IPv6Settings: Codable, Equatable, CustomStringConvertible { public struct IPv6Settings: Codable, Equatable, CustomStringConvertible {
/// Represents an IPv6 route in the routing table. /// Represents an IPv6 route in the routing table.
public struct Route: Codable, Hashable, CustomStringConvertible { public struct Route: Codable, Hashable, CustomStringConvertible {
/// The destination host or subnet. /// The destination host or subnet.
public let destination: String public let destination: String
/// The address prefix length. /// The address prefix length.
public let prefixLength: UInt8 public let prefixLength: UInt8
/// The address of the gateway (falls back to global gateway). /// The address of the gateway (falls back to global gateway).
public let gateway: String? public let gateway: String?
public init(_ destination: String, _ prefixLength: UInt8?, _ gateway: String?) { public init(_ destination: String, _ prefixLength: UInt8?, _ gateway: String?) {
self.destination = destination self.destination = destination
self.prefixLength = prefixLength ?? 3 self.prefixLength = prefixLength ?? 3
self.gateway = gateway self.gateway = gateway
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
"{\(destination.maskedDescription)/\(prefixLength) \(gateway?.maskedDescription ?? "*")}" "{\(destination.maskedDescription)/\(prefixLength) \(gateway?.maskedDescription ?? "*")}"
} }
} }
/// The address. /// The address.
public let address: String public let address: String
/// The address prefix length. /// The address prefix length.
public let addressPrefixLength: UInt8 public let addressPrefixLength: UInt8
/// The address of the default gateway. /// The address of the default gateway.
public let defaultGateway: String public let defaultGateway: String
/// The additional routes. /// The additional routes.
@available(*, deprecated, message: "Store routes separately") @available(*, deprecated, message: "Store routes separately")
public let routes: [Route] public let routes: [Route]
public init(address: String, addressPrefixLength: UInt8, defaultGateway: String) { public init(address: String, addressPrefixLength: UInt8, defaultGateway: String) {
self.address = address self.address = address
self.addressPrefixLength = addressPrefixLength self.addressPrefixLength = addressPrefixLength
@ -82,7 +82,7 @@ public struct IPv6Settings: Codable, Equatable, CustomStringConvertible {
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
"addr \(address)/\(addressPrefixLength) gw \(defaultGateway)" "addr \(address)/\(addressPrefixLength) gw \(defaultGateway)"
} }

View File

@ -38,7 +38,7 @@ import Foundation
/// Represents a specific I/O interface meant to work at the link layer (e.g. TCP/IP). /// Represents a specific I/O interface meant to work at the link layer (e.g. TCP/IP).
public protocol LinkInterface: IOInterface { public protocol LinkInterface: IOInterface {
/// When `true`, packets delivery is guaranteed. /// When `true`, packets delivery is guaranteed.
var isReliable: Bool { get } var isReliable: Bool { get }

View File

@ -27,24 +27,24 @@ import Foundation
/// Encapsulates a proxy setting. /// Encapsulates a proxy setting.
public struct Proxy: Codable, Equatable, RawRepresentable, CustomStringConvertible { public struct Proxy: Codable, Equatable, RawRepresentable, CustomStringConvertible {
/// The proxy address. /// The proxy address.
public let address: String public let address: String
/// The proxy port. /// The proxy port.
public let port: UInt16 public let port: UInt16
public init(_ address: String, _ port: UInt16) { public init(_ address: String, _ port: UInt16) {
self.address = address self.address = address
self.port = port self.port = port
} }
// MARK: RawRepresentable // MARK: RawRepresentable
public var rawValue: String { public var rawValue: String {
return "\(address):\(port)" return "\(address):\(port)"
} }
public init?(rawValue: String) { public init?(rawValue: String) {
let comps = rawValue.components(separatedBy: ":") let comps = rawValue.components(separatedBy: ":")
guard comps.count == 2, let port = UInt16(comps[1]) else { guard comps.count == 2, let port = UInt16(comps[1]) else {
@ -52,9 +52,9 @@ public struct Proxy: Codable, Equatable, RawRepresentable, CustomStringConvertib
} }
self.init(comps[0], port) self.init(comps[0], port)
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
return rawValue return rawValue
} }

View File

@ -64,10 +64,10 @@ public class SecureRandom {
} }
return randomNumber return randomNumber
} }
public static func uint32() throws -> UInt32 { public static func uint32() throws -> UInt32 {
var randomNumber: UInt32 = 0 var randomNumber: UInt32 = 0
try withUnsafeMutablePointer(to: &randomNumber) { try withUnsafeMutablePointer(to: &randomNumber) {
try $0.withMemoryRebound(to: UInt8.self, capacity: 4) { (randomBytes: UnsafeMutablePointer<UInt8>) -> Void in try $0.withMemoryRebound(to: UInt8.self, capacity: 4) { (randomBytes: UnsafeMutablePointer<UInt8>) -> Void in
guard SecRandomCopyBytes(kSecRandomDefault, 4, randomBytes) == 0 else { guard SecRandomCopyBytes(kSecRandomDefault, 4, randomBytes) == 0 else {
@ -75,7 +75,7 @@ public class SecureRandom {
} }
} }
} }
return randomNumber return randomNumber
} }
@ -88,7 +88,7 @@ public class SecureRandom {
throw SecureRandomError.randomGenerator throw SecureRandomError.randomGenerator
} }
} }
return randomData return randomData
} }
@ -99,7 +99,7 @@ public class SecureRandom {
bzero(randomBytes, length) bzero(randomBytes, length)
randomBytes.deallocate() randomBytes.deallocate()
} }
guard SecRandomCopyBytes(kSecRandomDefault, length, randomBytes) == 0 else { guard SecRandomCopyBytes(kSecRandomDefault, length, randomBytes) == 0 else {
throw SecureRandomError.randomGenerator throw SecureRandomError.randomGenerator
} }

View File

@ -27,7 +27,7 @@ import Foundation
/// Defines the basics of a VPN session. /// Defines the basics of a VPN session.
public protocol Session { public protocol Session {
/** /**
Establishes the link interface for this session. The interface must be up and running for sending and receiving packets. Establishes the link interface for this session. The interface must be up and running for sending and receiving packets.
@ -36,14 +36,14 @@ public protocol Session {
- Parameter link: The `LinkInterface` on which to establish the VPN session. - Parameter link: The `LinkInterface` on which to establish the VPN session.
*/ */
func setLink(_ link: LinkInterface) func setLink(_ link: LinkInterface)
/** /**
Returns `true` if the current session can rebind to a new link with `rebindLink(...)`. Returns `true` if the current session can rebind to a new link with `rebindLink(...)`.
- Returns: `true` if supports link rebinding. - Returns: `true` if supports link rebinding.
*/ */
func canRebindLink() -> Bool func canRebindLink() -> Bool
/** /**
Rebinds the session to a new link if supported. Rebinds the session to a new link if supported.
@ -53,7 +53,7 @@ public protocol Session {
- Seealso: `canRebindLink()` - Seealso: `canRebindLink()`
*/ */
func rebindLink(_ link: LinkInterface) func rebindLink(_ link: LinkInterface)
/** /**
Establishes the tunnel interface for this session. The interface must be up and running for sending and receiving packets. Establishes the tunnel interface for this session. The interface must be up and running for sending and receiving packets.
@ -62,14 +62,14 @@ public protocol Session {
- Parameter tunnel: The `TunnelInterface` on which to exchange the VPN data traffic. - Parameter tunnel: The `TunnelInterface` on which to exchange the VPN data traffic.
*/ */
func setTunnel(tunnel: TunnelInterface) func setTunnel(tunnel: TunnelInterface)
/** /**
Returns the current data bytes count. Returns the current data bytes count.
- Returns: The current data bytes count. - Returns: The current data bytes count.
*/ */
func dataCount() -> DataCount? func dataCount() -> DataCount?
/** /**
Returns the current server configuration. Returns the current server configuration.
@ -83,7 +83,7 @@ public protocol Session {
- Parameter error: An optional `Error` being the reason of the shutdown. - Parameter error: An optional `Error` being the reason of the shutdown.
*/ */
func shutdown(error: Error?) func shutdown(error: Error?)
/** /**
Shuts down the session with an optional `Error` reason and signals a reconnect flag to `OpenVPNSessionDelegate.sessionDidStop(...)`. Does nothing if the session is already stopped or about to stop. Shuts down the session with an optional `Error` reason and signals a reconnect flag to `OpenVPNSessionDelegate.sessionDidStop(...)`. Does nothing if the session is already stopped or about to stop.
@ -91,7 +91,7 @@ public protocol Session {
- Seealso: `OpenVPNSessionDelegate.sessionDidStop(...)` - Seealso: `OpenVPNSessionDelegate.sessionDidStop(...)`
*/ */
func reconnect(error: Error?) func reconnect(error: Error?)
/** /**
Cleans up the session resources. Cleans up the session resources.
*/ */

View File

@ -27,22 +27,22 @@ import Foundation
/// A socket type between UDP (recommended) and TCP. /// A socket type between UDP (recommended) and TCP.
public enum SocketType: String { public enum SocketType: String {
/// UDP socket type. /// UDP socket type.
case udp = "UDP" case udp = "UDP"
/// TCP socket type. /// TCP socket type.
case tcp = "TCP" case tcp = "TCP"
/// UDP socket type (IPv4). /// UDP socket type (IPv4).
case udp4 = "UDP4" case udp4 = "UDP4"
/// TCP socket type (IPv4). /// TCP socket type (IPv4).
case tcp4 = "TCP4" case tcp4 = "TCP4"
/// UDP socket type (IPv6). /// UDP socket type (IPv6).
case udp6 = "UDP6" case udp6 = "UDP6"
/// TCP socket type (IPv6). /// TCP socket type (IPv6).
case tcp6 = "TCP6" case tcp6 = "TCP6"
} }

View File

@ -61,9 +61,9 @@ public func Z(_ data: Data) -> ZeroingData {
return ZeroingData(data: data) return ZeroingData(data: data)
} }
//public func Z(_ data: Data, _ offset: Int, _ count: Int) -> ZeroingData { // public func Z(_ data: Data, _ offset: Int, _ count: Int) -> ZeroingData {
// return ZeroingData(data: data, offset: offset, count: count) // return ZeroingData(data: data, offset: offset, count: count)
//} // }
public func Z(_ string: String, nullTerminated: Bool) -> ZeroingData { public func Z(_ string: String, nullTerminated: Bool) -> ZeroingData {
return ZeroingData(string: string, nullTerminated: nullTerminated) return ZeroingData(string: string, nullTerminated: nullTerminated)

View File

@ -46,13 +46,13 @@ public enum KeychainError: Error {
/// Unable to add. /// Unable to add.
case add case add
/// Item not found. /// Item not found.
case notFound case notFound
/// Operation cancelled or unauthorized. /// Operation cancelled or unauthorized.
case userCancelled case userCancelled
// /// Unexpected item type returned. // /// Unexpected item type returned.
// case typeMismatch // case typeMismatch
} }
@ -70,9 +70,9 @@ public class Keychain {
public init(group: String?) { public init(group: String?) {
accessGroup = group accessGroup = group
} }
// MARK: Password // MARK: Password
/** /**
Sets a password. Sets a password.
@ -118,7 +118,7 @@ public class Keychain {
} }
return refData return refData
} }
/** /**
Removes a password. Removes a password.
@ -153,12 +153,12 @@ public class Keychain {
query[kSecAttrAccount as String] = username query[kSecAttrAccount as String] = username
query[kSecMatchLimit as String] = kSecMatchLimitOne query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecReturnData as String] = true query[kSecReturnData as String] = true
var result: AnyObject? var result: AnyObject?
switch SecItemCopyMatching(query as CFDictionary, &result) { switch SecItemCopyMatching(query as CFDictionary, &result) {
case errSecSuccess: case errSecSuccess:
break break
case errSecUserCanceled: case errSecUserCanceled:
throw KeychainError.userCancelled throw KeychainError.userCancelled
@ -190,12 +190,12 @@ public class Keychain {
query[kSecAttrAccount as String] = username query[kSecAttrAccount as String] = username
query[kSecMatchLimit as String] = kSecMatchLimitOne query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecReturnPersistentRef as String] = true query[kSecReturnPersistentRef as String] = true
var result: AnyObject? var result: AnyObject?
switch SecItemCopyMatching(query as CFDictionary, &result) { switch SecItemCopyMatching(query as CFDictionary, &result) {
case errSecSuccess: case errSecSuccess:
break break
case errSecUserCanceled: case errSecUserCanceled:
throw KeychainError.userCancelled throw KeychainError.userCancelled
@ -207,7 +207,7 @@ public class Keychain {
} }
return data return data
} }
/** /**
Gets a password associated with a password reference. Gets a password associated with a password reference.
@ -219,12 +219,12 @@ public class Keychain {
var query = [String: Any]() var query = [String: Any]()
query[kSecValuePersistentRef as String] = reference query[kSecValuePersistentRef as String] = reference
query[kSecReturnData as String] = true query[kSecReturnData as String] = true
var result: AnyObject? var result: AnyObject?
switch SecItemCopyMatching(query as CFDictionary, &result) { switch SecItemCopyMatching(query as CFDictionary, &result) {
case errSecSuccess: case errSecSuccess:
break break
case errSecUserCanceled: case errSecUserCanceled:
throw KeychainError.userCancelled throw KeychainError.userCancelled
@ -239,11 +239,11 @@ public class Keychain {
} }
return password return password
} }
// MARK: Key // MARK: Key
// https://forums.developer.apple.com/thread/13748 // https://forums.developer.apple.com/thread/13748
/** /**
Adds a public key. Adds a public key.
@ -269,7 +269,7 @@ public class Keychain {
} }
return try publicKey(withIdentifier: identifier) return try publicKey(withIdentifier: identifier)
} }
/** /**
Gets a public key. Gets a public key.
@ -292,7 +292,7 @@ public class Keychain {
switch SecItemCopyMatching(query as CFDictionary, &result) { switch SecItemCopyMatching(query as CFDictionary, &result) {
case errSecSuccess: case errSecSuccess:
break break
case errSecUserCanceled: case errSecUserCanceled:
throw KeychainError.userCancelled throw KeychainError.userCancelled
@ -305,7 +305,7 @@ public class Keychain {
// return key // return key
return result as! SecKey return result as! SecKey
} }
/** /**
Removes a public key. Removes a public key.
@ -325,9 +325,9 @@ public class Keychain {
let status = SecItemDelete(query as CFDictionary) let status = SecItemDelete(query as CFDictionary)
return status == errSecSuccess return status == errSecSuccess
} }
// MARK: Helpers // MARK: Helpers
public func setScope(query: inout [String: Any], context: String, userDefined: String?) { public func setScope(query: inout [String: Any], context: String, userDefined: String?) {
if let accessGroup = accessGroup { if let accessGroup = accessGroup {
query[kSecAttrAccessGroup as String] = accessGroup query[kSecAttrAccessGroup as String] = accessGroup

View File

@ -29,32 +29,32 @@ import NetworkExtension
/// Simulates a VPN provider. /// Simulates a VPN provider.
public class MockVPN: VPN { public class MockVPN: VPN {
private var tunnelBundleIdentifier: String? private var tunnelBundleIdentifier: String?
private var isEnabled: Bool { private var isEnabled: Bool {
didSet { didSet {
notifyReinstall(isEnabled) notifyReinstall(isEnabled)
} }
} }
private var vpnStatus: VPNStatus { private var vpnStatus: VPNStatus {
didSet { didSet {
notifyStatus(vpnStatus) notifyStatus(vpnStatus)
} }
} }
private let delayNanoseconds: UInt64 private let delayNanoseconds: UInt64
public init(delay: Int = 1) { public init(delay: Int = 1) {
delayNanoseconds = DispatchTimeInterval.seconds(delay).nanoseconds delayNanoseconds = DispatchTimeInterval.seconds(delay).nanoseconds
isEnabled = false isEnabled = false
vpnStatus = .disconnected vpnStatus = .disconnected
} }
// MARK: VPN // MARK: VPN
public func prepare() { public func prepare() {
} }
public func install( public func install(
_ tunnelBundleIdentifier: String, _ tunnelBundleIdentifier: String,
configuration: NetworkExtensionConfiguration, configuration: NetworkExtensionConfiguration,
@ -64,7 +64,7 @@ public class MockVPN: VPN {
isEnabled = true isEnabled = true
vpnStatus = .disconnected vpnStatus = .disconnected
} }
public func reconnect(after: DispatchTimeInterval) async throws { public func reconnect(after: DispatchTimeInterval) async throws {
if vpnStatus == .connected { if vpnStatus == .connected {
vpnStatus = .disconnecting vpnStatus = .disconnecting
@ -74,7 +74,7 @@ public class MockVPN: VPN {
await delay() await delay()
vpnStatus = .connected vpnStatus = .connected
} }
public func reconnect( public func reconnect(
_ tunnelBundleIdentifier: String, _ tunnelBundleIdentifier: String,
configuration: NetworkExtensionConfiguration, configuration: NetworkExtensionConfiguration,
@ -91,7 +91,7 @@ public class MockVPN: VPN {
await delay() await delay()
vpnStatus = .connected vpnStatus = .connected
} }
public func disconnect() async { public func disconnect() async {
guard vpnStatus != .disconnected else { guard vpnStatus != .disconnected else {
return return
@ -101,21 +101,21 @@ public class MockVPN: VPN {
vpnStatus = .disconnected vpnStatus = .disconnected
isEnabled = false isEnabled = false
} }
public func uninstall() async { public func uninstall() async {
vpnStatus = .disconnected vpnStatus = .disconnected
isEnabled = false isEnabled = false
} }
// MARK: Helpers // MARK: Helpers
private func notifyReinstall(_ isEnabled: Bool) { private func notifyReinstall(_ isEnabled: Bool) {
var notification = Notification(name: VPNNotification.didReinstall) var notification = Notification(name: VPNNotification.didReinstall)
notification.vpnBundleIdentifier = tunnelBundleIdentifier notification.vpnBundleIdentifier = tunnelBundleIdentifier
notification.vpnIsEnabled = isEnabled notification.vpnIsEnabled = isEnabled
NotificationCenter.default.post(notification) NotificationCenter.default.post(notification)
} }
private func notifyStatus(_ status: VPNStatus) { private func notifyStatus(_ status: VPNStatus) {
var notification = Notification(name: VPNNotification.didChangeStatus) var notification = Notification(name: VPNNotification.didChangeStatus)
notification.vpnBundleIdentifier = tunnelBundleIdentifier notification.vpnBundleIdentifier = tunnelBundleIdentifier

View File

@ -37,10 +37,10 @@ public struct NetworkExtensionExtra {
/// Disconnects on sleep if `true`. /// Disconnects on sleep if `true`.
public var disconnectsOnSleep = false public var disconnectsOnSleep = false
/// Enables best-effort kill switch. /// Enables best-effort kill switch.
public var killSwitch = false public var killSwitch = false
public init() { public init() {
} }
} }
@ -50,7 +50,7 @@ public protocol NetworkExtensionConfiguration {
/// The profile title in device settings. /// The profile title in device settings.
var title: String { get } var title: String { get }
/** /**
Returns a representation for use with tunnel implementations. Returns a representation for use with tunnel implementations.

View File

@ -44,13 +44,13 @@ public class NetworkExtensionVPN: VPN {
deinit { deinit {
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
} }
// MARK: Public // MARK: Public
public func prepare() async { public func prepare() async {
_ = try? await NETunnelProviderManager.loadAllFromPreferences() _ = try? await NETunnelProviderManager.loadAllFromPreferences()
} }
public func install( public func install(
_ tunnelBundleIdentifier: String, _ tunnelBundleIdentifier: String,
configuration: NetworkExtensionConfiguration, configuration: NetworkExtensionConfiguration,
@ -62,7 +62,7 @@ public class NetworkExtensionVPN: VPN {
extra: extra extra: extra
) )
} }
public func reconnect(after: DispatchTimeInterval) async throws { public func reconnect(after: DispatchTimeInterval) async throws {
let managers = try await lookupAll() let managers = try await lookupAll()
guard let manager = managers.first else { guard let manager = managers.first else {
@ -97,7 +97,7 @@ public class NetworkExtensionVPN: VPN {
throw error throw error
} }
} }
public func disconnect() async { public func disconnect() async {
guard let managers = try? await lookupAll() else { guard let managers = try? await lookupAll() else {
return return
@ -112,7 +112,7 @@ public class NetworkExtensionVPN: VPN {
try? await m.saveToPreferences() try? await m.saveToPreferences()
} }
} }
public func uninstall() async { public func uninstall() async {
guard let managers = try? await lookupAll() else { guard let managers = try? await lookupAll() else {
return return
@ -156,10 +156,10 @@ public class NetworkExtensionVPN: VPN {
await retainManagers(managers) { await retainManagers(managers) {
$0.isTunnel(withIdentifier: tunnelBundleIdentifier) $0.isTunnel(withIdentifier: tunnelBundleIdentifier)
} }
return targetManager return targetManager
} }
@discardableResult @discardableResult
private func install( private func install(
_ manager: NETunnelProviderManager, _ manager: NETunnelProviderManager,
@ -202,11 +202,11 @@ public class NetworkExtensionVPN: VPN {
try? await o.removeFromPreferences() try? await o.removeFromPreferences()
} }
} }
private func lookupAll() async throws -> [NETunnelProviderManager] { private func lookupAll() async throws -> [NETunnelProviderManager] {
try await NETunnelProviderManager.loadAllFromPreferences() try await NETunnelProviderManager.loadAllFromPreferences()
} }
// MARK: Notifications // MARK: Notifications
@objc private func vpnDidUpdate(_ notification: Notification) { @objc private func vpnDidUpdate(_ notification: Notification) {
@ -222,7 +222,7 @@ public class NetworkExtensionVPN: VPN {
} }
notifyReinstall(manager) notifyReinstall(manager)
} }
private func notifyReinstall(_ manager: NETunnelProviderManager) { private func notifyReinstall(_ manager: NETunnelProviderManager) {
let bundleId = manager.tunnelBundleIdentifier let bundleId = manager.tunnelBundleIdentifier
log.debug("VPN did reinstall (\(bundleId ?? "?")): isEnabled=\(manager.isEnabled)") log.debug("VPN did reinstall (\(bundleId ?? "?")): isEnabled=\(manager.isEnabled)")
@ -232,7 +232,7 @@ public class NetworkExtensionVPN: VPN {
notification.vpnIsEnabled = manager.isEnabled notification.vpnIsEnabled = manager.isEnabled
NotificationCenter.default.post(notification) NotificationCenter.default.post(notification)
} }
private func notifyStatus(_ connection: NETunnelProviderSession) { private func notifyStatus(_ connection: NETunnelProviderSession) {
guard let _ = connection.manager.localizedDescription else { guard let _ = connection.manager.localizedDescription else {
log.verbose("Ignoring VPN notification from bogus manager") log.verbose("Ignoring VPN notification from bogus manager")
@ -247,7 +247,7 @@ public class NetworkExtensionVPN: VPN {
notification.vpnStatus = connection.status.wrappedStatus notification.vpnStatus = connection.status.wrappedStatus
NotificationCenter.default.post(notification) NotificationCenter.default.post(notification)
} }
private func notifyInstallError(_ error: Error) { private func notifyInstallError(_ error: Error) {
log.error("VPN installation failed: \(error))") log.error("VPN installation failed: \(error))")
@ -265,7 +265,7 @@ private extension NEVPNManager {
} }
return proto.providerBundleIdentifier return proto.providerBundleIdentifier
} }
func isTunnel(withIdentifier bundleIdentifier: String) -> Bool { func isTunnel(withIdentifier bundleIdentifier: String) -> Bool {
return tunnelBundleIdentifier == bundleIdentifier return tunnelBundleIdentifier == bundleIdentifier
} }
@ -276,13 +276,13 @@ private extension NEVPNStatus {
switch self { switch self {
case .connected: case .connected:
return .connected return .connected
case .connecting, .reasserting: case .connecting, .reasserting:
return .connecting return .connecting
case .disconnecting: case .disconnecting:
return .disconnecting return .disconnecting
case .disconnected, .invalid: case .disconnected, .invalid:
return .disconnected return .disconnected

View File

@ -28,14 +28,14 @@ import Foundation
/// Helps controlling a VPN without messing with underlying implementations. /// Helps controlling a VPN without messing with underlying implementations.
public protocol VPN { public protocol VPN {
associatedtype Configuration associatedtype Configuration
associatedtype Extra associatedtype Extra
/** /**
Synchronizes with the current VPN state. Synchronizes with the current VPN state.
*/ */
func prepare() async func prepare() async
/** /**
Installs the VPN profile. Installs the VPN profile.
@ -72,12 +72,12 @@ public protocol VPN {
extra: Extra?, extra: Extra?,
after: DispatchTimeInterval after: DispatchTimeInterval
) async throws ) async throws
/** /**
Disconnects from the VPN. Disconnects from the VPN.
*/ */
func disconnect() async func disconnect() async
/** /**
Uninstalls the VPN profile. Uninstalls the VPN profile.
*/ */
@ -91,19 +91,19 @@ extension DispatchTimeInterval {
switch self { switch self {
case .seconds(let sec): case .seconds(let sec):
return UInt64(sec) * NSEC_PER_SEC return UInt64(sec) * NSEC_PER_SEC
case .milliseconds(let msec): case .milliseconds(let msec):
return UInt64(msec) * NSEC_PER_MSEC return UInt64(msec) * NSEC_PER_MSEC
case .microseconds(let usec): case .microseconds(let usec):
return UInt64(usec) * NSEC_PER_USEC return UInt64(usec) * NSEC_PER_USEC
case .nanoseconds(let nsec): case .nanoseconds(let nsec):
return UInt64(nsec) return UInt64(nsec)
case .never: case .never:
return 0 return 0
@unknown default: @unknown default:
return 0 return 0
} }

View File

@ -30,13 +30,13 @@ public enum VPNStatus: String {
/// VPN is connected. /// VPN is connected.
case connected case connected
/// VPN is attempting a connection. /// VPN is attempting a connection.
case connecting case connecting
/// VPN is disconnected. /// VPN is disconnected.
case disconnected case disconnected
/// VPN is completing a disconnection. /// VPN is completing a disconnection.
case disconnecting case disconnecting
} }

View File

@ -48,7 +48,7 @@ class ConnectionStrategy {
private var remotes: [ResolvedRemote] private var remotes: [ResolvedRemote]
private var currentRemoteIndex: Int private var currentRemoteIndex: Int
var currentRemote: ResolvedRemote? { var currentRemote: ResolvedRemote? {
guard currentRemoteIndex < remotes.count else { guard currentRemoteIndex < remotes.count else {
return nil return nil
@ -89,13 +89,12 @@ class ConnectionStrategy {
} }
return true return true
} }
func createSocket( func createSocket(
from provider: NEProvider, from provider: NEProvider,
timeout: Int, timeout: Int,
queue: DispatchQueue, queue: DispatchQueue,
completionHandler: @escaping (Result<GenericSocket, OpenVPNProviderError>) -> Void) completionHandler: @escaping (Result<GenericSocket, OpenVPNProviderError>) -> Void) {
{
guard let remote = currentRemote else { guard let remote = currentRemote else {
completionHandler(.failure(.exhaustedEndpoints)) completionHandler(.failure(.exhaustedEndpoints))
return return
@ -130,7 +129,7 @@ private extension NEProvider {
case .udp, .udp4, .udp6: case .udp, .udp4, .udp6:
let impl = createUDPSession(to: ep, from: nil) let impl = createUDPSession(to: ep, from: nil)
return NEUDPSocket(impl: impl) return NEUDPSocket(impl: impl)
case .tcp, .tcp4, .tcp6: case .tcp, .tcp4, .tcp6:
let impl = createTCPConnection(to: ep, enableTLS: false, tlsParameters: nil, delegate: nil) let impl = createTCPConnection(to: ep, enableTLS: false, tlsParameters: nil, delegate: nil)
return NETCPSocket(impl: impl) return NETCPSocket(impl: impl)

View File

@ -32,7 +32,7 @@ import CTunnelKitOpenVPNProtocol
class NETCPLink: LinkInterface { class NETCPLink: LinkInterface {
private let impl: NWTCPConnection private let impl: NWTCPConnection
private let maxPacketSize: Int private let maxPacketSize: Int
private let xorMethod: OpenVPN.XORMethod? private let xorMethod: OpenVPN.XORMethod?
@ -45,15 +45,15 @@ class NETCPLink: LinkInterface {
self.xorMethod = xorMethod self.xorMethod = xorMethod
xorMask = xorMethod?.mask xorMask = xorMethod?.mask
} }
// MARK: LinkInterface // MARK: LinkInterface
let isReliable: Bool = true let isReliable: Bool = true
var remoteAddress: String? { var remoteAddress: String? {
(impl.remoteAddress as? NWHostEndpoint)?.hostname (impl.remoteAddress as? NWHostEndpoint)?.hostname
} }
var remoteProtocol: String? { var remoteProtocol: String? {
guard let remote = impl.remoteAddress as? NWHostEndpoint else { guard let remote = impl.remoteAddress as? NWHostEndpoint else {
return nil return nil
@ -64,13 +64,13 @@ class NETCPLink: LinkInterface {
var packetBufferSize: Int { var packetBufferSize: Int {
return maxPacketSize return maxPacketSize
} }
func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) {
loopReadPackets(queue, Data(), handler) loopReadPackets(queue, Data(), handler)
} }
private func loopReadPackets(_ queue: DispatchQueue, _ buffer: Data, _ handler: @escaping ([Data]?, Error?) -> Void) { private func loopReadPackets(_ queue: DispatchQueue, _ buffer: Data, _ handler: @escaping ([Data]?, Error?) -> Void) {
// WARNING: runs in Network.framework queue // WARNING: runs in Network.framework queue
impl.readMinimumLength(2, maximumLength: packetBufferSize) { [weak self] (data, error) in impl.readMinimumLength(2, maximumLength: packetBufferSize) { [weak self] (data, error) in
guard let self = self else { guard let self = self else {
@ -81,7 +81,7 @@ class NETCPLink: LinkInterface {
handler(nil, error) handler(nil, error)
return return
} }
var newBuffer = buffer var newBuffer = buffer
newBuffer.append(contentsOf: data) newBuffer.append(contentsOf: data)
var until = 0 var until = 0
@ -93,12 +93,12 @@ class NETCPLink: LinkInterface {
) )
newBuffer = newBuffer.subdata(in: until..<newBuffer.count) newBuffer = newBuffer.subdata(in: until..<newBuffer.count)
self.loopReadPackets(queue, newBuffer, handler) self.loopReadPackets(queue, newBuffer, handler)
handler(packets, nil) handler(packets, nil)
} }
} }
} }
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
let stream = PacketStream.outboundStream( let stream = PacketStream.outboundStream(
fromPacket: packet, fromPacket: packet,
@ -109,7 +109,7 @@ class NETCPLink: LinkInterface {
completionHandler?(error) completionHandler?(error)
} }
} }
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) {
let stream = PacketStream.outboundStream( let stream = PacketStream.outboundStream(
fromPackets: packets, fromPackets: packets,

View File

@ -32,9 +32,9 @@ import TunnelKitOpenVPNProtocol
class NEUDPLink: LinkInterface { class NEUDPLink: LinkInterface {
private let impl: NWUDPSession private let impl: NWUDPSession
private let maxDatagrams: Int private let maxDatagrams: Int
private let xor: XORProcessor private let xor: XORProcessor
init(impl: NWUDPSession, maxDatagrams: Int? = nil, xorMethod: OpenVPN.XORMethod?) { init(impl: NWUDPSession, maxDatagrams: Int? = nil, xorMethod: OpenVPN.XORMethod?) {
@ -42,15 +42,15 @@ class NEUDPLink: LinkInterface {
self.maxDatagrams = maxDatagrams ?? 200 self.maxDatagrams = maxDatagrams ?? 200
xor = XORProcessor(method: xorMethod) xor = XORProcessor(method: xorMethod)
} }
// MARK: LinkInterface // MARK: LinkInterface
let isReliable: Bool = false let isReliable: Bool = false
var remoteAddress: String? { var remoteAddress: String? {
(impl.resolvedEndpoint as? NWHostEndpoint)?.hostname (impl.resolvedEndpoint as? NWHostEndpoint)?.hostname
} }
var remoteProtocol: String? { var remoteProtocol: String? {
guard let remote = impl.resolvedEndpoint as? NWHostEndpoint else { guard let remote = impl.resolvedEndpoint as? NWHostEndpoint else {
return nil return nil
@ -61,9 +61,9 @@ class NEUDPLink: LinkInterface {
var packetBufferSize: Int { var packetBufferSize: Int {
return maxDatagrams return maxDatagrams
} }
func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) {
// WARNING: runs in Network.framework queue // WARNING: runs in Network.framework queue
impl.setReadHandler({ [weak self] packets, error in impl.setReadHandler({ [weak self] packets, error in
guard let self = self else { guard let self = self else {
@ -78,14 +78,14 @@ class NEUDPLink: LinkInterface {
} }
}, maxDatagrams: maxDatagrams) }, maxDatagrams: maxDatagrams)
} }
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
let dataToUse = xor.processPacket(packet, outbound: true) let dataToUse = xor.processPacket(packet, outbound: true)
impl.writeDatagram(dataToUse) { error in impl.writeDatagram(dataToUse) { error in
completionHandler?(error) completionHandler?(error)
} }
} }
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) {
let packetsToUse = xor.processPackets(packets, outbound: true) let packetsToUse = xor.processPackets(packets, outbound: true)
impl.writeMultipleDatagrams(packetsToUse) { error in impl.writeMultipleDatagrams(packetsToUse) { error in

View File

@ -33,17 +33,17 @@ private let log = SwiftyBeaver.self
struct NetworkSettingsBuilder { struct NetworkSettingsBuilder {
let remoteAddress: String let remoteAddress: String
let localOptions: OpenVPN.Configuration let localOptions: OpenVPN.Configuration
let remoteOptions: OpenVPN.Configuration let remoteOptions: OpenVPN.Configuration
init(remoteAddress: String, localOptions: OpenVPN.Configuration, remoteOptions: OpenVPN.Configuration) { init(remoteAddress: String, localOptions: OpenVPN.Configuration, remoteOptions: OpenVPN.Configuration) {
self.remoteAddress = remoteAddress self.remoteAddress = remoteAddress
self.localOptions = localOptions self.localOptions = localOptions
self.remoteOptions = remoteOptions self.remoteOptions = remoteOptions
} }
func build() -> NEPacketTunnelNetworkSettings { func build() -> NEPacketTunnelNetworkSettings {
let ipv4Settings = computedIPv4Settings let ipv4Settings = computedIPv4Settings
let ipv6Settings = computedIPv6Settings let ipv6Settings = computedIPv6Settings
@ -91,19 +91,19 @@ extension NetworkSettingsBuilder {
var isGateway: Bool { var isGateway: Bool {
isIPv4Gateway || isIPv6Gateway isIPv4Gateway || isIPv6Gateway
} }
private var routingPolicies: [OpenVPN.RoutingPolicy]? { private var routingPolicies: [OpenVPN.RoutingPolicy]? {
pullRoutes ? (remoteOptions.routingPolicies ?? localOptions.routingPolicies) : localOptions.routingPolicies pullRoutes ? (remoteOptions.routingPolicies ?? localOptions.routingPolicies) : localOptions.routingPolicies
} }
private var isIPv4Gateway: Bool { private var isIPv4Gateway: Bool {
routingPolicies?.contains(.IPv4) ?? false routingPolicies?.contains(.IPv4) ?? false
} }
private var isIPv6Gateway: Bool { private var isIPv6Gateway: Bool {
routingPolicies?.contains(.IPv6) ?? false routingPolicies?.contains(.IPv6) ?? false
} }
private var allRoutes4: [IPv4Settings.Route] { private var allRoutes4: [IPv4Settings.Route] {
var routes = localOptions.routes4 ?? [] var routes = localOptions.routes4 ?? []
if pullRoutes, let remoteRoutes = remoteOptions.routes4 { if pullRoutes, let remoteRoutes = remoteOptions.routes4 {
@ -111,7 +111,7 @@ extension NetworkSettingsBuilder {
} }
return routes return routes
} }
private var allRoutes6: [IPv6Settings.Route] { private var allRoutes6: [IPv6Settings.Route] {
var routes = localOptions.routes6 ?? [] var routes = localOptions.routes6 ?? []
if pullRoutes, let remoteRoutes = remoteOptions.routes6 { if pullRoutes, let remoteRoutes = remoteOptions.routes6 {
@ -119,7 +119,7 @@ extension NetworkSettingsBuilder {
} }
return routes return routes
} }
private var allDNSServers: [String] { private var allDNSServers: [String] {
var servers = localOptions.dnsServers ?? [] var servers = localOptions.dnsServers ?? []
if pullDNS, let remoteServers = remoteOptions.dnsServers { if pullDNS, let remoteServers = remoteOptions.dnsServers {
@ -127,7 +127,7 @@ extension NetworkSettingsBuilder {
} }
return servers return servers
} }
private var dnsDomain: String? { private var dnsDomain: String? {
var domain = localOptions.dnsDomain var domain = localOptions.dnsDomain
if pullDNS, let remoteDomain = remoteOptions.dnsDomain { if pullDNS, let remoteDomain = remoteOptions.dnsDomain {
@ -143,7 +143,7 @@ extension NetworkSettingsBuilder {
} }
return searchDomains return searchDomains
} }
private var allProxyBypassDomains: [String] { private var allProxyBypassDomains: [String] {
var bypass = localOptions.proxyBypassDomains ?? [] var bypass = localOptions.proxyBypassDomains ?? []
if pullProxy, let remoteBypass = remoteOptions.proxyBypassDomains { if pullProxy, let remoteBypass = remoteOptions.proxyBypassDomains {
@ -164,7 +164,7 @@ extension NetworkSettingsBuilder {
} }
let ipv4Settings = NEIPv4Settings(addresses: [ipv4.address], subnetMasks: [ipv4.addressMask]) let ipv4Settings = NEIPv4Settings(addresses: [ipv4.address], subnetMasks: [ipv4.addressMask])
var neRoutes: [NEIPv4Route] = [] var neRoutes: [NEIPv4Route] = []
// route all traffic to VPN? // route all traffic to VPN?
if isIPv4Gateway { if isIPv4Gateway {
let defaultRoute = NEIPv4Route.default() let defaultRoute = NEIPv4Route.default()
@ -172,7 +172,7 @@ extension NetworkSettingsBuilder {
neRoutes.append(defaultRoute) neRoutes.append(defaultRoute)
log.info("Routing.IPv4: Setting default gateway to \(ipv4.defaultGateway)") log.info("Routing.IPv4: Setting default gateway to \(ipv4.defaultGateway)")
} }
for r in allRoutes4 { for r in allRoutes4 {
let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask) let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask)
let gw = r.gateway ?? ipv4.defaultGateway let gw = r.gateway ?? ipv4.defaultGateway
@ -185,14 +185,14 @@ extension NetworkSettingsBuilder {
ipv4Settings.excludedRoutes = [] ipv4Settings.excludedRoutes = []
return ipv4Settings return ipv4Settings
} }
private var computedIPv6Settings: NEIPv6Settings? { private var computedIPv6Settings: NEIPv6Settings? {
guard let ipv6 = remoteOptions.ipv6 else { guard let ipv6 = remoteOptions.ipv6 else {
return nil return nil
} }
let ipv6Settings = NEIPv6Settings(addresses: [ipv6.address], networkPrefixLengths: [ipv6.addressPrefixLength as NSNumber]) let ipv6Settings = NEIPv6Settings(addresses: [ipv6.address], networkPrefixLengths: [ipv6.addressPrefixLength as NSNumber])
var neRoutes: [NEIPv6Route] = [] var neRoutes: [NEIPv6Route] = []
// route all traffic to VPN? // route all traffic to VPN?
if isIPv6Gateway { if isIPv6Gateway {
let defaultRoute = NEIPv6Route.default() let defaultRoute = NEIPv6Route.default()
@ -200,7 +200,7 @@ extension NetworkSettingsBuilder {
neRoutes.append(defaultRoute) neRoutes.append(defaultRoute)
log.info("Routing.IPv6: Setting default gateway to \(ipv6.defaultGateway)") log.info("Routing.IPv6: Setting default gateway to \(ipv6.defaultGateway)")
} }
for r in allRoutes6 { for r in allRoutes6 {
let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber) let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber)
let gw = r.gateway ?? ipv6.defaultGateway let gw = r.gateway ?? ipv6.defaultGateway
@ -213,7 +213,7 @@ extension NetworkSettingsBuilder {
ipv6Settings.excludedRoutes = [] ipv6Settings.excludedRoutes = []
return ipv6Settings return ipv6Settings
} }
var hasGateway: Bool { var hasGateway: Bool {
var hasGateway = false var hasGateway = false
if isIPv4Gateway && remoteOptions.ipv4 != nil { if isIPv4Gateway && remoteOptions.ipv4 != nil {
@ -258,7 +258,7 @@ extension NetworkSettingsBuilder {
default: default:
break break
} }
// fall back // fall back
if dnsSettings == nil { if dnsSettings == nil {
let dnsServers = allDNSServers let dnsServers = allDNSServers
@ -275,7 +275,7 @@ extension NetworkSettingsBuilder {
} }
} }
} }
// "hack" for split DNS (i.e. use VPN only for DNS) // "hack" for split DNS (i.e. use VPN only for DNS)
if !isGateway { if !isGateway {
dnsSettings?.matchDomains = [""] dnsSettings?.matchDomains = [""]
@ -294,7 +294,7 @@ extension NetworkSettingsBuilder {
dnsSettings?.matchDomains = dnsSettings?.searchDomains dnsSettings?.matchDomains = dnsSettings?.searchDomains
} }
} }
return dnsSettings return dnsSettings
} }
} }
@ -327,7 +327,7 @@ extension NetworkSettingsBuilder {
proxySettings?.autoProxyConfigurationEnabled = true proxySettings?.autoProxyConfigurationEnabled = true
log.info("Routing: Setting PAC \(pacURL)") log.info("Routing: Setting PAC \(pacURL)")
} }
// only set if there is a proxy (proxySettings set to non-nil above) // only set if there is a proxy (proxySettings set to non-nil above)
if proxySettings != nil { if proxySettings != nil {
let bypass = allProxyBypassDomains let bypass = allProxyBypassDomains

View File

@ -57,39 +57,39 @@ private let log = SwiftyBeaver.self
Packet Tunnel Provider extension both on iOS and macOS. Packet Tunnel Provider extension both on iOS and macOS.
*/ */
open class OpenVPNTunnelProvider: NEPacketTunnelProvider { open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
// MARK: Tweaks // MARK: Tweaks
/// An optional string describing host app version on tunnel start. /// An optional string describing host app version on tunnel start.
public var appVersion: String? public var appVersion: String?
/// The log separator between sessions. /// The log separator between sessions.
public var logSeparator = "--- EOF ---" public var logSeparator = "--- EOF ---"
/// The maximum size of the log. /// The maximum size of the log.
public var maxLogSize = 20000 public var maxLogSize = 20000
/// The log level when `OpenVPNTunnelProvider.Configuration.shouldDebug` is enabled. /// The log level when `OpenVPNTunnelProvider.Configuration.shouldDebug` is enabled.
public var debugLogLevel: SwiftyBeaver.Level = .debug public var debugLogLevel: SwiftyBeaver.Level = .debug
/// The number of milliseconds after which a DNS resolution fails. /// The number of milliseconds after which a DNS resolution fails.
public var dnsTimeout = 3000 public var dnsTimeout = 3000
/// The number of milliseconds after which the tunnel gives up on a connection attempt. /// The number of milliseconds after which the tunnel gives up on a connection attempt.
public var socketTimeout = 5000 public var socketTimeout = 5000
/// The number of milliseconds after which the tunnel is shut down forcibly. /// The number of milliseconds after which the tunnel is shut down forcibly.
public var shutdownTimeout = 2000 public var shutdownTimeout = 2000
/// The number of milliseconds after which a reconnection attempt is issued. /// The number of milliseconds after which a reconnection attempt is issued.
public var reconnectionDelay = 1000 public var reconnectionDelay = 1000
/// The number of link failures after which the tunnel is expected to die. /// The number of link failures after which the tunnel is expected to die.
public var maxLinkFailures = 3 public var maxLinkFailures = 3
/// The number of milliseconds between data count updates. Set to 0 to disable updates (default). /// The number of milliseconds between data count updates. Set to 0 to disable updates (default).
public var dataCountInterval = 0 public var dataCountInterval = 0
/// A list of public DNS servers to use as fallback when none are provided (defaults to CloudFlare). /// A list of public DNS servers to use as fallback when none are provided (defaults to CloudFlare).
public var fallbackDNSServers = [ public var fallbackDNSServers = [
"1.1.1.1", "1.1.1.1",
@ -97,13 +97,13 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
"2606:4700:4700::1111", "2606:4700:4700::1111",
"2606:4700:4700::1001" "2606:4700:4700::1001"
] ]
// MARK: Constants // MARK: Constants
private let tunnelQueue = DispatchQueue(label: OpenVPNTunnelProvider.description(), qos: .utility) private let tunnelQueue = DispatchQueue(label: OpenVPNTunnelProvider.description(), qos: .utility)
private let prngSeedLength = 64 private let prngSeedLength = 64
private var cachesURL: URL { private var cachesURL: URL {
let appGroup = cfg.appGroup let appGroup = cfg.appGroup
guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
@ -115,32 +115,32 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
// MARK: Tunnel configuration // MARK: Tunnel configuration
private var cfg: OpenVPN.ProviderConfiguration! private var cfg: OpenVPN.ProviderConfiguration!
private var strategy: ConnectionStrategy! private var strategy: ConnectionStrategy!
// MARK: Internal state // MARK: Internal state
private var session: OpenVPNSession? private var session: OpenVPNSession?
private var socket: GenericSocket? private var socket: GenericSocket?
private var pendingStartHandler: ((Error?) -> Void)? private var pendingStartHandler: ((Error?) -> Void)?
private var pendingStopHandler: (() -> Void)? private var pendingStopHandler: (() -> Void)?
private var isCountingData = false private var isCountingData = false
private var shouldReconnect = false private var shouldReconnect = false
// MARK: NEPacketTunnelProvider (XPC queue) // MARK: NEPacketTunnelProvider (XPC queue)
open override var reasserting: Bool { open override var reasserting: Bool {
didSet { didSet {
log.debug("Reasserting flag \(reasserting ? "set" : "cleared")") log.debug("Reasserting flag \(reasserting ? "set" : "cleared")")
} }
} }
open override func startTunnel(options: [String : NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) { open override func startTunnel(options: [String: NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) {
// required configuration // required configuration
do { do {
@ -160,7 +160,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
switch te { switch te {
case .parameter(let name): case .parameter(let name):
message = "Tunnel configuration incomplete: \(name)" message = "Tunnel configuration incomplete: \(name)"
default: default:
break break
} }
@ -198,7 +198,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
log.info("Starting tunnel...") log.info("Starting tunnel...")
cfg._appexSetLastError(nil) cfg._appexSetLastError(nil)
guard OpenVPN.prepareRandomNumberGenerator(seedLength: prngSeedLength) else { guard OpenVPN.prepareRandomNumberGenerator(seedLength: prngSeedLength) else {
completionHandler(OpenVPNProviderConfigurationError.prngInitialization) completionHandler(OpenVPNProviderConfigurationError.prngInitialization)
return return
@ -231,7 +231,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
self.connectTunnel() self.connectTunnel()
} }
} }
open override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { open override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
pendingStartHandler = nil pendingStartHandler = nil
log.info("Stopping tunnel...") log.info("Stopping tunnel...")
@ -261,35 +261,35 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
session.shutdown(error: nil) session.shutdown(error: nil)
} }
} }
// MARK: Wake/Sleep (debugging placeholders) // MARK: Wake/Sleep (debugging placeholders)
open override func wake() { open override func wake() {
log.verbose("Wake signal received") log.verbose("Wake signal received")
} }
open override func sleep(completionHandler: @escaping () -> Void) { open override func sleep(completionHandler: @escaping () -> Void) {
log.verbose("Sleep signal received") log.verbose("Sleep signal received")
completionHandler() completionHandler()
} }
// MARK: Connection (tunnel queue) // MARK: Connection (tunnel queue)
private func connectTunnel(upgradedSocket: GenericSocket? = nil) { private func connectTunnel(upgradedSocket: GenericSocket? = nil) {
log.info("Creating link session") log.info("Creating link session")
// reuse upgraded socket // reuse upgraded socket
if let upgradedSocket = upgradedSocket, !upgradedSocket.isShutdown { if let upgradedSocket = upgradedSocket, !upgradedSocket.isShutdown {
log.debug("Socket follows a path upgrade") log.debug("Socket follows a path upgrade")
connectTunnel(via: upgradedSocket) connectTunnel(via: upgradedSocket)
return return
} }
strategy.createSocket(from: self, timeout: dnsTimeout, queue: tunnelQueue) { strategy.createSocket(from: self, timeout: dnsTimeout, queue: tunnelQueue) {
switch $0 { switch $0 {
case .success(let socket): case .success(let socket):
self.connectTunnel(via: socket) self.connectTunnel(via: socket)
case .failure(let error): case .failure(let error):
if case .dnsFailure = error { if case .dnsFailure = error {
self.tunnelQueue.async { self.tunnelQueue.async {
@ -302,7 +302,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
} }
} }
} }
private func connectTunnel(via socket: GenericSocket) { private func connectTunnel(via socket: GenericSocket) {
log.info("Will connect to \(socket)") log.info("Will connect to \(socket)")
cfg._appexSetLastError(nil) cfg._appexSetLastError(nil)
@ -312,16 +312,16 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
self.socket?.delegate = self self.socket?.delegate = self
self.socket?.observe(queue: tunnelQueue, activeTimeout: socketTimeout) self.socket?.observe(queue: tunnelQueue, activeTimeout: socketTimeout)
} }
private func finishTunnelDisconnection(error: Error?) { private func finishTunnelDisconnection(error: Error?) {
if let session = session, !(shouldReconnect && session.canRebindLink()) { if let session = session, !(shouldReconnect && session.canRebindLink()) {
session.cleanup() session.cleanup()
} }
socket?.delegate = nil socket?.delegate = nil
socket?.unobserve() socket?.unobserve()
socket = nil socket = nil
if let error = error { if let error = error {
log.error("Tunnel did stop (error: \(error))") log.error("Tunnel did stop (error: \(error))")
setErrorStatus(with: error) setErrorStatus(with: error)
@ -329,7 +329,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
log.info("Tunnel did stop on request") log.info("Tunnel did stop on request")
} }
} }
private func disposeTunnel(error: Error?) { private func disposeTunnel(error: Error?) {
log.info("Dispose tunnel in \(reconnectionDelay) milliseconds...") log.info("Dispose tunnel in \(reconnectionDelay) milliseconds...")
tunnelQueue.asyncAfter(deadline: .now() + .milliseconds(reconnectionDelay)) { [weak self] in tunnelQueue.asyncAfter(deadline: .now() + .milliseconds(reconnectionDelay)) { [weak self] in
@ -342,7 +342,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
// failed to start // failed to start
if pendingStartHandler != nil { if pendingStartHandler != nil {
// //
// CAUTION // CAUTION
// //
@ -374,7 +374,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
forceExitOnMac() forceExitOnMac()
} }
} }
// MARK: Data counter (tunnel queue) // MARK: Data counter (tunnel queue)
private func refreshDataCount() { private func refreshDataCount() {
@ -393,9 +393,9 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
} }
extension OpenVPNTunnelProvider: GenericSocketDelegate { extension OpenVPNTunnelProvider: GenericSocketDelegate {
// MARK: GenericSocketDelegate (tunnel queue) // MARK: GenericSocketDelegate (tunnel queue)
public func socketDidTimeout(_ socket: GenericSocket) { public func socketDidTimeout(_ socket: GenericSocket) {
log.debug("Socket timed out waiting for activity, cancelling...") log.debug("Socket timed out waiting for activity, cancelling...")
shouldReconnect = true shouldReconnect = true
@ -409,7 +409,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
} }
} }
} }
public func socketDidBecomeActive(_ socket: GenericSocket) { public func socketDidBecomeActive(_ socket: GenericSocket) {
guard let session = session, let producer = socket as? LinkProducer else { guard let session = session, let producer = socket as? LinkProducer else {
return return
@ -421,12 +421,12 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
session.setLink(producer.link(userObject: cfg.configuration.xorMethod)) session.setLink(producer.link(userObject: cfg.configuration.xorMethod))
} }
} }
public func socket(_ socket: GenericSocket, didShutdownWithFailure failure: Bool) { public func socket(_ socket: GenericSocket, didShutdownWithFailure failure: Bool) {
guard let session = session else { guard let session = session else {
return return
} }
var shutdownError: Error? var shutdownError: Error?
let didTimeoutNegotiation: Bool let didTimeoutNegotiation: Bool
var upgradedSocket: GenericSocket? var upgradedSocket: GenericSocket?
@ -437,7 +437,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
shutdownError = OpenVPNProviderError.linkError shutdownError = OpenVPNProviderError.linkError
} }
didTimeoutNegotiation = (shutdownError as? OpenVPNError == .negotiationTimeout) didTimeoutNegotiation = (shutdownError as? OpenVPNError == .negotiationTimeout)
// only try upgrade on network errors // only try upgrade on network errors
if shutdownError as? OpenVPNError == nil { if shutdownError as? OpenVPNError == nil {
upgradedSocket = socket.upgraded() upgradedSocket = socket.upgraded()
@ -475,7 +475,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
// shut down // shut down
disposeTunnel(error: shutdownError) disposeTunnel(error: shutdownError)
} }
public func socketHasBetterPath(_ socket: GenericSocket) { public func socketHasBetterPath(_ socket: GenericSocket) {
log.debug("Stopping tunnel due to a new better path") log.debug("Stopping tunnel due to a new better path")
logCurrentSSID() logCurrentSSID()
@ -484,9 +484,9 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
} }
extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
// MARK: OpenVPNSessionDelegate (tunnel queue) // MARK: OpenVPNSessionDelegate (tunnel queue)
public func sessionDidStart(_ session: OpenVPNSession, remoteAddress: String, remoteProtocol: String?, options: OpenVPN.Configuration) { public func sessionDidStart(_ session: OpenVPNSession, remoteAddress: String, remoteProtocol: String?, options: OpenVPN.Configuration) {
log.info("Session did start") log.info("Session did start")
log.info("\tAddress: \(remoteAddress.maskedDescription)") log.info("\tAddress: \(remoteAddress.maskedDescription)")
@ -504,18 +504,18 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
bringNetworkUp(remoteAddress: remoteAddress, localOptions: session.configuration, remoteOptions: options) { (error) in bringNetworkUp(remoteAddress: remoteAddress, localOptions: session.configuration, remoteOptions: options) { (error) in
// FIXME: XPC queue // FIXME: XPC queue
self.reasserting = false self.reasserting = false
if let error = error { if let error = error {
log.error("Failed to configure tunnel: \(error)") log.error("Failed to configure tunnel: \(error)")
self.pendingStartHandler?(error) self.pendingStartHandler?(error)
self.pendingStartHandler = nil self.pendingStartHandler = nil
return return
} }
log.info("Tunnel interface is now UP") log.info("Tunnel interface is now UP")
session.setTunnel(tunnel: NETunnelInterface(impl: self.packetFlow)) session.setTunnel(tunnel: NETunnelInterface(impl: self.packetFlow))
self.pendingStartHandler?(nil) self.pendingStartHandler?(nil)
@ -525,7 +525,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
isCountingData = true isCountingData = true
refreshDataCount() refreshDataCount()
} }
public func sessionDidStop(_: OpenVPNSession, withError error: Error?, shouldReconnect: Bool) { public func sessionDidStop(_: OpenVPNSession, withError error: Error?, shouldReconnect: Bool) {
cfg._appexSetServerConfiguration(nil) cfg._appexSetServerConfiguration(nil)
@ -541,7 +541,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
self.shouldReconnect = shouldReconnect self.shouldReconnect = shouldReconnect
socket?.shutdown() socket?.shutdown()
} }
private func bringNetworkUp(remoteAddress: String, localOptions: OpenVPN.Configuration, remoteOptions: OpenVPN.Configuration, completionHandler: @escaping (Error?) -> Void) { private func bringNetworkUp(remoteAddress: String, localOptions: OpenVPN.Configuration, remoteOptions: OpenVPN.Configuration, completionHandler: @escaping (Error?) -> Void) {
let newSettings = NetworkSettingsBuilder(remoteAddress: remoteAddress, localOptions: localOptions, remoteOptions: remoteOptions) let newSettings = NetworkSettingsBuilder(remoteAddress: remoteAddress, localOptions: localOptions, remoteOptions: remoteOptions)
@ -599,13 +599,13 @@ extension OpenVPNTunnelProvider {
} }
return true return true
} }
// MARK: Logging // MARK: Logging
private func configureLogging() { private func configureLogging() {
let logLevel: SwiftyBeaver.Level = (cfg.shouldDebug ? debugLogLevel : .info) let logLevel: SwiftyBeaver.Level = (cfg.shouldDebug ? debugLogLevel : .info)
let logFormat = cfg.debugLogFormat ?? "$Dyyyy-MM-dd HH:mm:ss.SSS$d $L $N.$F:$l - $M" let logFormat = cfg.debugLogFormat ?? "$Dyyyy-MM-dd HH:mm:ss.SSS$d $L $N.$F:$l - $M"
if cfg.shouldDebug { if cfg.shouldDebug {
let console = ConsoleDestination() let console = ConsoleDestination()
console.useNSLog = true console.useNSLog = true
@ -623,7 +623,7 @@ extension OpenVPNTunnelProvider {
// store path for clients // store path for clients
cfg._appexSetDebugLogPath() cfg._appexSetDebugLogPath()
} }
private func flushLog() { private func flushLog() {
log.debug("Flushing log...") log.debug("Flushing log...")
@ -649,37 +649,37 @@ extension OpenVPNTunnelProvider {
// } // }
// MARK: Errors // MARK: Errors
private func setErrorStatus(with error: Error) { private func setErrorStatus(with error: Error) {
cfg._appexSetLastError(unifiedError(from: error)) cfg._appexSetLastError(unifiedError(from: error))
} }
private func unifiedError(from error: Error) -> OpenVPNProviderError { private func unifiedError(from error: Error) -> OpenVPNProviderError {
if let te = error.openVPNErrorCode() { if let te = error.openVPNErrorCode() {
switch te { switch te {
case .cryptoRandomGenerator, .cryptoAlgorithm: case .cryptoRandomGenerator, .cryptoAlgorithm:
return .encryptionInitialization return .encryptionInitialization
case .cryptoEncryption, .cryptoHMAC: case .cryptoEncryption, .cryptoHMAC:
return .encryptionData return .encryptionData
case .tlscaRead, .tlscaUse, .tlscaPeerVerification, case .tlscaRead, .tlscaUse, .tlscaPeerVerification,
.tlsClientCertificateRead, .tlsClientCertificateUse, .tlsClientCertificateRead, .tlsClientCertificateUse,
.tlsClientKeyRead, .tlsClientKeyUse: .tlsClientKeyRead, .tlsClientKeyUse:
return .tlsInitialization return .tlsInitialization
case .tlsServerCertificate, .tlsServerEKU, .tlsServerHost: case .tlsServerCertificate, .tlsServerEKU, .tlsServerHost:
return .tlsServerVerification return .tlsServerVerification
case .tlsHandshake: case .tlsHandshake:
return .tlsHandshake return .tlsHandshake
case .dataPathOverflow, .dataPathPeerIdMismatch: case .dataPathOverflow, .dataPathPeerIdMismatch:
return .unexpectedReply return .unexpectedReply
case .dataPathCompression: case .dataPathCompression:
return .serverCompression return .serverCompression
default: default:
break break
} }
@ -687,19 +687,19 @@ extension OpenVPNTunnelProvider {
switch se { switch se {
case .negotiationTimeout, .pingTimeout, .staleSession: case .negotiationTimeout, .pingTimeout, .staleSession:
return .timeout return .timeout
case .badCredentials: case .badCredentials:
return .authentication return .authentication
case .serverCompression: case .serverCompression:
return .serverCompression return .serverCompression
case .failedLinkWrite: case .failedLinkWrite:
return .linkError return .linkError
case .noRouting: case .noRouting:
return .routing return .routing
case .serverShutdown: case .serverShutdown:
return .serverShutdown return .serverShutdown

View File

@ -31,27 +31,27 @@ private let log = SwiftyBeaver.self
class ResolvedRemote: CustomStringConvertible { class ResolvedRemote: CustomStringConvertible {
let originalEndpoint: Endpoint let originalEndpoint: Endpoint
private(set) var isResolved: Bool private(set) var isResolved: Bool
private(set) var resolvedEndpoints: [Endpoint] private(set) var resolvedEndpoints: [Endpoint]
private var currentEndpointIndex: Int private var currentEndpointIndex: Int
var currentEndpoint: Endpoint? { var currentEndpoint: Endpoint? {
guard currentEndpointIndex < resolvedEndpoints.count else { guard currentEndpointIndex < resolvedEndpoints.count else {
return nil return nil
} }
return resolvedEndpoints[currentEndpointIndex] return resolvedEndpoints[currentEndpointIndex]
} }
init(_ originalEndpoint: Endpoint) { init(_ originalEndpoint: Endpoint) {
self.originalEndpoint = originalEndpoint self.originalEndpoint = originalEndpoint
isResolved = false isResolved = false
resolvedEndpoints = [] resolvedEndpoints = []
currentEndpointIndex = 0 currentEndpointIndex = 0
} }
func nextEndpoint() -> Bool { func nextEndpoint() -> Bool {
currentEndpointIndex += 1 currentEndpointIndex += 1
return currentEndpointIndex < resolvedEndpoints.count return currentEndpointIndex < resolvedEndpoints.count
@ -63,7 +63,7 @@ class ResolvedRemote: CustomStringConvertible {
completionHandler() completionHandler()
} }
} }
private func handleResult(_ result: Result<[DNSRecord], DNSError>) { private func handleResult(_ result: Result<[DNSRecord], DNSError>) {
switch result { switch result {
case .success(let records): case .success(let records):
@ -87,9 +87,9 @@ class ResolvedRemote: CustomStringConvertible {
log.debug("Unrolled endpoints: \(endpoints.maskedDescription)") log.debug("Unrolled endpoints: \(endpoints.maskedDescription)")
return endpoints return endpoints
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
var description: String { var description: String {
"{\(originalEndpoint.maskedDescription), resolved: \(resolvedEndpoints.maskedDescription)}" "{\(originalEndpoint.maskedDescription), resolved: \(resolvedEndpoints.maskedDescription)}"
} }

View File

@ -27,36 +27,36 @@ import Foundation
import CTunnelKitOpenVPNCore import CTunnelKitOpenVPNCore
extension OpenVPN { extension OpenVPN {
/// Defines the type of compression algorithm. /// Defines the type of compression algorithm.
public enum CompressionAlgorithm: Int, Codable, CustomStringConvertible { public enum CompressionAlgorithm: Int, Codable, CustomStringConvertible {
/// No compression. /// No compression.
case disabled case disabled
/// LZO compression. /// LZO compression.
case LZO case LZO
/// Any other compression algorithm (unsupported). /// Any other compression algorithm (unsupported).
case other case other
public var native: CompressionAlgorithmNative { public var native: CompressionAlgorithmNative {
guard let val = CompressionAlgorithmNative(rawValue: rawValue) else { guard let val = CompressionAlgorithmNative(rawValue: rawValue) else {
fatalError("Unhandled CompressionAlgorithm bridging") fatalError("Unhandled CompressionAlgorithm bridging")
} }
return val return val
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
switch self { switch self {
case .disabled: case .disabled:
return "disabled" return "disabled"
case .LZO: case .LZO:
return "lzo" return "lzo"
case .other: case .other:
return "other" return "other"
} }

View File

@ -33,7 +33,7 @@ extension OpenVPN {
/// No compression framing. /// No compression framing.
case disabled case disabled
/// Framing compatible with `comp-lzo` (deprecated in 2.4). /// Framing compatible with `comp-lzo` (deprecated in 2.4).
case compLZO case compLZO
@ -42,27 +42,27 @@ extension OpenVPN {
/// Framing compatible with 2.4 `compress` (version 2, e.g. stub-v2). /// Framing compatible with 2.4 `compress` (version 2, e.g. stub-v2).
case compressV2 case compressV2
public var native: CompressionFramingNative { public var native: CompressionFramingNative {
guard let val = CompressionFramingNative(rawValue: rawValue) else { guard let val = CompressionFramingNative(rawValue: rawValue) else {
fatalError("Unhandled CompressionFraming bridging") fatalError("Unhandled CompressionFraming bridging")
} }
return val return val
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
public var description: String { public var description: String {
switch self { switch self {
case .disabled: case .disabled:
return "disabled" return "disabled"
case .compress: case .compress:
return "compress" return "compress"
case .compressV2: case .compressV2:
return "compress" return "compress"
case .compLZO: case .compLZO:
return "comp-lzo" return "comp-lzo"
} }

View File

@ -41,16 +41,16 @@ import TunnelKitCore
private let log = SwiftyBeaver.self private let log = SwiftyBeaver.self
extension OpenVPN { extension OpenVPN {
/// A pair of credentials for authentication. /// A pair of credentials for authentication.
public struct Credentials: Codable, Equatable { public struct Credentials: Codable, Equatable {
/// The username. /// The username.
public let username: String public let username: String
/// The password. /// The password.
public let password: String public let password: String
public init(_ username: String, _ password: String) { public init(_ username: String, _ password: String) {
self.username = username self.username = username
self.password = password self.password = password
@ -59,64 +59,64 @@ extension OpenVPN {
/// Encryption algorithm. /// Encryption algorithm.
public enum Cipher: String, Codable, CustomStringConvertible { public enum Cipher: String, Codable, CustomStringConvertible {
// WARNING: must match OpenSSL algorithm names // WARNING: must match OpenSSL algorithm names
/// AES encryption with 128-bit key size and CBC. /// AES encryption with 128-bit key size and CBC.
case aes128cbc = "AES-128-CBC" case aes128cbc = "AES-128-CBC"
/// AES encryption with 192-bit key size and CBC. /// AES encryption with 192-bit key size and CBC.
case aes192cbc = "AES-192-CBC" case aes192cbc = "AES-192-CBC"
/// AES encryption with 256-bit key size and CBC. /// AES encryption with 256-bit key size and CBC.
case aes256cbc = "AES-256-CBC" case aes256cbc = "AES-256-CBC"
/// AES encryption with 128-bit key size and GCM. /// AES encryption with 128-bit key size and GCM.
case aes128gcm = "AES-128-GCM" case aes128gcm = "AES-128-GCM"
/// AES encryption with 192-bit key size and GCM. /// AES encryption with 192-bit key size and GCM.
case aes192gcm = "AES-192-GCM" case aes192gcm = "AES-192-GCM"
/// AES encryption with 256-bit key size and GCM. /// AES encryption with 256-bit key size and GCM.
case aes256gcm = "AES-256-GCM" case aes256gcm = "AES-256-GCM"
/// Returns the key size for this cipher. /// Returns the key size for this cipher.
public var keySize: Int { public var keySize: Int {
switch self { switch self {
case .aes128cbc, .aes128gcm: case .aes128cbc, .aes128gcm:
return 128 return 128
case .aes192cbc, .aes192gcm: case .aes192cbc, .aes192gcm:
return 192 return 192
case .aes256cbc, .aes256gcm: case .aes256cbc, .aes256gcm:
return 256 return 256
} }
} }
/// Digest should be ignored when this is `true`. /// Digest should be ignored when this is `true`.
public var embedsDigest: Bool { public var embedsDigest: Bool {
return rawValue.hasSuffix("-GCM") return rawValue.hasSuffix("-GCM")
} }
/// Returns a generic name for this cipher. /// Returns a generic name for this cipher.
public var genericName: String { public var genericName: String {
return rawValue.hasSuffix("-GCM") ? "AES-GCM" : "AES-CBC" return rawValue.hasSuffix("-GCM") ? "AES-GCM" : "AES-CBC"
} }
public var description: String { public var description: String {
return rawValue return rawValue
} }
} }
/// Message digest algorithm. /// Message digest algorithm.
public enum Digest: String, Codable, CustomStringConvertible { public enum Digest: String, Codable, CustomStringConvertible {
// WARNING: must match OpenSSL algorithm names // WARNING: must match OpenSSL algorithm names
/// SHA1 message digest. /// SHA1 message digest.
case sha1 = "SHA1" case sha1 = "SHA1"
/// SHA224 message digest. /// SHA224 message digest.
case sha224 = "SHA224" case sha224 = "SHA224"
@ -128,17 +128,17 @@ extension OpenVPN {
/// SHA256 message digest. /// SHA256 message digest.
case sha512 = "SHA512" case sha512 = "SHA512"
/// Returns a generic name for this digest. /// Returns a generic name for this digest.
public var genericName: String { public var genericName: String {
return "HMAC" return "HMAC"
} }
public var description: String { public var description: String {
return "\(genericName)-\(rawValue)" return "\(genericName)-\(rawValue)"
} }
} }
/// Routing policy. /// Routing policy.
public enum RoutingPolicy: String, Codable { public enum RoutingPolicy: String, Codable {
@ -147,20 +147,20 @@ extension OpenVPN {
/// All IPv6 traffic goes through the VPN. /// All IPv6 traffic goes through the VPN.
case IPv6 case IPv6
/// Block LAN while connected. /// Block LAN while connected.
case blockLocal case blockLocal
} }
/// Settings that can be pulled from server. /// Settings that can be pulled from server.
public enum PullMask: String, Codable, CaseIterable { public enum PullMask: String, Codable, CaseIterable {
/// Routes and gateways. /// Routes and gateways.
case routes case routes
/// DNS settings. /// DNS settings.
case dns case dns
/// Proxy settings. /// Proxy settings.
case proxy case proxy
} }
@ -169,91 +169,91 @@ extension OpenVPN {
public struct ConfigurationBuilder { public struct ConfigurationBuilder {
// MARK: General // MARK: General
/// The cipher algorithm for data encryption. /// The cipher algorithm for data encryption.
public var cipher: Cipher? public var cipher: Cipher?
/// The set of supported cipher algorithms for data encryption (2.5.). /// The set of supported cipher algorithms for data encryption (2.5.).
public var dataCiphers: [Cipher]? public var dataCiphers: [Cipher]?
/// The digest algorithm for HMAC. /// The digest algorithm for HMAC.
public var digest: Digest? public var digest: Digest?
/// Compression framing, disabled by default. /// Compression framing, disabled by default.
public var compressionFraming: CompressionFraming? public var compressionFraming: CompressionFraming?
/// Compression algorithm, disabled by default. /// Compression algorithm, disabled by default.
public var compressionAlgorithm: CompressionAlgorithm? public var compressionAlgorithm: CompressionAlgorithm?
/// The CA for TLS negotiation (PEM format). /// The CA for TLS negotiation (PEM format).
public var ca: CryptoContainer? public var ca: CryptoContainer?
/// The optional client certificate for TLS negotiation (PEM format). /// The optional client certificate for TLS negotiation (PEM format).
public var clientCertificate: CryptoContainer? public var clientCertificate: CryptoContainer?
/// The private key for the certificate in `clientCertificate` (PEM format). /// The private key for the certificate in `clientCertificate` (PEM format).
public var clientKey: CryptoContainer? public var clientKey: CryptoContainer?
/// The optional TLS wrapping. /// The optional TLS wrapping.
public var tlsWrap: TLSWrap? public var tlsWrap: TLSWrap?
/// If set, overrides TLS security level (0 = lowest). /// If set, overrides TLS security level (0 = lowest).
public var tlsSecurityLevel: Int? public var tlsSecurityLevel: Int?
/// Sends periodical keep-alive packets if set. /// Sends periodical keep-alive packets if set.
public var keepAliveInterval: TimeInterval? public var keepAliveInterval: TimeInterval?
/// Disconnects after no keep-alive packets are received within timeout interval if set. /// Disconnects after no keep-alive packets are received within timeout interval if set.
public var keepAliveTimeout: TimeInterval? public var keepAliveTimeout: TimeInterval?
/// The number of seconds after which a renegotiation should be initiated. If `nil`, the client will never initiate a renegotiation. /// The number of seconds after which a renegotiation should be initiated. If `nil`, the client will never initiate a renegotiation.
public var renegotiatesAfter: TimeInterval? public var renegotiatesAfter: TimeInterval?
// MARK: Client // MARK: Client
/// The list of server endpoints. /// The list of server endpoints.
public var remotes: [Endpoint]? public var remotes: [Endpoint]?
/// If true, checks EKU of server certificate. /// If true, checks EKU of server certificate.
public var checksEKU: Bool? public var checksEKU: Bool?
/// If true, checks if hostname (sanHost) is present in certificates SAN. /// If true, checks if hostname (sanHost) is present in certificates SAN.
public var checksSANHost: Bool? public var checksSANHost: Bool?
/// The server hostname used for checking certificate SAN. /// The server hostname used for checking certificate SAN.
public var sanHost: String? public var sanHost: String?
/// Picks endpoint from `remotes` randomly. /// Picks endpoint from `remotes` randomly.
public var randomizeEndpoint: Bool? public var randomizeEndpoint: Bool?
/// Prepend hostnames with a number of random bytes defined in `Configuration.randomHostnamePrefixLength`. /// Prepend hostnames with a number of random bytes defined in `Configuration.randomHostnamePrefixLength`.
public var randomizeHostnames: Bool? public var randomizeHostnames: Bool?
/// Server is patched for the PIA VPN provider. /// Server is patched for the PIA VPN provider.
public var usesPIAPatches: Bool? public var usesPIAPatches: Bool?
/// The tunnel MTU. /// The tunnel MTU.
public var mtu: Int? public var mtu: Int?
/// Requires username authentication. /// Requires username authentication.
public var authUserPass: Bool? public var authUserPass: Bool?
// MARK: Server // MARK: Server
/// The auth-token returned by the server. /// The auth-token returned by the server.
public var authToken: String? public var authToken: String?
/// The peer-id returned by the server. /// The peer-id returned by the server.
public var peerId: UInt32? public var peerId: UInt32?
// MARK: Routing // MARK: Routing
/// The settings for IPv4. `OpenVPNSession` only evaluates this server-side. /// The settings for IPv4. `OpenVPNSession` only evaluates this server-side.
public var ipv4: IPv4Settings? public var ipv4: IPv4Settings?
/// The settings for IPv6. `OpenVPNSession` only evaluates this server-side. /// The settings for IPv6. `OpenVPNSession` only evaluates this server-side.
public var ipv6: IPv6Settings? public var ipv6: IPv6Settings?
/// The IPv4 routes if `ipv4` is nil. /// The IPv4 routes if `ipv4` is nil.
public var routes4: [IPv4Settings.Route]? public var routes4: [IPv4Settings.Route]?
@ -262,13 +262,13 @@ extension OpenVPN {
/// Set false to ignore DNS settings, even when pushed. /// Set false to ignore DNS settings, even when pushed.
public var isDNSEnabled: Bool? public var isDNSEnabled: Bool?
/// The DNS protocol, defaults to `.plain` (iOS 14+ / macOS 11+). /// The DNS protocol, defaults to `.plain` (iOS 14+ / macOS 11+).
public var dnsProtocol: DNSProtocol? public var dnsProtocol: DNSProtocol?
/// The DNS servers if `dnsProtocol = .plain` or nil. /// The DNS servers if `dnsProtocol = .plain` or nil.
public var dnsServers: [String]? public var dnsServers: [String]?
/// The server URL if `dnsProtocol = .https`. /// The server URL if `dnsProtocol = .https`.
public var dnsHTTPSURL: URL? public var dnsHTTPSURL: URL?
@ -295,30 +295,30 @@ extension OpenVPN {
/// The Proxy Auto-Configuration (PAC) url. /// The Proxy Auto-Configuration (PAC) url.
public var proxyAutoConfigurationURL: URL? public var proxyAutoConfigurationURL: URL?
/// Set false to ignore proxy settings, even when pushed. /// Set false to ignore proxy settings, even when pushed.
public var isProxyEnabled: Bool? public var isProxyEnabled: Bool?
/// The HTTP proxy. /// The HTTP proxy.
public var httpProxy: Proxy? public var httpProxy: Proxy?
/// The HTTPS proxy. /// The HTTPS proxy.
public var httpsProxy: Proxy? public var httpsProxy: Proxy?
/// The list of domains not passing through the proxy. /// The list of domains not passing through the proxy.
public var proxyBypassDomains: [String]? public var proxyBypassDomains: [String]?
/// Policies for redirecting traffic through the VPN gateway. /// Policies for redirecting traffic through the VPN gateway.
public var routingPolicies: [RoutingPolicy]? public var routingPolicies: [RoutingPolicy]?
/// Server settings that must not be pulled. /// Server settings that must not be pulled.
public var noPullMask: [PullMask]? public var noPullMask: [PullMask]?
// MARK: Extra // MARK: Extra
/// The method to follow in regards to the XOR patch. /// The method to follow in regards to the XOR patch.
public var xorMethod: XORMethod? public var xorMethod: XORMethod?
/** /**
Creates a `ConfigurationBuilder`. Creates a `ConfigurationBuilder`.
@ -332,7 +332,7 @@ extension OpenVPN {
compressionAlgorithm = Configuration.Fallback.compressionAlgorithm compressionAlgorithm = Configuration.Fallback.compressionAlgorithm
} }
} }
/** /**
Builds a `Configuration` object. Builds a `Configuration` object.
@ -386,45 +386,45 @@ extension OpenVPN {
) )
} }
} }
/// The immutable configuration for `OpenVPNSession`. /// The immutable configuration for `OpenVPNSession`.
public struct Configuration: Codable, Equatable { public struct Configuration: Codable, Equatable {
struct Fallback { struct Fallback {
static let cipher: Cipher = .aes128cbc static let cipher: Cipher = .aes128cbc
static let digest: Digest = .sha1 static let digest: Digest = .sha1
static let compressionFraming: CompressionFraming = .disabled static let compressionFraming: CompressionFraming = .disabled
static let compressionAlgorithm: CompressionAlgorithm = .disabled static let compressionAlgorithm: CompressionAlgorithm = .disabled
} }
private static let randomHostnamePrefixLength = 6 private static let randomHostnamePrefixLength = 6
/// - Seealso: `ConfigurationBuilder.cipher` /// - Seealso: `ConfigurationBuilder.cipher`
public let cipher: Cipher? public let cipher: Cipher?
/// - Seealso: `ConfigurationBuilder.dataCiphers` /// - Seealso: `ConfigurationBuilder.dataCiphers`
public let dataCiphers: [Cipher]? public let dataCiphers: [Cipher]?
/// - Seealso: `ConfigurationBuilder.digest` /// - Seealso: `ConfigurationBuilder.digest`
public let digest: Digest? public let digest: Digest?
/// - Seealso: `ConfigurationBuilder.compressionFraming` /// - Seealso: `ConfigurationBuilder.compressionFraming`
public let compressionFraming: CompressionFraming? public let compressionFraming: CompressionFraming?
/// - Seealso: `ConfigurationBuilder.compressionAlgorithm` /// - Seealso: `ConfigurationBuilder.compressionAlgorithm`
public let compressionAlgorithm: CompressionAlgorithm? public let compressionAlgorithm: CompressionAlgorithm?
/// - Seealso: `ConfigurationBuilder.ca` /// - Seealso: `ConfigurationBuilder.ca`
public let ca: CryptoContainer? public let ca: CryptoContainer?
/// - Seealso: `ConfigurationBuilder.clientCertificate` /// - Seealso: `ConfigurationBuilder.clientCertificate`
public let clientCertificate: CryptoContainer? public let clientCertificate: CryptoContainer?
/// - Seealso: `ConfigurationBuilder.clientKey` /// - Seealso: `ConfigurationBuilder.clientKey`
public let clientKey: CryptoContainer? public let clientKey: CryptoContainer?
/// - Seealso: `ConfigurationBuilder.tlsWrap` /// - Seealso: `ConfigurationBuilder.tlsWrap`
public let tlsWrap: TLSWrap? public let tlsWrap: TLSWrap?
@ -433,7 +433,7 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.keepAliveInterval` /// - Seealso: `ConfigurationBuilder.keepAliveInterval`
public let keepAliveInterval: TimeInterval? public let keepAliveInterval: TimeInterval?
/// - Seealso: `ConfigurationBuilder.keepAliveTimeout` /// - Seealso: `ConfigurationBuilder.keepAliveTimeout`
public let keepAliveTimeout: TimeInterval? public let keepAliveTimeout: TimeInterval?
@ -445,22 +445,22 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.checksEKU` /// - Seealso: `ConfigurationBuilder.checksEKU`
public let checksEKU: Bool? public let checksEKU: Bool?
/// - Seealso: `ConfigurationBuilder.checksSANHost` /// - Seealso: `ConfigurationBuilder.checksSANHost`
public let checksSANHost: Bool? public let checksSANHost: Bool?
/// - Seealso: `ConfigurationBuilder.sanHost` /// - Seealso: `ConfigurationBuilder.sanHost`
public let sanHost: String? public let sanHost: String?
/// - Seealso: `ConfigurationBuilder.randomizeEndpoint` /// - Seealso: `ConfigurationBuilder.randomizeEndpoint`
public let randomizeEndpoint: Bool? public let randomizeEndpoint: Bool?
/// - Seealso: `ConfigurationBuilder.randomizeHostnames` /// - Seealso: `ConfigurationBuilder.randomizeHostnames`
public var randomizeHostnames: Bool? public var randomizeHostnames: Bool?
/// - Seealso: `ConfigurationBuilder.usesPIAPatches` /// - Seealso: `ConfigurationBuilder.usesPIAPatches`
public let usesPIAPatches: Bool? public let usesPIAPatches: Bool?
/// - Seealso: `ConfigurationBuilder.mtu` /// - Seealso: `ConfigurationBuilder.mtu`
public let mtu: Int? public let mtu: Int?
@ -469,10 +469,10 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.authToken` /// - Seealso: `ConfigurationBuilder.authToken`
public let authToken: String? public let authToken: String?
/// - Seealso: `ConfigurationBuilder.peerId` /// - Seealso: `ConfigurationBuilder.peerId`
public let peerId: UInt32? public let peerId: UInt32?
/// - Seealso: `ConfigurationBuilder.ipv4` /// - Seealso: `ConfigurationBuilder.ipv4`
public let ipv4: IPv4Settings? public let ipv4: IPv4Settings?
@ -490,16 +490,16 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.dnsProtocol` /// - Seealso: `ConfigurationBuilder.dnsProtocol`
public let dnsProtocol: DNSProtocol? public let dnsProtocol: DNSProtocol?
/// - Seealso: `ConfigurationBuilder.dnsServers` /// - Seealso: `ConfigurationBuilder.dnsServers`
public let dnsServers: [String]? public let dnsServers: [String]?
/// - Seealso: `ConfigurationBuilder.dnsHTTPSURL` /// - Seealso: `ConfigurationBuilder.dnsHTTPSURL`
public let dnsHTTPSURL: URL? public let dnsHTTPSURL: URL?
/// - Seealso: `ConfigurationBuilder.dnsTLSServerName` /// - Seealso: `ConfigurationBuilder.dnsTLSServerName`
public let dnsTLSServerName: String? public let dnsTLSServerName: String?
/// - Seealso: `ConfigurationBuilder.dnsDomain` /// - Seealso: `ConfigurationBuilder.dnsDomain`
public let dnsDomain: String? public let dnsDomain: String?
@ -514,24 +514,24 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.httpsProxy` /// - Seealso: `ConfigurationBuilder.httpsProxy`
public let httpsProxy: Proxy? public let httpsProxy: Proxy?
/// - Seealso: `ConfigurationBuilder.proxyAutoConfigurationURL` /// - Seealso: `ConfigurationBuilder.proxyAutoConfigurationURL`
public let proxyAutoConfigurationURL: URL? public let proxyAutoConfigurationURL: URL?
/// - Seealso: `ConfigurationBuilder.proxyBypassDomains` /// - Seealso: `ConfigurationBuilder.proxyBypassDomains`
public let proxyBypassDomains: [String]? public let proxyBypassDomains: [String]?
/// - Seealso: `ConfigurationBuilder.routingPolicies` /// - Seealso: `ConfigurationBuilder.routingPolicies`
public let routingPolicies: [RoutingPolicy]? public let routingPolicies: [RoutingPolicy]?
/// - Seealso: `ConfigurationBuilder.noPullMask` /// - Seealso: `ConfigurationBuilder.noPullMask`
public let noPullMask: [PullMask]? public let noPullMask: [PullMask]?
/// - Seealso: `ConfigurationBuilder.xorMethod` /// - Seealso: `ConfigurationBuilder.xorMethod`
public let xorMethod: XORMethod? public let xorMethod: XORMethod?
// MARK: Shortcuts // MARK: Shortcuts
public var fallbackCipher: Cipher { public var fallbackCipher: Cipher {
return cipher ?? Fallback.cipher return cipher ?? Fallback.cipher
} }
@ -556,9 +556,9 @@ extension OpenVPN {
let pulled = Array(Set(all).subtracting(notPulled)) let pulled = Array(Set(all).subtracting(notPulled))
return !pulled.isEmpty ? pulled : nil return !pulled.isEmpty ? pulled : nil
} }
// MARK: Computed // MARK: Computed
public var processedRemotes: [Endpoint]? { public var processedRemotes: [Endpoint]? {
guard var processedRemotes = remotes else { guard var processedRemotes = remotes else {
return nil return nil
@ -584,7 +584,7 @@ extension OpenVPN {
// MARK: Modification // MARK: Modification
extension OpenVPN.Configuration { extension OpenVPN.Configuration {
/** /**
Returns a `ConfigurationBuilder` to use this configuration as a starting point for a new one. Returns a `ConfigurationBuilder` to use this configuration as a starting point for a new one.
@ -701,7 +701,7 @@ extension OpenVPN.Configuration {
log.info("\tTLS security level: default") log.info("\tTLS security level: default")
} }
} }
if let keepAliveSeconds = keepAliveInterval, keepAliveSeconds > 0 { if let keepAliveSeconds = keepAliveInterval, keepAliveSeconds > 0 {
log.info("\tKeep-alive interval: \(keepAliveSeconds.asTimeString)") log.info("\tKeep-alive interval: \(keepAliveSeconds.asTimeString)")
} else if isLocal { } else if isLocal {

View File

@ -29,19 +29,19 @@ extension OpenVPN {
/// Error raised by the configuration parser, with details about the line that triggered it. /// Error raised by the configuration parser, with details about the line that triggered it.
public enum ConfigurationError: Error { public enum ConfigurationError: Error {
/// Option syntax is incorrect. /// Option syntax is incorrect.
case malformed(option: String) case malformed(option: String)
/// A required option is missing. /// A required option is missing.
case missingConfiguration(option: String) case missingConfiguration(option: String)
/// An option is unsupported. /// An option is unsupported.
case unsupportedConfiguration(option: String) case unsupportedConfiguration(option: String)
/// Passphrase required to decrypt private keys. /// Passphrase required to decrypt private keys.
case encryptionPassphrase case encryptionPassphrase
/// Encryption passphrase is incorrect or key is corrupt. /// Encryption passphrase is incorrect or key is corrupt.
case unableToDecrypt(error: Error) case unableToDecrypt(error: Error)
} }

View File

@ -37,136 +37,136 @@ extension OpenVPN {
public class ConfigurationParser { public class ConfigurationParser {
// XXX: parsing is very optimistic // XXX: parsing is very optimistic
/// Regexes used to parse OpenVPN options. /// Regexes used to parse OpenVPN options.
public struct Regex { public struct Regex {
// MARK: General // MARK: General
static let cipher = NSRegularExpression("^cipher +[^,\\s]+") static let cipher = NSRegularExpression("^cipher +[^,\\s]+")
static let dataCiphers = NSRegularExpression("^(data-ciphers|ncp-ciphers) +[^,\\s]+(:[^,\\s]+)*") static let dataCiphers = NSRegularExpression("^(data-ciphers|ncp-ciphers) +[^,\\s]+(:[^,\\s]+)*")
static let dataCiphersFallback = NSRegularExpression("^data-ciphers-fallback +[^,\\s]+") static let dataCiphersFallback = NSRegularExpression("^data-ciphers-fallback +[^,\\s]+")
static let auth = NSRegularExpression("^auth +[\\w\\-]+") static let auth = NSRegularExpression("^auth +[\\w\\-]+")
static let compLZO = NSRegularExpression("^comp-lzo.*") static let compLZO = NSRegularExpression("^comp-lzo.*")
static let compress = NSRegularExpression("^compress.*") static let compress = NSRegularExpression("^compress.*")
static let keyDirection = NSRegularExpression("^key-direction +\\d") static let keyDirection = NSRegularExpression("^key-direction +\\d")
static let ping = NSRegularExpression("^ping +\\d+") static let ping = NSRegularExpression("^ping +\\d+")
static let pingRestart = NSRegularExpression("^ping-restart +\\d+") static let pingRestart = NSRegularExpression("^ping-restart +\\d+")
static let keepAlive = NSRegularExpression("^keepalive +\\d+ ++\\d+") static let keepAlive = NSRegularExpression("^keepalive +\\d+ ++\\d+")
static let renegSec = NSRegularExpression("^reneg-sec +\\d+") static let renegSec = NSRegularExpression("^reneg-sec +\\d+")
static let blockBegin = NSRegularExpression("^<[\\w\\-]+>") static let blockBegin = NSRegularExpression("^<[\\w\\-]+>")
static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>") static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>")
// MARK: Client // MARK: Client
static let proto = NSRegularExpression("^proto +(udp[46]?|tcp[46]?)") static let proto = NSRegularExpression("^proto +(udp[46]?|tcp[46]?)")
static let port = NSRegularExpression("^port +\\d+") static let port = NSRegularExpression("^port +\\d+")
static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp[46]?|tcp[46]?))?") static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp[46]?|tcp[46]?))?")
static let authUserPass = NSRegularExpression("^auth-user-pass") static let authUserPass = NSRegularExpression("^auth-user-pass")
static let eku = NSRegularExpression("^remote-cert-tls +server") static let eku = NSRegularExpression("^remote-cert-tls +server")
static let remoteRandom = NSRegularExpression("^remote-random") static let remoteRandom = NSRegularExpression("^remote-random")
static let remoteRandomHostname = NSRegularExpression("^remote-random-hostname") static let remoteRandomHostname = NSRegularExpression("^remote-random-hostname")
static let mtu = NSRegularExpression("^tun-mtu +\\d+") static let mtu = NSRegularExpression("^tun-mtu +\\d+")
// MARK: Server // MARK: Server
public static let authToken = NSRegularExpression("^auth-token +[a-zA-Z0-9/=+]+") public static let authToken = NSRegularExpression("^auth-token +[a-zA-Z0-9/=+]+")
static let peerId = NSRegularExpression("^peer-id +[0-9]+") static let peerId = NSRegularExpression("^peer-id +[0-9]+")
// MARK: Routing // MARK: Routing
static let topology = NSRegularExpression("^topology +(net30|p2p|subnet)") static let topology = NSRegularExpression("^topology +(net30|p2p|subnet)")
static let ifconfig = NSRegularExpression("^ifconfig +[\\d\\.]+ [\\d\\.]+") static let ifconfig = NSRegularExpression("^ifconfig +[\\d\\.]+ [\\d\\.]+")
static let ifconfig6 = NSRegularExpression("^ifconfig-ipv6 +[\\da-fA-F:]+/\\d+ [\\da-fA-F:]+") static let ifconfig6 = NSRegularExpression("^ifconfig-ipv6 +[\\da-fA-F:]+/\\d+ [\\da-fA-F:]+")
static let route = NSRegularExpression("^route +[\\d\\.]+( +[\\d\\.]+){0,2}") static let route = NSRegularExpression("^route +[\\d\\.]+( +[\\d\\.]+){0,2}")
static let route6 = NSRegularExpression("^route-ipv6 +[\\da-fA-F:]+/\\d+( +[\\da-fA-F:]+){0,2}") static let route6 = NSRegularExpression("^route-ipv6 +[\\da-fA-F:]+/\\d+( +[\\da-fA-F:]+){0,2}")
static let gateway = NSRegularExpression("^route-gateway +[\\d\\.]+") static let gateway = NSRegularExpression("^route-gateway +[\\d\\.]+")
static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+") static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+")
static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+") static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+")
static let domainSearch = NSRegularExpression("^dhcp-option +DOMAIN-SEARCH +[^ ]+") static let domainSearch = NSRegularExpression("^dhcp-option +DOMAIN-SEARCH +[^ ]+")
static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS? +[^ ]+ +\\d+|AUTO_CONFIG_URL +[^ ]+)") static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS? +[^ ]+ +\\d+|AUTO_CONFIG_URL +[^ ]+)")
static let proxyBypass = NSRegularExpression("^dhcp-option +PROXY_BYPASS +.+") static let proxyBypass = NSRegularExpression("^dhcp-option +PROXY_BYPASS +.+")
static let redirectGateway = NSRegularExpression("^redirect-gateway.*") static let redirectGateway = NSRegularExpression("^redirect-gateway.*")
static let routeNoPull = NSRegularExpression("^route-nopull") static let routeNoPull = NSRegularExpression("^route-nopull")
// MARK: Extra // MARK: Extra
static let xorInfo = NSRegularExpression("^scramble +(xormask|xorptrpos|reverse|obfuscate)[\\s]?([^\\s]+)?") static let xorInfo = NSRegularExpression("^scramble +(xormask|xorptrpos|reverse|obfuscate)[\\s]?([^\\s]+)?")
// MARK: Unsupported // MARK: Unsupported
// static let fragment = NSRegularExpression("^fragment +\\d+") // static let fragment = NSRegularExpression("^fragment +\\d+")
static let fragment = NSRegularExpression("^fragment") static let fragment = NSRegularExpression("^fragment")
static let connectionProxy = NSRegularExpression("^\\w+-proxy") static let connectionProxy = NSRegularExpression("^\\w+-proxy")
static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ") static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ")
static let connection = NSRegularExpression("^<connection>") static let connection = NSRegularExpression("^<connection>")
// MARK: Continuation // MARK: Continuation
static let continuation = NSRegularExpression("^push-continuation [12]") static let continuation = NSRegularExpression("^push-continuation [12]")
} }
private enum Topology: String { private enum Topology: String {
case net30 case net30
case p2p case p2p
case subnet case subnet
} }
private enum RedirectGateway: String { private enum RedirectGateway: String {
case def1 // default case def1 // default
case noIPv4 = "!ipv4" case noIPv4 = "!ipv4"
case ipv6 case ipv6
case local case local
case autolocal case autolocal
case blockLocal = "block-local" case blockLocal = "block-local"
case bypassDHCP = "bypass-dhcp" case bypassDHCP = "bypass-dhcp"
case bypassDNS = "bypass-dns" case bypassDNS = "bypass-dns"
} }
/// Result of the parser. /// Result of the parser.
public struct Result { public struct Result {
@ -181,11 +181,11 @@ extension OpenVPN {
/// ///
/// - Seealso: `ConfigurationParser.parsed(...)` /// - Seealso: `ConfigurationParser.parsed(...)`
public let strippedLines: [String]? public let strippedLines: [String]?
/// Holds an optional `ConfigurationError` that didn't block the parser, but it would be worth taking care of. /// Holds an optional `ConfigurationError` that didn't block the parser, but it would be worth taking care of.
public let warning: ConfigurationError? public let warning: ConfigurationError?
} }
/** /**
Parses a configuration from a .ovpn file. Parses a configuration from a .ovpn file.
@ -254,7 +254,7 @@ extension OpenVPN {
var unsupportedError: ConfigurationError? var unsupportedError: ConfigurationError?
var currentBlockName: String? var currentBlockName: String?
var currentBlock: [String] = [] var currentBlock: [String] = []
var optDataCiphers: [Cipher]? var optDataCiphers: [Cipher]?
var optDataCiphersFallback: Cipher? var optDataCiphersFallback: Cipher?
var optCipher: Cipher? var optCipher: Cipher?
@ -304,7 +304,7 @@ extension OpenVPN {
log.verbose("Configuration file:") log.verbose("Configuration file:")
for line in lines { for line in lines {
log.verbose(line) log.verbose(line)
var isHandled = false var isHandled = false
var strippedLine = line var strippedLine = line
defer { defer {
@ -312,9 +312,9 @@ extension OpenVPN {
optStrippedLines?.append(strippedLine) optStrippedLines?.append(strippedLine)
} }
} }
// MARK: Unsupported // MARK: Unsupported
// check blocks first // check blocks first
Regex.connection.enumerateSpacedComponents(in: line) { (_) in Regex.connection.enumerateSpacedComponents(in: line) { (_) in
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "<connection> blocks") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "<connection> blocks")
@ -331,7 +331,7 @@ extension OpenVPN {
if line.contains("mtu") || line.contains("mssfix") { if line.contains("mtu") || line.contains("mssfix") {
isHandled = true isHandled = true
} }
// MARK: Continuation // MARK: Continuation
var isContinuation = false var isContinuation = false
@ -343,7 +343,7 @@ extension OpenVPN {
} }
// MARK: Inline content // MARK: Inline content
if unsupportedError == nil { if unsupportedError == nil {
if currentBlockName == nil { if currentBlockName == nil {
Regex.blockBegin.enumerateSpacedComponents(in: line) { Regex.blockBegin.enumerateSpacedComponents(in: line) {
@ -351,7 +351,7 @@ extension OpenVPN {
let tag = $0.first! let tag = $0.first!
let from = tag.index(after: tag.startIndex) let from = tag.index(after: tag.startIndex)
let to = tag.index(before: tag.endIndex) let to = tag.index(before: tag.endIndex)
currentBlockName = String(tag[from..<to]) currentBlockName = String(tag[from..<to])
currentBlock = [] currentBlock = []
} }
@ -361,33 +361,33 @@ extension OpenVPN {
let tag = $0.first! let tag = $0.first!
let from = tag.index(tag.startIndex, offsetBy: 2) let from = tag.index(tag.startIndex, offsetBy: 2)
let to = tag.index(before: tag.endIndex) let to = tag.index(before: tag.endIndex)
let blockName = String(tag[from..<to]) let blockName = String(tag[from..<to])
guard blockName == currentBlockName else { guard blockName == currentBlockName else {
return return
} }
// first is opening tag // first is opening tag
currentBlock.removeFirst() currentBlock.removeFirst()
switch blockName { switch blockName {
case "ca": case "ca":
optCA = CryptoContainer(pem: currentBlock.joined(separator: "\n")) optCA = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
case "cert": case "cert":
optClientCertificate = CryptoContainer(pem: currentBlock.joined(separator: "\n")) optClientCertificate = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
case "key": case "key":
ConfigurationParser.normalizeEncryptedPEMBlock(block: &currentBlock) ConfigurationParser.normalizeEncryptedPEMBlock(block: &currentBlock)
optClientKey = CryptoContainer(pem: currentBlock.joined(separator: "\n")) optClientKey = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
case "tls-auth": case "tls-auth":
optTLSKeyLines = currentBlock.map(Substring.init(_:)) optTLSKeyLines = currentBlock.map(Substring.init(_:))
optTLSStrategy = .auth optTLSStrategy = .auth
case "tls-crypt": case "tls-crypt":
optTLSKeyLines = currentBlock.map(Substring.init(_:)) optTLSKeyLines = currentBlock.map(Substring.init(_:))
optTLSStrategy = .crypt optTLSStrategy = .crypt
default: default:
break break
} }
@ -399,9 +399,9 @@ extension OpenVPN {
currentBlock.append(line) currentBlock.append(line)
continue continue
} }
// MARK: General // MARK: General
Regex.cipher.enumerateSpacedArguments(in: line) { Regex.cipher.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let rawValue = $0.first else { guard let rawValue = $0.first else {
@ -443,7 +443,7 @@ extension OpenVPN {
Regex.compLZO.enumerateSpacedArguments(in: line) { Regex.compLZO.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
optCompressionFraming = .compLZO optCompressionFraming = .compLZO
if !LZOFactory.isSupported() { if !LZOFactory.isSupported() {
guard let arg = $0.first else { guard let arg = $0.first else {
optWarning = optWarning ?? .unsupportedConfiguration(option: line) optWarning = optWarning ?? .unsupportedConfiguration(option: line)
@ -461,7 +461,7 @@ extension OpenVPN {
Regex.compress.enumerateSpacedArguments(in: line) { Regex.compress.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
optCompressionFraming = .compress optCompressionFraming = .compress
if !LZOFactory.isSupported() { if !LZOFactory.isSupported() {
guard $0.isEmpty else { guard $0.isEmpty else {
unsupportedError = .unsupportedConfiguration(option: line) unsupportedError = .unsupportedConfiguration(option: line)
@ -472,10 +472,10 @@ extension OpenVPN {
switch arg { switch arg {
case "lzo": case "lzo":
optCompressionAlgorithm = .LZO optCompressionAlgorithm = .LZO
case "stub": case "stub":
optCompressionAlgorithm = .disabled optCompressionAlgorithm = .disabled
case "stub-v2": case "stub-v2":
optCompressionFraming = .compressV2 optCompressionFraming = .compressV2
optCompressionAlgorithm = .disabled optCompressionAlgorithm = .disabled
@ -524,9 +524,9 @@ extension OpenVPN {
} }
optRenegotiateAfterSeconds = TimeInterval(arg) optRenegotiateAfterSeconds = TimeInterval(arg)
} }
// MARK: Client // MARK: Client
Regex.proto.enumerateSpacedArguments(in: line) { Regex.proto.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let str = $0.first else { guard let str = $0.first else {
@ -561,7 +561,7 @@ extension OpenVPN {
strippedComponents.append($0[2]) strippedComponents.append($0[2])
} }
optRemotes.append((hostname, port, proto)) optRemotes.append((hostname, port, proto))
// replace private data // replace private data
strippedLine = strippedComponents.joined(separator: " ") strippedLine = strippedComponents.joined(separator: " ")
} }
@ -588,18 +588,18 @@ extension OpenVPN {
isHandled = true isHandled = true
authUserPass = true authUserPass = true
} }
// MARK: Server // MARK: Server
Regex.authToken.enumerateSpacedArguments(in: line) { Regex.authToken.enumerateSpacedArguments(in: line) {
optAuthToken = $0[0] optAuthToken = $0[0]
} }
Regex.peerId.enumerateSpacedArguments(in: line) { Regex.peerId.enumerateSpacedArguments(in: line) {
optPeerId = UInt32($0[0]) optPeerId = UInt32($0[0])
} }
// MARK: Routing // MARK: Routing
Regex.topology.enumerateSpacedArguments(in: line) { Regex.topology.enumerateSpacedArguments(in: line) {
optTopology = $0.first optTopology = $0.first
} }
@ -611,7 +611,7 @@ extension OpenVPN {
} }
Regex.route.enumerateSpacedArguments(in: line) { Regex.route.enumerateSpacedArguments(in: line) {
let routeEntryArguments = $0 let routeEntryArguments = $0
let address = routeEntryArguments[0] let address = routeEntryArguments[0]
let mask = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : "255.255.255.255" let mask = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : "255.255.255.255"
var gateway = (routeEntryArguments.count > 2) ? routeEntryArguments[2] : nil // defaultGateway4 var gateway = (routeEntryArguments.count > 2) ? routeEntryArguments[2] : nil // defaultGateway4
@ -625,7 +625,7 @@ extension OpenVPN {
} }
Regex.route6.enumerateSpacedArguments(in: line) { Regex.route6.enumerateSpacedArguments(in: line) {
let routeEntryArguments = $0 let routeEntryArguments = $0
let destinationComponents = routeEntryArguments[0].components(separatedBy: "/") let destinationComponents = routeEntryArguments[0].components(separatedBy: "/")
guard destinationComponents.count == 2 else { guard destinationComponents.count == 2 else {
return return
@ -633,7 +633,7 @@ extension OpenVPN {
guard let prefix = UInt8(destinationComponents[1]) else { guard let prefix = UInt8(destinationComponents[1]) else {
return return
} }
let destination = destinationComponents[0] let destination = destinationComponents[0]
var gateway = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : nil // defaultGateway6 var gateway = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : nil // defaultGateway6
if gateway == "vpn_gateway" { if gateway == "vpn_gateway" {
@ -687,7 +687,7 @@ extension OpenVPN {
switch $0[0] { switch $0[0] {
case "PROXY_HTTPS": case "PROXY_HTTPS":
optHTTPSProxy = Proxy($0[1], port) optHTTPSProxy = Proxy($0[1], port)
case "PROXY_HTTP": case "PROXY_HTTP":
optHTTPProxy = Proxy($0[1], port) optHTTPProxy = Proxy($0[1], port)
@ -717,7 +717,7 @@ extension OpenVPN {
Regex.routeNoPull.enumerateSpacedComponents(in: line) { _ in Regex.routeNoPull.enumerateSpacedComponents(in: line) { _ in
optRouteNoPull = true optRouteNoPull = true
} }
// MARK: Extra // MARK: Extra
Regex.xorInfo.enumerateSpacedArguments(in: line) { Regex.xorInfo.enumerateSpacedArguments(in: line) {
@ -747,14 +747,14 @@ extension OpenVPN {
return return
} }
} }
// //
if let error = unsupportedError { if let error = unsupportedError {
throw error throw error
} }
} }
if isClient { if isClient {
guard let _ = optCA else { guard let _ = optCA else {
throw ConfigurationError.missingConfiguration(option: "ca") throw ConfigurationError.missingConfiguration(option: "ca")
@ -763,9 +763,9 @@ extension OpenVPN {
throw ConfigurationError.missingConfiguration(option: "cipher or data-ciphers") throw ConfigurationError.missingConfiguration(option: "cipher or data-ciphers")
} }
} }
// MARK: Post-processing // MARK: Post-processing
// ensure that non-nil network settings also imply non-empty // ensure that non-nil network settings also imply non-empty
if let array = optRoutes4 { if let array = optRoutes4 {
assert(!array.isEmpty) assert(!array.isEmpty)
@ -784,11 +784,11 @@ extension OpenVPN {
} }
// //
var sessionBuilder = ConfigurationBuilder() var sessionBuilder = ConfigurationBuilder()
// MARK: General // MARK: General
sessionBuilder.cipher = optDataCiphersFallback ?? optCipher sessionBuilder.cipher = optDataCiphersFallback ?? optCipher
sessionBuilder.dataCiphers = optDataCiphers sessionBuilder.dataCiphers = optDataCiphers
sessionBuilder.digest = optDigest sessionBuilder.digest = optDigest
@ -797,7 +797,7 @@ extension OpenVPN {
sessionBuilder.ca = optCA sessionBuilder.ca = optCA
sessionBuilder.clientCertificate = optClientCertificate sessionBuilder.clientCertificate = optClientCertificate
sessionBuilder.authUserPass = authUserPass sessionBuilder.authUserPass = authUserPass
if let clientKey = optClientKey, clientKey.isEncrypted { if let clientKey = optClientKey, clientKey.isEncrypted {
// FIXME: remove dependency on TLSBox // FIXME: remove dependency on TLSBox
guard let passphrase = passphrase, !passphrase.isEmpty else { guard let passphrase = passphrase, !passphrase.isEmpty else {
@ -811,13 +811,13 @@ extension OpenVPN {
} else { } else {
sessionBuilder.clientKey = optClientKey sessionBuilder.clientKey = optClientKey
} }
if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy { if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy {
let optKey: StaticKey? let optKey: StaticKey?
switch strategy { switch strategy {
case .auth: case .auth:
optKey = StaticKey(lines: keyLines, direction: optKeyDirection) optKey = StaticKey(lines: keyLines, direction: optKeyDirection)
case .crypt: case .crypt:
optKey = StaticKey(lines: keyLines, direction: .client) optKey = StaticKey(lines: keyLines, direction: .client)
} }
@ -825,13 +825,13 @@ extension OpenVPN {
sessionBuilder.tlsWrap = TLSWrap(strategy: strategy, key: key) sessionBuilder.tlsWrap = TLSWrap(strategy: strategy, key: key)
} }
} }
sessionBuilder.keepAliveInterval = optKeepAliveSeconds sessionBuilder.keepAliveInterval = optKeepAliveSeconds
sessionBuilder.keepAliveTimeout = optKeepAliveTimeoutSeconds sessionBuilder.keepAliveTimeout = optKeepAliveTimeoutSeconds
sessionBuilder.renegotiatesAfter = optRenegotiateAfterSeconds sessionBuilder.renegotiatesAfter = optRenegotiateAfterSeconds
// MARK: Client // MARK: Client
optDefaultProto = optDefaultProto ?? .udp optDefaultProto = optDefaultProto ?? .udp
optDefaultPort = optDefaultPort ?? 1194 optDefaultPort = optDefaultPort ?? 1194
if !optRemotes.isEmpty { if !optRemotes.isEmpty {
@ -850,20 +850,20 @@ extension OpenVPN {
Endpoint($0.0, .init($0.2, $0.1)) Endpoint($0.0, .init($0.2, $0.1))
} }
} }
sessionBuilder.authUserPass = authUserPass sessionBuilder.authUserPass = authUserPass
sessionBuilder.checksEKU = optChecksEKU sessionBuilder.checksEKU = optChecksEKU
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
sessionBuilder.randomizeHostnames = optRandomizeHostnames sessionBuilder.randomizeHostnames = optRandomizeHostnames
sessionBuilder.mtu = optMTU sessionBuilder.mtu = optMTU
// MARK: Server // MARK: Server
sessionBuilder.authToken = optAuthToken sessionBuilder.authToken = optAuthToken
sessionBuilder.peerId = optPeerId sessionBuilder.peerId = optPeerId
// MARK: Routing // MARK: Routing
// //
// excerpts from OpenVPN manpage // excerpts from OpenVPN manpage
// //
@ -881,15 +881,15 @@ extension OpenVPN {
guard ifconfig4Arguments.count == 2 else { guard ifconfig4Arguments.count == 2 else {
throw ConfigurationError.malformed(option: "ifconfig takes 2 arguments") throw ConfigurationError.malformed(option: "ifconfig takes 2 arguments")
} }
let address4: String let address4: String
let addressMask4: String let addressMask4: String
let defaultGateway4: String let defaultGateway4: String
let topology = Topology(rawValue: optTopology ?? "") ?? .net30 let topology = Topology(rawValue: optTopology ?? "") ?? .net30
switch topology { switch topology {
case .subnet: case .subnet:
// default gateway required when topology is subnet // default gateway required when topology is subnet
guard let gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else { guard let gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else {
throw ConfigurationError.malformed(option: "route-gateway takes 1 argument") throw ConfigurationError.malformed(option: "route-gateway takes 1 argument")
@ -897,7 +897,7 @@ extension OpenVPN {
address4 = ifconfig4Arguments[0] address4 = ifconfig4Arguments[0]
addressMask4 = ifconfig4Arguments[1] addressMask4 = ifconfig4Arguments[1]
defaultGateway4 = gateway4Arguments[0] defaultGateway4 = gateway4Arguments[0]
default: default:
address4 = ifconfig4Arguments[0] address4 = ifconfig4Arguments[0]
addressMask4 = "255.255.255.255" addressMask4 = "255.255.255.255"
@ -913,7 +913,7 @@ extension OpenVPN {
sessionBuilder.routes4 = optRoutes4?.map { sessionBuilder.routes4 = optRoutes4?.map {
IPv4Settings.Route($0.0, $0.1, $0.2) IPv4Settings.Route($0.0, $0.1, $0.2)
} }
if let ifconfig6Arguments = optIfconfig6Arguments { if let ifconfig6Arguments = optIfconfig6Arguments {
guard ifconfig6Arguments.count == 2 else { guard ifconfig6Arguments.count == 2 else {
throw ConfigurationError.malformed(option: "ifconfig-ipv6 takes 2 arguments") throw ConfigurationError.malformed(option: "ifconfig-ipv6 takes 2 arguments")
@ -925,10 +925,10 @@ extension OpenVPN {
guard let addressPrefix6 = UInt8(address6Components[1]) else { guard let addressPrefix6 = UInt8(address6Components[1]) else {
throw ConfigurationError.malformed(option: "ifconfig-ipv6 address prefix must be a 8-bit number") throw ConfigurationError.malformed(option: "ifconfig-ipv6 address prefix must be a 8-bit number")
} }
let address6 = address6Components[0] let address6 = address6Components[0]
let defaultGateway6 = ifconfig6Arguments[1] let defaultGateway6 = ifconfig6Arguments[1]
sessionBuilder.ipv6 = IPv6Settings( sessionBuilder.ipv6 = IPv6Settings(
address: address6, address: address6,
addressPrefixLength: addressPrefix6, addressPrefixLength: addressPrefix6,
@ -938,7 +938,7 @@ extension OpenVPN {
sessionBuilder.routes6 = optRoutes6?.map { sessionBuilder.routes6 = optRoutes6?.map {
IPv6Settings.Route($0.0, $0.1, $0.2) IPv6Settings.Route($0.0, $0.1, $0.2)
} }
sessionBuilder.dnsServers = optDNSServers sessionBuilder.dnsServers = optDNSServers
sessionBuilder.dnsDomain = optDomain sessionBuilder.dnsDomain = optDomain
sessionBuilder.searchDomains = optSearchDomains sessionBuilder.searchDomains = optSearchDomains
@ -956,10 +956,10 @@ extension OpenVPN {
switch opt { switch opt {
case .def1: case .def1:
policies.insert(.IPv4) policies.insert(.IPv4)
case .ipv6: case .ipv6:
policies.insert(.IPv6) policies.insert(.IPv6)
case .blockLocal: case .blockLocal:
policies.insert(.blockLocal) policies.insert(.blockLocal)
@ -973,13 +973,13 @@ extension OpenVPN {
} }
sessionBuilder.routingPolicies = [RoutingPolicy](policies) sessionBuilder.routingPolicies = [RoutingPolicy](policies)
} }
// MARK: Extra // MARK: Extra
sessionBuilder.xorMethod = optXorMethod sessionBuilder.xorMethod = optXorMethod
// //
return Result( return Result(
url: originalURL, url: originalURL,
configuration: sessionBuilder.build(), configuration: sessionBuilder.build(),
@ -992,7 +992,7 @@ extension OpenVPN {
// if block.count >= 1 && block[0].contains("ENCRYPTED") { // if block.count >= 1 && block[0].contains("ENCRYPTED") {
// return true // return true
// } // }
// XXX: restore blank line after encryption header (easier than tweaking trimmedLines) // XXX: restore blank line after encryption header (easier than tweaking trimmedLines)
if block.count >= 3 && block[1].contains("Proc-Type") { if block.count >= 3 && block[1].contains("Proc-Type") {
block.insert("", at: 3) block.insert("", at: 3)

View File

@ -46,14 +46,14 @@ extension OpenVPN {
private static let begin = "-----BEGIN " private static let begin = "-----BEGIN "
private static let end = "-----END " private static let end = "-----END "
/// The content in PEM format (ASCII). /// The content in PEM format (ASCII).
public let pem: String public let pem: String
var isEncrypted: Bool { var isEncrypted: Bool {
return pem.contains("ENCRYPTED") return pem.contains("ENCRYPTED")
} }
public init(pem: String) { public init(pem: String) {
guard let beginRange = pem.range(of: CryptoContainer.begin) else { guard let beginRange = pem.range(of: CryptoContainer.begin) else {
self.pem = "" self.pem = ""
@ -61,7 +61,7 @@ extension OpenVPN {
} }
self.pem = String(pem[beginRange.lowerBound...]) self.pem = String(pem[beginRange.lowerBound...])
} }
func write(to url: URL) throws { func write(to url: URL) throws {
try pem.write(to: url, atomically: true, encoding: .ascii) try pem.write(to: url, atomically: true, encoding: .ascii)
} }
@ -73,13 +73,13 @@ extension OpenVPN {
} }
// MARK: Codable // MARK: Codable
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
let pem = try container.decode(String.self) let pem = try container.decode(String.self)
self.init(pem: pem) self.init(pem: pem)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer() var container = encoder.singleValueContainer()
try container.encode(pem) try container.encode(pem)

View File

@ -31,7 +31,7 @@ extension Error {
let te = self as NSError let te = self as NSError
return te.domain == OpenVPNErrorDomain return te.domain == OpenVPNErrorDomain
} }
public func openVPNErrorCode() -> OpenVPNErrorCode? { public func openVPNErrorCode() -> OpenVPNErrorCode? {
let te = self as NSError let te = self as NSError
guard te.domain == OpenVPNErrorDomain else { guard te.domain == OpenVPNErrorDomain else {

View File

@ -38,37 +38,37 @@ import Foundation
/// The possible errors raised/thrown during `OpenVPNSession` operation. /// The possible errors raised/thrown during `OpenVPNSession` operation.
public enum OpenVPNError: String, Error { public enum OpenVPNError: String, Error {
/// The negotiation timed out. /// The negotiation timed out.
case negotiationTimeout case negotiationTimeout
/// The VPN session id is missing. /// The VPN session id is missing.
case missingSessionId case missingSessionId
/// The VPN session id doesn't match. /// The VPN session id doesn't match.
case sessionMismatch case sessionMismatch
/// The connection key is wrong or wasn't expected. /// The connection key is wrong or wasn't expected.
case badKey case badKey
/// The control packet has an incorrect prefix payload. /// The control packet has an incorrect prefix payload.
case wrongControlDataPrefix case wrongControlDataPrefix
/// The provided credentials failed authentication. /// The provided credentials failed authentication.
case badCredentials case badCredentials
/// The PUSH_REPLY is multipart. /// The PUSH_REPLY is multipart.
case continuationPushReply case continuationPushReply
/// The reply to PUSH_REQUEST is malformed. /// The reply to PUSH_REQUEST is malformed.
case malformedPushReply case malformedPushReply
/// A write operation failed at the link layer (e.g. network unreachable). /// A write operation failed at the link layer (e.g. network unreachable).
case failedLinkWrite case failedLinkWrite
/// The server couldn't ping back before timeout. /// The server couldn't ping back before timeout.
case pingTimeout case pingTimeout
/// The session reached a stale state and can't be recovered. /// The session reached a stale state and can't be recovered.
case staleSession case staleSession

View File

@ -33,7 +33,7 @@ extension OpenVPN {
public struct StaticKey: Codable, Equatable { public struct StaticKey: Codable, Equatable {
enum CodingKeys: CodingKey { enum CodingKeys: CodingKey {
case data case data
case dir case dir
} }
@ -42,27 +42,27 @@ extension OpenVPN {
/// Conventional server direction (implicit for tls-crypt). /// Conventional server direction (implicit for tls-crypt).
case server = 0 case server = 0
/// Conventional client direction (implicit for tls-crypt). /// Conventional client direction (implicit for tls-crypt).
case client = 1 case client = 1
} }
private static let contentLength = 256 // 2048-bit private static let contentLength = 256 // 2048-bit
private static let keyCount = 4 private static let keyCount = 4
private static let keyLength = StaticKey.contentLength / StaticKey.keyCount private static let keyLength = StaticKey.contentLength / StaticKey.keyCount
private static let fileHead = "-----BEGIN OpenVPN Static key V1-----" private static let fileHead = "-----BEGIN OpenVPN Static key V1-----"
private static let fileFoot = "-----END OpenVPN Static key V1-----" private static let fileFoot = "-----END OpenVPN Static key V1-----"
private static let nonHexCharset = CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted private static let nonHexCharset = CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted
private let secureData: ZeroingData private let secureData: ZeroingData
public let direction: Direction? public let direction: Direction?
/// Returns the encryption key. /// Returns the encryption key.
/// ///
/// - Precondition: `direction` must be non-nil. /// - Precondition: `direction` must be non-nil.
@ -74,7 +74,7 @@ extension OpenVPN {
switch direction { switch direction {
case .server: case .server:
return key(at: 0) return key(at: 0)
case .client: case .client:
return key(at: 2) return key(at: 2)
} }
@ -91,12 +91,12 @@ extension OpenVPN {
switch direction { switch direction {
case .server: case .server:
return key(at: 2) return key(at: 2)
case .client: case .client:
return key(at: 0) return key(at: 0)
} }
} }
/// Returns the HMAC sending key. /// Returns the HMAC sending key.
/// ///
/// - Seealso: `ConfigurationBuilder.tlsWrap` /// - Seealso: `ConfigurationBuilder.tlsWrap`
@ -107,12 +107,12 @@ extension OpenVPN {
switch direction { switch direction {
case .server: case .server:
return key(at: 1) return key(at: 1)
case .client: case .client:
return key(at: 3) return key(at: 3)
} }
} }
/// Returns the HMAC receiving key. /// Returns the HMAC receiving key.
/// ///
/// - Seealso: `ConfigurationBuilder.tlsWrap` /// - Seealso: `ConfigurationBuilder.tlsWrap`
@ -123,12 +123,12 @@ extension OpenVPN {
switch direction { switch direction {
case .server: case .server:
return key(at: 3) return key(at: 3)
case .client: case .client:
return key(at: 1) return key(at: 1)
} }
} }
/** /**
Initializes with data and direction. Initializes with data and direction.
@ -140,7 +140,7 @@ extension OpenVPN {
secureData = Z(data) secureData = Z(data)
self.direction = direction self.direction = direction
} }
/** /**
Initializes with file content and direction. Initializes with file content and direction.
@ -151,7 +151,7 @@ extension OpenVPN {
let lines = file.split(separator: "\n") let lines = file.split(separator: "\n")
self.init(lines: lines, direction: direction) self.init(lines: lines, direction: direction)
} }
public init?(lines: [Substring], direction: Direction?) { public init?(lines: [Substring], direction: Direction?) {
var isHead = true var isHead = true
var hexLines: [Substring] = [] var hexLines: [Substring] = []
@ -187,10 +187,10 @@ extension OpenVPN {
return nil return nil
} }
let data = Data(hex: hex) let data = Data(hex: hex)
self.init(data: data, direction: direction) self.init(data: data, direction: direction)
} }
/** /**
Initializes as bidirectional. Initializes as bidirectional.
@ -199,41 +199,41 @@ extension OpenVPN {
public init(biData data: Data) { public init(biData data: Data) {
self.init(data: data, direction: nil) self.init(data: data, direction: nil)
} }
private func key(at: Int) -> ZeroingData { private func key(at: Int) -> ZeroingData {
let size = secureData.count / StaticKey.keyCount // 64 bytes each let size = secureData.count / StaticKey.keyCount // 64 bytes each
assert(size == StaticKey.keyLength) assert(size == StaticKey.keyLength)
return secureData.withOffset(at * size, count: size) return secureData.withOffset(at * size, count: size)
} }
public static func deserialized(_ data: Data) throws -> StaticKey { public static func deserialized(_ data: Data) throws -> StaticKey {
return try JSONDecoder().decode(StaticKey.self, from: data) return try JSONDecoder().decode(StaticKey.self, from: data)
} }
public func serialized() -> Data? { public func serialized() -> Data? {
return try? JSONEncoder().encode(self) return try? JSONEncoder().encode(self)
} }
// MARK: Equatable // MARK: Equatable
public static func ==(lhs: Self, rhs: Self) -> Bool { public static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.secureData.toData() == rhs.secureData.toData() return lhs.secureData.toData() == rhs.secureData.toData()
} }
// MARK: Codable // MARK: Codable
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
secureData = Z(try container.decode(Data.self, forKey: .data)) secureData = Z(try container.decode(Data.self, forKey: .data))
direction = try container.decodeIfPresent(Direction.self, forKey: .dir) direction = try container.decodeIfPresent(Direction.self, forKey: .dir)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(secureData.toData(), forKey: .data) try container.encode(secureData.toData(), forKey: .data)
try container.encodeIfPresent(direction, forKey: .dir) try container.encodeIfPresent(direction, forKey: .dir)
} }
public var hexString: String { public var hexString: String {
return secureData.toHex() return secureData.toHex()
} }

View File

@ -32,7 +32,7 @@ extension OpenVPN {
/// The wrapping strategy. /// The wrapping strategy.
public enum Strategy: String, Codable, Equatable { public enum Strategy: String, Codable, Equatable {
/// Authenticates payload (--tls-auth). /// Authenticates payload (--tls-auth).
case auth case auth
@ -42,10 +42,10 @@ extension OpenVPN {
/// The wrapping strategy. /// The wrapping strategy.
public let strategy: Strategy public let strategy: Strategy
/// The static encryption key. /// The static encryption key.
public let key: StaticKey public let key: StaticKey
public init(strategy: Strategy, key: StaticKey) { public init(strategy: Strategy, key: StaticKey) {
self.strategy = strategy self.strategy = strategy
self.key = key self.key = key
@ -54,7 +54,7 @@ extension OpenVPN {
public static func deserialized(_ data: Data) throws -> TLSWrap { public static func deserialized(_ data: Data) throws -> TLSWrap {
return try JSONDecoder().decode(TLSWrap.self, from: data) return try JSONDecoder().decode(TLSWrap.self, from: data)
} }
public func serialized() -> Data? { public func serialized() -> Data? {
return try? JSONEncoder().encode(self) return try? JSONEncoder().encode(self)
} }

View File

@ -27,19 +27,19 @@ import Foundation
import CTunnelKitOpenVPNCore import CTunnelKitOpenVPNCore
extension OpenVPN { extension OpenVPN {
/// The obfuscation method. /// The obfuscation method.
public enum XORMethod: Codable, Equatable { public enum XORMethod: Codable, Equatable {
/// XORs the bytes in each buffer with the given mask. /// XORs the bytes in each buffer with the given mask.
case xormask(mask: Data) case xormask(mask: Data)
/// XORs each byte with its position in the packet. /// XORs each byte with its position in the packet.
case xorptrpos case xorptrpos
/// Reverses the order of bytes in each buffer except for the first (abcde becomes aedcb). /// Reverses the order of bytes in each buffer except for the first (abcde becomes aedcb).
case reverse case reverse
/// Performs several of the above steps (xormask -> xorptrpos -> reverse -> xorptrpos). /// Performs several of the above steps (xormask -> xorptrpos -> reverse -> xorptrpos).
case obfuscate(mask: Data) case obfuscate(mask: Data)
@ -48,13 +48,13 @@ extension OpenVPN {
switch self { switch self {
case .xormask: case .xormask:
return .mask return .mask
case .xorptrpos: case .xorptrpos:
return .ptrPos return .ptrPos
case .reverse: case .reverse:
return .reverse return .reverse
case .obfuscate: case .obfuscate:
return .obfuscate return .obfuscate
} }
@ -65,10 +65,10 @@ extension OpenVPN {
switch self { switch self {
case .xormask(let mask): case .xormask(let mask):
return mask return mask
case .obfuscate(let mask): case .obfuscate(let mask):
return mask return mask
default: default:
return nil return nil
} }

View File

@ -46,34 +46,34 @@ extension OpenVPN {
case lastError = "OpenVPN.LastError" case lastError = "OpenVPN.LastError"
} }
/// Optional version identifier about the client pushed to server in peer-info as `IV_UI_VER`. /// Optional version identifier about the client pushed to server in peer-info as `IV_UI_VER`.
public var versionIdentifier: String? public var versionIdentifier: String?
/// The configuration title. /// The configuration title.
public let title: String public let title: String
/// The access group for shared data. /// The access group for shared data.
public let appGroup: String public let appGroup: String
/// The client configuration. /// The client configuration.
public let configuration: OpenVPN.Configuration public let configuration: OpenVPN.Configuration
/// The optional username. /// The optional username.
public var username: String? public var username: String?
/// Enables debugging. /// Enables debugging.
public var shouldDebug = false public var shouldDebug = false
/// Debug log path. /// Debug log path.
public var debugLogPath: String? = nil public var debugLogPath: String?
/// Optional debug log format (SwiftyBeaver format). /// Optional debug log format (SwiftyBeaver format).
public var debugLogFormat: String? = nil public var debugLogFormat: String?
/// Mask private data in debug log (default is `true`). /// Mask private data in debug log (default is `true`).
public var masksPrivateData = true public var masksPrivateData = true
public init(_ title: String, appGroup: String, configuration: OpenVPN.Configuration) { public init(_ title: String, appGroup: String, configuration: OpenVPN.Configuration) {
self.title = title self.title = title
self.appGroup = appGroup self.appGroup = appGroup
@ -142,7 +142,7 @@ extension OpenVPN.ProviderConfiguration {
public var lastError: OpenVPNProviderError? { public var lastError: OpenVPNProviderError? {
return defaults?.openVPNLastError return defaults?.openVPNLastError
} }
/** /**
The URL of the latest debug log. The URL of the latest debug log.
*/ */
@ -208,7 +208,7 @@ extension UserDefaults {
openVPNDataCountArray = [newValue.received, newValue.sent] openVPNDataCountArray = [newValue.received, newValue.sent]
} }
} }
@objc private var openVPNDataCountArray: [UInt]? { @objc private var openVPNDataCountArray: [UInt]? {
get { get {
return array(forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue) as? [UInt] return array(forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue) as? [UInt]
@ -217,7 +217,7 @@ extension UserDefaults {
set(newValue, forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue) set(newValue, forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue)
} }
} }
private func openVPNRemoveDataCountArray() { private func openVPNRemoveDataCountArray() {
removeObject(forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue) removeObject(forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue)
} }
@ -249,7 +249,7 @@ extension UserDefaults {
} }
} }
} }
public fileprivate(set) var openVPNLastError: OpenVPNProviderError? { public fileprivate(set) var openVPNLastError: OpenVPNProviderError? {
get { get {
guard let rawValue = string(forKey: OpenVPN.ProviderConfiguration.Keys.lastError.rawValue) else { guard let rawValue = string(forKey: OpenVPN.ProviderConfiguration.Keys.lastError.rawValue) else {

View File

@ -38,35 +38,35 @@ import Foundation
/// Mostly programming errors by host app. /// Mostly programming errors by host app.
public enum OpenVPNProviderConfigurationError: Error { public enum OpenVPNProviderConfigurationError: Error {
/// A field in the `OpenVPNProvider.Configuration` provided is incorrect or incomplete. /// A field in the `OpenVPNProvider.Configuration` provided is incorrect or incomplete.
case parameter(name: String) case parameter(name: String)
/// Credentials are missing or inaccessible. /// Credentials are missing or inaccessible.
case credentials(details: String) case credentials(details: String)
/// The pseudo-random number generator could not be initialized. /// The pseudo-random number generator could not be initialized.
case prngInitialization case prngInitialization
/// The TLS certificate could not be serialized. /// The TLS certificate could not be serialized.
case certificateSerialization case certificateSerialization
} }
/// The errors causing a tunnel disconnection. /// The errors causing a tunnel disconnection.
public enum OpenVPNProviderError: String, Error { public enum OpenVPNProviderError: String, Error {
/// Socket endpoint could not be resolved. /// Socket endpoint could not be resolved.
case dnsFailure case dnsFailure
/// No more endpoints available to try. /// No more endpoints available to try.
case exhaustedEndpoints case exhaustedEndpoints
/// Socket failed to reach active state. /// Socket failed to reach active state.
case socketActivity case socketActivity
/// Credentials authentication failed. /// Credentials authentication failed.
case authentication case authentication
/// TLS could not be initialized (e.g. malformed CA or client PEMs). /// TLS could not be initialized (e.g. malformed CA or client PEMs).
case tlsInitialization case tlsInitialization
@ -75,37 +75,37 @@ public enum OpenVPNProviderError: String, Error {
/// TLS handshake failed. /// TLS handshake failed.
case tlsHandshake case tlsHandshake
/// The encryption logic could not be initialized (e.g. PRNG, algorithms). /// The encryption logic could not be initialized (e.g. PRNG, algorithms).
case encryptionInitialization case encryptionInitialization
/// Data encryption/decryption failed. /// Data encryption/decryption failed.
case encryptionData case encryptionData
/// The LZO engine failed. /// The LZO engine failed.
case lzo case lzo
/// Server uses an unsupported compression algorithm. /// Server uses an unsupported compression algorithm.
case serverCompression case serverCompression
/// Tunnel timed out. /// Tunnel timed out.
case timeout case timeout
/// An error occurred at the link level. /// An error occurred at the link level.
case linkError case linkError
/// Network routing information is missing or incomplete. /// Network routing information is missing or incomplete.
case routing case routing
/// The current network changed (e.g. switched from WiFi to data connection). /// The current network changed (e.g. switched from WiFi to data connection).
case networkChanged case networkChanged
/// Default gateway could not be attained. /// Default gateway could not be attained.
case gatewayUnattainable case gatewayUnattainable
/// Remove server has shut down. /// Remove server has shut down.
case serverShutdown case serverShutdown
/// The server replied in an unexpected way. /// The server replied in an unexpected way.
case unexpectedReply case unexpectedReply
} }

View File

@ -53,28 +53,28 @@ fileprivate extension ZeroingData {
extension OpenVPN { extension OpenVPN {
class Authenticator { class Authenticator {
private var controlBuffer: ZeroingData private var controlBuffer: ZeroingData
private(set) var preMaster: ZeroingData private(set) var preMaster: ZeroingData
private(set) var random1: ZeroingData private(set) var random1: ZeroingData
private(set) var random2: ZeroingData private(set) var random2: ZeroingData
private(set) var serverRandom1: ZeroingData? private(set) var serverRandom1: ZeroingData?
private(set) var serverRandom2: ZeroingData? private(set) var serverRandom2: ZeroingData?
private(set) var username: ZeroingData? private(set) var username: ZeroingData?
private(set) var password: ZeroingData? private(set) var password: ZeroingData?
var withLocalOptions: Bool var withLocalOptions: Bool
init(_ username: String?, _ password: String?) throws { init(_ username: String?, _ password: String?) throws {
preMaster = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.preMasterLength) preMaster = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.preMasterLength)
random1 = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.randomLength) random1 = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.randomLength)
random2 = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.randomLength) random2 = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.randomLength)
// XXX: not 100% secure, can't erase input username/password // XXX: not 100% secure, can't erase input username/password
if let username = username, let password = password { if let username = username, let password = password {
self.username = Z(username, nullTerminated: true) self.username = Z(username, nullTerminated: true)
@ -83,12 +83,12 @@ extension OpenVPN {
self.username = nil self.username = nil
self.password = nil self.password = nil
} }
withLocalOptions = true withLocalOptions = true
controlBuffer = Z() controlBuffer = Z()
} }
func reset() { func reset() {
controlBuffer.zero() controlBuffer.zero()
preMaster.zero() preMaster.zero()
@ -99,18 +99,18 @@ extension OpenVPN {
username = nil username = nil
password = nil password = nil
} }
// MARK: Authentication request // MARK: Authentication request
// Ruby: on_tls_connect // Ruby: on_tls_connect
func putAuth(into: TLSBox, options: Configuration) throws { func putAuth(into: TLSBox, options: Configuration) throws {
let raw = Z(ProtocolMacros.tlsPrefix) let raw = Z(ProtocolMacros.tlsPrefix)
// local keys // local keys
raw.append(preMaster) raw.append(preMaster)
raw.append(random1) raw.append(random1)
raw.append(random2) raw.append(random2)
// options string // options string
let optsString: String let optsString: String
if withLocalOptions { if withLocalOptions {
@ -122,10 +122,10 @@ extension OpenVPN {
switch comp { switch comp {
case .compLZO: case .compLZO:
opts.append("comp-lzo") opts.append("comp-lzo")
case .compress: case .compress:
opts.append("compress") opts.append("compress")
default: default:
break break
} }
@ -147,7 +147,7 @@ extension OpenVPN {
} }
log.debug("TLS.auth: Local options: \(optsString)") log.debug("TLS.auth: Local options: \(optsString)")
raw.appendSized(Z(optsString, nullTerminated: true)) raw.appendSized(Z(optsString, nullTerminated: true))
// credentials // credentials
if let username = username, let password = password { if let username = username, let password = password {
raw.appendSized(username) raw.appendSized(username)
@ -169,40 +169,40 @@ extension OpenVPN {
} else { } else {
log.debug("TLS.auth: Put plaintext (\(raw.count) bytes)") log.debug("TLS.auth: Put plaintext (\(raw.count) bytes)")
} }
try into.putRawPlainText(raw.bytes, length: raw.count) try into.putRawPlainText(raw.bytes, length: raw.count)
} }
// MARK: Server replies // MARK: Server replies
func appendControlData(_ data: ZeroingData) { func appendControlData(_ data: ZeroingData) {
controlBuffer.append(data) controlBuffer.append(data)
} }
func parseAuthReply() throws -> Bool { func parseAuthReply() throws -> Bool {
let prefixLength = ProtocolMacros.tlsPrefix.count let prefixLength = ProtocolMacros.tlsPrefix.count
// TLS prefix + random (x2) + opts length [+ opts] // TLS prefix + random (x2) + opts length [+ opts]
guard (controlBuffer.count >= prefixLength + 2 * CoreConfiguration.OpenVPN.randomLength + 2) else { guard controlBuffer.count >= prefixLength + 2 * CoreConfiguration.OpenVPN.randomLength + 2 else {
return false return false
} }
let prefix = controlBuffer.withOffset(0, count: prefixLength) let prefix = controlBuffer.withOffset(0, count: prefixLength)
guard prefix.isEqual(to: ProtocolMacros.tlsPrefix) else { guard prefix.isEqual(to: ProtocolMacros.tlsPrefix) else {
throw OpenVPNError.wrongControlDataPrefix throw OpenVPNError.wrongControlDataPrefix
} }
var offset = ProtocolMacros.tlsPrefix.count var offset = ProtocolMacros.tlsPrefix.count
let serverRandom1 = controlBuffer.withOffset(offset, count: CoreConfiguration.OpenVPN.randomLength) let serverRandom1 = controlBuffer.withOffset(offset, count: CoreConfiguration.OpenVPN.randomLength)
offset += CoreConfiguration.OpenVPN.randomLength offset += CoreConfiguration.OpenVPN.randomLength
let serverRandom2 = controlBuffer.withOffset(offset, count: CoreConfiguration.OpenVPN.randomLength) let serverRandom2 = controlBuffer.withOffset(offset, count: CoreConfiguration.OpenVPN.randomLength)
offset += CoreConfiguration.OpenVPN.randomLength offset += CoreConfiguration.OpenVPN.randomLength
let serverOptsLength = Int(controlBuffer.networkUInt16Value(fromOffset: offset)) let serverOptsLength = Int(controlBuffer.networkUInt16Value(fromOffset: offset))
offset += 2 offset += 2
guard controlBuffer.count >= offset + serverOptsLength else { guard controlBuffer.count >= offset + serverOptsLength else {
return false return false
} }
@ -214,22 +214,22 @@ extension OpenVPN {
} else { } else {
log.debug("TLS.auth: Parsed server random") log.debug("TLS.auth: Parsed server random")
} }
if let serverOptsString = serverOpts.nullTerminatedString(fromOffset: 0) { if let serverOptsString = serverOpts.nullTerminatedString(fromOffset: 0) {
log.debug("TLS.auth: Parsed server options: \"\(serverOptsString)\"") log.debug("TLS.auth: Parsed server options: \"\(serverOptsString)\"")
} }
self.serverRandom1 = serverRandom1 self.serverRandom1 = serverRandom1
self.serverRandom2 = serverRandom2 self.serverRandom2 = serverRandom2
controlBuffer.remove(untilOffset: offset) controlBuffer.remove(untilOffset: offset)
return true return true
} }
func parseMessages() -> [String] { func parseMessages() -> [String] {
var messages = [String]() var messages = [String]()
var offset = 0 var offset = 0
while true { while true {
guard let msg = controlBuffer.nullTerminatedString(fromOffset: offset) else { guard let msg = controlBuffer.nullTerminatedString(fromOffset: offset) else {
break break

View File

@ -35,7 +35,7 @@ private let log = SwiftyBeaver.self
extension OpenVPN { extension OpenVPN {
class ControlChannelError: Error, CustomStringConvertible { class ControlChannelError: Error, CustomStringConvertible {
let description: String let description: String
init(_ message: String) { init(_ message: String) {
description = "\(String(describing: ControlChannelError.self))(\(message))" description = "\(String(describing: ControlChannelError.self))(\(message))"
} }
@ -43,9 +43,9 @@ extension OpenVPN {
class ControlChannel { class ControlChannel {
private let serializer: ControlChannelSerializer private let serializer: ControlChannelSerializer
private(set) var sessionId: Data? private(set) var sessionId: Data?
var remoteSessionId: Data? { var remoteSessionId: Data? {
didSet { didSet {
if let id = remoteSessionId { if let id = remoteSessionId {
@ -63,11 +63,11 @@ extension OpenVPN {
private var plainBuffer: ZeroingData private var plainBuffer: ZeroingData
private var dataCount: BidirectionalState<Int> private var dataCount: BidirectionalState<Int>
convenience init() { convenience init() {
self.init(serializer: PlainSerializer()) self.init(serializer: PlainSerializer())
} }
convenience init(withAuthKey key: StaticKey, digest: Digest) throws { convenience init(withAuthKey key: StaticKey, digest: Digest) throws {
self.init(serializer: try AuthSerializer(withKey: key, digest: digest)) self.init(serializer: try AuthSerializer(withKey: key, digest: digest))
} }
@ -75,7 +75,7 @@ extension OpenVPN {
convenience init(withCryptKey key: StaticKey) throws { convenience init(withCryptKey key: StaticKey) throws {
self.init(serializer: try CryptSerializer(withKey: key)) self.init(serializer: try CryptSerializer(withKey: key))
} }
private init(serializer: ControlChannelSerializer) { private init(serializer: ControlChannelSerializer) {
self.serializer = serializer self.serializer = serializer
sessionId = nil sessionId = nil
@ -86,7 +86,7 @@ extension OpenVPN {
plainBuffer = Z(count: TLSBoxMaxBufferLength) plainBuffer = Z(count: TLSBoxMaxBufferLength)
dataCount = BidirectionalState(withResetValue: 0) dataCount = BidirectionalState(withResetValue: 0)
} }
func reset(forNewSession: Bool) throws { func reset(forNewSession: Bool) throws {
if forNewSession { if forNewSession {
try sessionId = SecureRandom.data(length: PacketSessionIdLength) try sessionId = SecureRandom.data(length: PacketSessionIdLength)
@ -112,7 +112,7 @@ extension OpenVPN {
func enqueueInboundPacket(packet: ControlPacket) -> [ControlPacket] { func enqueueInboundPacket(packet: ControlPacket) -> [ControlPacket] {
queue.inbound.append(packet) queue.inbound.append(packet)
queue.inbound.sort { $0.packetId < $1.packetId } queue.inbound.sort { $0.packetId < $1.packetId }
var toHandle: [ControlPacket] = [] var toHandle: [ControlPacket] = []
for queuedPacket in queue.inbound { for queuedPacket in queue.inbound {
if queuedPacket.packetId < currentPacketId.inbound { if queuedPacket.packetId < currentPacketId.inbound {
@ -122,15 +122,15 @@ extension OpenVPN {
if queuedPacket.packetId != currentPacketId.inbound { if queuedPacket.packetId != currentPacketId.inbound {
continue continue
} }
toHandle.append(queuedPacket) toHandle.append(queuedPacket)
currentPacketId.inbound += 1 currentPacketId.inbound += 1
queue.inbound.removeFirst() queue.inbound.removeFirst()
} }
return toHandle return toHandle
} }
func enqueueOutboundPackets(withCode code: PacketCode, key: UInt8, payload: Data, maxPacketSize: Int) { func enqueueOutboundPackets(withCode code: PacketCode, key: UInt8, payload: Data, maxPacketSize: Int) {
guard let sessionId = sessionId else { guard let sessionId = sessionId else {
fatalError("Missing sessionId, do reset(forNewSession: true) first") fatalError("Missing sessionId, do reset(forNewSession: true) first")
@ -139,40 +139,40 @@ extension OpenVPN {
let oldIdOut = currentPacketId.outbound let oldIdOut = currentPacketId.outbound
var queuedCount = 0 var queuedCount = 0
var offset = 0 var offset = 0
repeat { repeat {
let subPayloadLength = min(maxPacketSize, payload.count - offset) let subPayloadLength = min(maxPacketSize, payload.count - offset)
let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength) let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength)
let packet = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: currentPacketId.outbound, payload: subPayloadData) let packet = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: currentPacketId.outbound, payload: subPayloadData)
queue.outbound.append(packet) queue.outbound.append(packet)
currentPacketId.outbound += 1 currentPacketId.outbound += 1
offset += maxPacketSize offset += maxPacketSize
queuedCount += subPayloadLength queuedCount += subPayloadLength
} while (offset < payload.count) } while (offset < payload.count)
assert(queuedCount == payload.count) assert(queuedCount == payload.count)
// packet count // packet count
let packetCount = currentPacketId.outbound - oldIdOut let packetCount = currentPacketId.outbound - oldIdOut
if (packetCount > 1) { if packetCount > 1 {
log.debug("Control: Enqueued \(packetCount) packets [\(oldIdOut)-\(currentPacketId.outbound - 1)]") log.debug("Control: Enqueued \(packetCount) packets [\(oldIdOut)-\(currentPacketId.outbound - 1)]")
} else { } else {
log.debug("Control: Enqueued 1 packet [\(oldIdOut)]") log.debug("Control: Enqueued 1 packet [\(oldIdOut)]")
} }
} }
func writeOutboundPackets() throws -> [Data] { func writeOutboundPackets() throws -> [Data] {
var rawList: [Data] = [] var rawList: [Data] = []
for packet in queue.outbound { for packet in queue.outbound {
if let sentDate = packet.sentDate { if let sentDate = packet.sentDate {
let timeAgo = -sentDate.timeIntervalSinceNow let timeAgo = -sentDate.timeIntervalSinceNow
guard (timeAgo >= CoreConfiguration.OpenVPN.retransmissionLimit) else { guard timeAgo >= CoreConfiguration.OpenVPN.retransmissionLimit else {
log.debug("Control: Skip writing packet with packetId \(packet.packetId) (sent on \(sentDate), \(timeAgo) seconds ago)") log.debug("Control: Skip writing packet with packetId \(packet.packetId) (sent on \(sentDate), \(timeAgo) seconds ago)")
continue continue
} }
} }
log.debug("Control: Write control packet \(packet)") log.debug("Control: Write control packet \(packet)")
let raw = try serializer.serialize(packet: packet) let raw = try serializer.serialize(packet: packet)
@ -185,11 +185,11 @@ extension OpenVPN {
// log.verbose("Packets now pending ack: \(pendingAcks)") // log.verbose("Packets now pending ack: \(pendingAcks)")
return rawList return rawList
} }
func hasPendingAcks() -> Bool { func hasPendingAcks() -> Bool {
return !pendingAcks.isEmpty return !pendingAcks.isEmpty
} }
// Ruby: handle_acks // Ruby: handle_acks
private func readAcks(_ packetIds: [UInt32], acksRemoteSessionId: Data) throws { private func readAcks(_ packetIds: [UInt32], acksRemoteSessionId: Data) throws {
guard let sessionId = sessionId else { guard let sessionId = sessionId else {
@ -199,18 +199,18 @@ extension OpenVPN {
log.error("Control: Ack session mismatch (\(acksRemoteSessionId.toHex()) != \(sessionId.toHex()))") log.error("Control: Ack session mismatch (\(acksRemoteSessionId.toHex()) != \(sessionId.toHex()))")
throw OpenVPNError.sessionMismatch throw OpenVPNError.sessionMismatch
} }
// drop queued out packets if ack-ed // drop queued out packets if ack-ed
queue.outbound.removeAll { queue.outbound.removeAll {
return packetIds.contains($0.packetId) return packetIds.contains($0.packetId)
} }
// remove ack-ed packets from pending // remove ack-ed packets from pending
pendingAcks.subtract(packetIds) pendingAcks.subtract(packetIds)
// log.verbose("Packets still pending ack: \(pendingAcks)") // log.verbose("Packets still pending ack: \(pendingAcks)")
} }
func writeAcks(withKey key: UInt8, ackPacketIds: [UInt32], ackRemoteSessionId: Data) throws -> Data { func writeAcks(withKey key: UInt8, ackPacketIds: [UInt32], ackRemoteSessionId: Data) throws -> Data {
guard let sessionId = sessionId else { guard let sessionId = sessionId else {
throw OpenVPNError.missingSessionId throw OpenVPNError.missingSessionId
@ -219,13 +219,13 @@ extension OpenVPN {
log.debug("Control: Write ack packet \(packet)") log.debug("Control: Write ack packet \(packet)")
return try serializer.serialize(packet: packet) return try serializer.serialize(packet: packet)
} }
func currentControlData(withTLS tls: TLSBox) throws -> ZeroingData { func currentControlData(withTLS tls: TLSBox) throws -> ZeroingData {
var length = 0 var length = 0
try tls.pullRawPlainText(plainBuffer.mutableBytes, length: &length) try tls.pullRawPlainText(plainBuffer.mutableBytes, length: &length)
return plainBuffer.withOffset(0, count: length) return plainBuffer.withOffset(0, count: length)
} }
func addReceivedDataCount(_ count: Int) { func addReceivedDataCount(_ count: Int) {
dataCount.inbound += count dataCount.inbound += count
} }
@ -233,7 +233,7 @@ extension OpenVPN {
func addSentDataCount(_ count: Int) { func addSentDataCount(_ count: Int) {
dataCount.outbound += count dataCount.outbound += count
} }
func currentDataCount() -> DataCount { func currentDataCount() -> DataCount {
return DataCount(UInt(dataCount.inbound), UInt(dataCount.outbound)) return DataCount(UInt(dataCount.inbound), UInt(dataCount.outbound))
} }

View File

@ -34,7 +34,7 @@ private let log = SwiftyBeaver.self
protocol ControlChannelSerializer { protocol ControlChannelSerializer {
func reset() func reset()
func serialize(packet: ControlPacket) throws -> Data func serialize(packet: ControlPacket) throws -> Data
func deserialize(data: Data, start: Int, end: Int?) throws -> ControlPacket func deserialize(data: Data, start: Int, end: Int?) throws -> ControlPacket
@ -44,15 +44,15 @@ extension OpenVPN.ControlChannel {
class PlainSerializer: ControlChannelSerializer { class PlainSerializer: ControlChannelSerializer {
func reset() { func reset() {
} }
func serialize(packet: ControlPacket) throws -> Data { func serialize(packet: ControlPacket) throws -> Data {
return packet.serialized() return packet.serialized()
} }
func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket { func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket {
var offset = start var offset = start
let end = end ?? packet.count let end = end ?? packet.count
guard end >= offset + PacketOpcodeLength else { guard end >= offset + PacketOpcodeLength else {
throw OpenVPN.ControlChannelError("Missing opcode") throw OpenVPN.ControlChannelError("Missing opcode")
} }
@ -64,7 +64,7 @@ extension OpenVPN.ControlChannel {
offset += PacketOpcodeLength offset += PacketOpcodeLength
log.debug("Control: Try read packet with code \(code) and key \(key)") log.debug("Control: Try read packet with code \(code) and key \(key)")
guard end >= offset + PacketSessionIdLength else { guard end >= offset + PacketSessionIdLength else {
throw OpenVPN.ControlChannelError("Missing sessionId") throw OpenVPN.ControlChannelError("Missing sessionId")
} }
@ -134,23 +134,23 @@ extension OpenVPN.ControlChannel {
extension OpenVPN.ControlChannel { extension OpenVPN.ControlChannel {
class AuthSerializer: ControlChannelSerializer { class AuthSerializer: ControlChannelSerializer {
private let encrypter: Encrypter private let encrypter: Encrypter
private let decrypter: Decrypter private let decrypter: Decrypter
private let prefixLength: Int private let prefixLength: Int
private let hmacLength: Int private let hmacLength: Int
private let authLength: Int private let authLength: Int
private let preambleLength: Int private let preambleLength: Int
private var currentReplayId: BidirectionalState<UInt32> private var currentReplayId: BidirectionalState<UInt32>
private let timestamp: UInt32 private let timestamp: UInt32
private let plain: PlainSerializer private let plain: PlainSerializer
init(withKey key: OpenVPN.StaticKey, digest: OpenVPN.Digest) throws { init(withKey key: OpenVPN.StaticKey, digest: OpenVPN.Digest) throws {
let crypto = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: digest.rawValue) let crypto = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: digest.rawValue)
try crypto.configure( try crypto.configure(
@ -161,40 +161,40 @@ extension OpenVPN.ControlChannel {
) )
encrypter = crypto.encrypter() encrypter = crypto.encrypter()
decrypter = crypto.decrypter() decrypter = crypto.decrypter()
prefixLength = PacketOpcodeLength + PacketSessionIdLength prefixLength = PacketOpcodeLength + PacketSessionIdLength
hmacLength = crypto.digestLength() hmacLength = crypto.digestLength()
authLength = hmacLength + PacketReplayIdLength + PacketReplayTimestampLength authLength = hmacLength + PacketReplayIdLength + PacketReplayTimestampLength
preambleLength = prefixLength + authLength preambleLength = prefixLength + authLength
currentReplayId = BidirectionalState(withResetValue: 1) currentReplayId = BidirectionalState(withResetValue: 1)
timestamp = UInt32(Date().timeIntervalSince1970) timestamp = UInt32(Date().timeIntervalSince1970)
plain = PlainSerializer() plain = PlainSerializer()
} }
func reset() { func reset() {
currentReplayId.reset() currentReplayId.reset()
} }
func serialize(packet: ControlPacket) throws -> Data { func serialize(packet: ControlPacket) throws -> Data {
return try serialize(packet: packet, timestamp: timestamp) return try serialize(packet: packet, timestamp: timestamp)
} }
func serialize(packet: ControlPacket, timestamp: UInt32) throws -> Data { func serialize(packet: ControlPacket, timestamp: UInt32) throws -> Data {
let data = try packet.serialized(withAuthenticator: encrypter, replayId: currentReplayId.outbound, timestamp: timestamp) let data = try packet.serialized(withAuthenticator: encrypter, replayId: currentReplayId.outbound, timestamp: timestamp)
currentReplayId.outbound += 1 currentReplayId.outbound += 1
return data return data
} }
// XXX: start/end are ignored, parses whole packet // XXX: start/end are ignored, parses whole packet
func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket { func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket {
let end = packet.count let end = packet.count
// data starts with (prefix=(header + sessionId) + auth=(hmac + replayId)) // data starts with (prefix=(header + sessionId) + auth=(hmac + replayId))
guard end >= preambleLength else { guard end >= preambleLength else {
throw OpenVPN.ControlChannelError("Missing HMAC") throw OpenVPN.ControlChannelError("Missing HMAC")
} }
// needs a copy for swapping // needs a copy for swapping
var authPacket = packet var authPacket = packet
let authCount = authPacket.count let authCount = authPacket.count
@ -203,9 +203,9 @@ extension OpenVPN.ControlChannel {
PacketSwapCopy(ptr, packet, prefixLength, authLength) PacketSwapCopy(ptr, packet, prefixLength, authLength)
try decrypter.verifyBytes(ptr, length: authCount, flags: nil) try decrypter.verifyBytes(ptr, length: authCount, flags: nil)
} }
// TODO: validate replay packet id // TODO: validate replay packet id
return try plain.deserialize(data: authPacket, start: authLength, end: nil) return try plain.deserialize(data: authPacket, start: authLength, end: nil)
} }
} }
@ -214,19 +214,19 @@ extension OpenVPN.ControlChannel {
extension OpenVPN.ControlChannel { extension OpenVPN.ControlChannel {
class CryptSerializer: ControlChannelSerializer { class CryptSerializer: ControlChannelSerializer {
private let encrypter: Encrypter private let encrypter: Encrypter
private let decrypter: Decrypter private let decrypter: Decrypter
private let headerLength: Int private let headerLength: Int
private var adLength: Int private var adLength: Int
private let tagLength: Int private let tagLength: Int
private var currentReplayId: BidirectionalState<UInt32> private var currentReplayId: BidirectionalState<UInt32>
private let timestamp: UInt32 private let timestamp: UInt32
private let plain: PlainSerializer private let plain: PlainSerializer
init(withKey key: OpenVPN.StaticKey) throws { init(withKey key: OpenVPN.StaticKey) throws {
@ -239,7 +239,7 @@ extension OpenVPN.ControlChannel {
) )
encrypter = crypto.encrypter() encrypter = crypto.encrypter()
decrypter = crypto.decrypter() decrypter = crypto.decrypter()
headerLength = PacketOpcodeLength + PacketSessionIdLength headerLength = PacketOpcodeLength + PacketSessionIdLength
adLength = headerLength + PacketReplayIdLength + PacketReplayTimestampLength adLength = headerLength + PacketReplayIdLength + PacketReplayTimestampLength
tagLength = crypto.tagLength() tagLength = crypto.tagLength()
@ -248,30 +248,30 @@ extension OpenVPN.ControlChannel {
timestamp = UInt32(Date().timeIntervalSince1970) timestamp = UInt32(Date().timeIntervalSince1970)
plain = PlainSerializer() plain = PlainSerializer()
} }
func reset() { func reset() {
currentReplayId.reset() currentReplayId.reset()
} }
func serialize(packet: ControlPacket) throws -> Data { func serialize(packet: ControlPacket) throws -> Data {
return try serialize(packet: packet, timestamp: timestamp) return try serialize(packet: packet, timestamp: timestamp)
} }
func serialize(packet: ControlPacket, timestamp: UInt32) throws -> Data { func serialize(packet: ControlPacket, timestamp: UInt32) throws -> Data {
let data = try packet.serialized(with: encrypter, replayId: currentReplayId.outbound, timestamp: timestamp, adLength: adLength) let data = try packet.serialized(with: encrypter, replayId: currentReplayId.outbound, timestamp: timestamp, adLength: adLength)
currentReplayId.outbound += 1 currentReplayId.outbound += 1
return data return data
} }
// XXX: start/end are ignored, parses whole packet // XXX: start/end are ignored, parses whole packet
func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket { func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket {
let end = end ?? packet.count let end = end ?? packet.count
// data starts with (ad=(header + sessionId + replayId) + tag) // data starts with (ad=(header + sessionId + replayId) + tag)
guard end >= start + adLength + tagLength else { guard end >= start + adLength + tagLength else {
throw OpenVPN.ControlChannelError("Missing AD+TAG") throw OpenVPN.ControlChannelError("Missing AD+TAG")
} }
let encryptedCount = packet.count - adLength let encryptedCount = packet.count - adLength
var decryptedPacket = Data(count: decrypter.encryptionCapacity(withLength: encryptedCount)) var decryptedPacket = Data(count: decrypter.encryptionCapacity(withLength: encryptedCount))
var decryptedCount = 0 var decryptedCount = 0
@ -285,9 +285,9 @@ extension OpenVPN.ControlChannel {
} }
} }
decryptedPacket.count = headerLength + decryptedCount decryptedPacket.count = headerLength + decryptedCount
// TODO: validate replay packet id // TODO: validate replay packet id
return try plain.deserialize(data: decryptedPacket, start: 0, end: nil) return try plain.deserialize(data: decryptedPacket, start: 0, end: nil)
} }
} }

View File

@ -41,29 +41,29 @@ import CTunnelKitOpenVPNProtocol
extension CoreConfiguration { extension CoreConfiguration {
struct OpenVPN { struct OpenVPN {
// MARK: Session // MARK: Session
static let usesReplayProtection = true static let usesReplayProtection = true
static let negotiationTimeout = 30.0 static let negotiationTimeout = 30.0
static let hardResetTimeout = 10.0 static let hardResetTimeout = 10.0
static let tickInterval = 0.2 static let tickInterval = 0.2
static let pushRequestInterval = 2.0 static let pushRequestInterval = 2.0
static let pingTimeoutCheckInterval = 10.0 static let pingTimeoutCheckInterval = 10.0
static let pingTimeout = 120.0 static let pingTimeout = 120.0
static let retransmissionLimit = 0.1 static let retransmissionLimit = 0.1
static let softNegotiationTimeout = 120.0 static let softNegotiationTimeout = 120.0
// MARK: Authentication // MARK: Authentication
static func peerInfo(extra: [String: String]? = nil) -> String { static func peerInfo(extra: [String: String]? = nil) -> String {
let platform: String let platform: String
let platformVersion = ProcessInfo.processInfo.operatingSystemVersion let platformVersion = ProcessInfo.processInfo.operatingSystemVersion
@ -79,7 +79,7 @@ extension CoreConfiguration {
"IV_UI_VER=\(uiVersion)", "IV_UI_VER=\(uiVersion)",
"IV_PROTO=2", "IV_PROTO=2",
"IV_NCP=2", "IV_NCP=2",
"IV_LZO_STUB=1", "IV_LZO_STUB=1"
] ]
if LZOFactory.isSupported() { if LZOFactory.isSupported() {
info.append("IV_LZO=1") info.append("IV_LZO=1")
@ -97,19 +97,19 @@ extension CoreConfiguration {
info.append("") info.append("")
return info.joined(separator: "\n") return info.joined(separator: "\n")
} }
static let randomLength = 32 static let randomLength = 32
// MARK: Keys // MARK: Keys
static let label1 = "OpenVPN master secret" static let label1 = "OpenVPN master secret"
static let label2 = "OpenVPN key expansion" static let label2 = "OpenVPN key expansion"
static let preMasterLength = 48 static let preMasterLength = 48
static let keyLength = 64 static let keyLength = 64
static let keysCount = 4 static let keysCount = 4
} }
} }

View File

@ -43,9 +43,9 @@ import CTunnelKitOpenVPNProtocol
extension OpenVPN { extension OpenVPN {
class EncryptionBridge { class EncryptionBridge {
private static let maxHmacLength = 100 private static let maxHmacLength = 100
private let box: CryptoBox private let box: CryptoBox
// Ruby: keys_prf // Ruby: keys_prf
private static func keysPRF( private static func keysPRF(
_ label: String, _ label: String,
@ -55,7 +55,7 @@ extension OpenVPN {
_ clientSessionId: Data?, _ clientSessionId: Data?,
_ serverSessionId: Data?, _ serverSessionId: Data?,
_ size: Int) throws -> ZeroingData { _ size: Int) throws -> ZeroingData {
let seed = Z(label, nullTerminated: false) let seed = Z(label, nullTerminated: false)
seed.append(clientSeed) seed.append(clientSeed)
seed.append(serverSeed) seed.append(serverSeed)
@ -69,36 +69,36 @@ extension OpenVPN {
let lenx = len + (secret.count & 1) let lenx = len + (secret.count & 1)
let secret1 = secret.withOffset(0, count: lenx) let secret1 = secret.withOffset(0, count: lenx)
let secret2 = secret.withOffset(len, count: lenx) let secret2 = secret.withOffset(len, count: lenx)
let hash1 = try keysHash("md5", secret1, seed, size) let hash1 = try keysHash("md5", secret1, seed, size)
let hash2 = try keysHash("sha1", secret2, seed, size) let hash2 = try keysHash("sha1", secret2, seed, size)
let prf = Z() let prf = Z()
for i in 0..<hash1.count { for i in 0..<hash1.count {
let h1 = hash1.bytes[i] let h1 = hash1.bytes[i]
let h2 = hash2.bytes[i] let h2 = hash2.bytes[i]
prf.append(Z(h1 ^ h2)) prf.append(Z(h1 ^ h2))
} }
return prf return prf
} }
// Ruby: keys_hash // Ruby: keys_hash
private static func keysHash(_ digestName: String, _ secret: ZeroingData, _ seed: ZeroingData, _ size: Int) throws -> ZeroingData { private static func keysHash(_ digestName: String, _ secret: ZeroingData, _ seed: ZeroingData, _ size: Int) throws -> ZeroingData {
let out = Z() let out = Z()
let buffer = Z(count: EncryptionBridge.maxHmacLength) let buffer = Z(count: EncryptionBridge.maxHmacLength)
var chain = try EncryptionBridge.hmac(buffer, digestName, secret, seed) var chain = try EncryptionBridge.hmac(buffer, digestName, secret, seed)
while (out.count < size) { while out.count < size {
out.append(try EncryptionBridge.hmac(buffer, digestName, secret, chain.appending(seed))) out.append(try EncryptionBridge.hmac(buffer, digestName, secret, chain.appending(seed)))
chain = try EncryptionBridge.hmac(buffer, digestName, secret, chain) chain = try EncryptionBridge.hmac(buffer, digestName, secret, chain)
} }
return out.withOffset(0, count: size) return out.withOffset(0, count: size)
} }
// Ruby: hmac // Ruby: hmac
private static func hmac(_ buffer: ZeroingData, _ digestName: String, _ secret: ZeroingData, _ data: ZeroingData) throws -> ZeroingData { private static func hmac(_ buffer: ZeroingData, _ digestName: String, _ secret: ZeroingData, _ data: ZeroingData) throws -> ZeroingData {
var length = 0 var length = 0
try CryptoBox.hmac( try CryptoBox.hmac(
withDigestName: digestName, withDigestName: digestName,
secret: secret.bytes, secret: secret.bytes,
@ -108,44 +108,44 @@ extension OpenVPN {
hmac: buffer.mutableBytes, hmac: buffer.mutableBytes,
hmacLength: &length hmacLength: &length
) )
return buffer.withOffset(0, count: length) return buffer.withOffset(0, count: length)
} }
convenience init(_ cipher: Cipher, _ digest: Digest, _ auth: Authenticator, convenience init(_ cipher: Cipher, _ digest: Digest, _ auth: Authenticator,
_ sessionId: Data, _ remoteSessionId: Data) throws { _ sessionId: Data, _ remoteSessionId: Data) throws {
guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else { guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else {
fatalError("Configuring encryption without server randoms") fatalError("Configuring encryption without server randoms")
} }
let masterData = try EncryptionBridge.keysPRF( let masterData = try EncryptionBridge.keysPRF(
CoreConfiguration.OpenVPN.label1, auth.preMaster, auth.random1, CoreConfiguration.OpenVPN.label1, auth.preMaster, auth.random1,
serverRandom1, nil, nil, serverRandom1, nil, nil,
CoreConfiguration.OpenVPN.preMasterLength CoreConfiguration.OpenVPN.preMasterLength
) )
let keysData = try EncryptionBridge.keysPRF( let keysData = try EncryptionBridge.keysPRF(
CoreConfiguration.OpenVPN.label2, masterData, auth.random2, CoreConfiguration.OpenVPN.label2, masterData, auth.random2,
serverRandom2, sessionId, remoteSessionId, serverRandom2, sessionId, remoteSessionId,
CoreConfiguration.OpenVPN.keysCount * CoreConfiguration.OpenVPN.keyLength CoreConfiguration.OpenVPN.keysCount * CoreConfiguration.OpenVPN.keyLength
) )
var keysArray = [ZeroingData]() var keysArray = [ZeroingData]()
for i in 0..<CoreConfiguration.OpenVPN.keysCount { for i in 0..<CoreConfiguration.OpenVPN.keysCount {
let offset = i * CoreConfiguration.OpenVPN.keyLength let offset = i * CoreConfiguration.OpenVPN.keyLength
let zbuf = keysData.withOffset(offset, count: CoreConfiguration.OpenVPN.keyLength) let zbuf = keysData.withOffset(offset, count: CoreConfiguration.OpenVPN.keyLength)
keysArray.append(zbuf) keysArray.append(zbuf)
} }
let cipherEncKey = keysArray[0] let cipherEncKey = keysArray[0]
let hmacEncKey = keysArray[1] let hmacEncKey = keysArray[1]
let cipherDecKey = keysArray[2] let cipherDecKey = keysArray[2]
let hmacDecKey = keysArray[3] let hmacDecKey = keysArray[3]
try self.init(cipher, digest, cipherEncKey, cipherDecKey, hmacEncKey, hmacDecKey) try self.init(cipher, digest, cipherEncKey, cipherDecKey, hmacEncKey, hmacDecKey)
} }
init(_ cipher: Cipher, _ digest: Digest, _ cipherEncKey: ZeroingData, _ cipherDecKey: ZeroingData, _ hmacEncKey: ZeroingData, _ hmacDecKey: ZeroingData) throws { init(_ cipher: Cipher, _ digest: Digest, _ cipherEncKey: ZeroingData, _ cipherDecKey: ZeroingData, _ hmacEncKey: ZeroingData, _ hmacDecKey: ZeroingData) throws {
box = CryptoBox(cipherAlgorithm: cipher.rawValue, digestAlgorithm: digest.rawValue) box = CryptoBox(cipherAlgorithm: cipher.rawValue, digestAlgorithm: digest.rawValue)
try box.configure( try box.configure(
@ -155,7 +155,7 @@ extension OpenVPN {
hmacDecKey: hmacDecKey hmacDecKey: hmacDecKey
) )
} }
func encrypter() -> DataPathEncrypter { func encrypter() -> DataPathEncrypter {
return box.encrypter().dataPathEncrypter() return box.encrypter().dataPathEncrypter()
} }

View File

@ -41,35 +41,35 @@ import TunnelKitOpenVPNCore
extension OpenVPNSession { extension OpenVPNSession {
struct PIAHardReset { struct PIAHardReset {
private static let obfuscationKeyLength = 3 private static let obfuscationKeyLength = 3
private static let magic = "53eo0rk92gxic98p1asgl5auh59r1vp4lmry1e3chzi100qntd" private static let magic = "53eo0rk92gxic98p1asgl5auh59r1vp4lmry1e3chzi100qntd"
private static let encodedFormat = "\(magic)crypto\t%@|%@\tca\t%@" private static let encodedFormat = "\(magic)crypto\t%@|%@\tca\t%@"
private let caMd5Digest: String private let caMd5Digest: String
private let cipherName: String private let cipherName: String
private let digestName: String private let digestName: String
init(caMd5Digest: String, cipher: OpenVPN.Cipher, digest: OpenVPN.Digest) { init(caMd5Digest: String, cipher: OpenVPN.Cipher, digest: OpenVPN.Digest) {
self.caMd5Digest = caMd5Digest self.caMd5Digest = caMd5Digest
cipherName = cipher.rawValue.lowercased() cipherName = cipher.rawValue.lowercased()
digestName = digest.rawValue.lowercased() digestName = digest.rawValue.lowercased()
} }
// Ruby: pia_settings // Ruby: pia_settings
func encodedData() throws -> Data { func encodedData() throws -> Data {
guard let plainData = String(format: PIAHardReset.encodedFormat, cipherName, digestName, caMd5Digest).data(using: .ascii) else { guard let plainData = String(format: PIAHardReset.encodedFormat, cipherName, digestName, caMd5Digest).data(using: .ascii) else {
fatalError("Unable to encode string to ASCII") fatalError("Unable to encode string to ASCII")
} }
let keyBytes = try SecureRandom.data(length: PIAHardReset.obfuscationKeyLength) let keyBytes = try SecureRandom.data(length: PIAHardReset.obfuscationKeyLength)
var encodedData = Data(keyBytes) var encodedData = Data(keyBytes)
for (i, b) in plainData.enumerated() { for (i, b) in plainData.enumerated() {
let keyChar = keyBytes[i % keyBytes.count] let keyChar = keyBytes[i % keyBytes.count]
let xorredB = b ^ keyChar let xorredB = b ^ keyChar
encodedData.append(xorredB) encodedData.append(xorredB)
} }
return encodedData return encodedData

View File

@ -45,7 +45,7 @@ private let log = SwiftyBeaver.self
/// Observes major events notified by a `OpenVPNSession`. /// Observes major events notified by a `OpenVPNSession`.
public protocol OpenVPNSessionDelegate: AnyObject { public protocol OpenVPNSessionDelegate: AnyObject {
/** /**
Called after starting a session. Called after starting a session.
@ -54,7 +54,7 @@ public protocol OpenVPNSessionDelegate: AnyObject {
- Parameter options: The pulled tunnel settings. - Parameter options: The pulled tunnel settings.
*/ */
func sessionDidStart(_: OpenVPNSession, remoteAddress: String, remoteProtocol: String?, options: OpenVPN.Configuration) func sessionDidStart(_: OpenVPNSession, remoteAddress: String, remoteProtocol: String?, options: OpenVPN.Configuration)
/** /**
Called after stopping a session. Called after stopping a session.
@ -69,22 +69,22 @@ public protocol OpenVPNSessionDelegate: AnyObject {
public class OpenVPNSession: Session { public class OpenVPNSession: Session {
private enum StopMethod { private enum StopMethod {
case shutdown case shutdown
case reconnect case reconnect
} }
private struct Caches { private struct Caches {
static let ca = "ca.pem" static let ca = "ca.pem"
} }
// MARK: Configuration // MARK: Configuration
/// The session base configuration. /// The session base configuration.
public let configuration: OpenVPN.Configuration public let configuration: OpenVPN.Configuration
/// The optional credentials. /// The optional credentials.
public var credentials: OpenVPN.Credentials? public var credentials: OpenVPN.Credentials?
private var keepAliveInterval: TimeInterval? { private var keepAliveInterval: TimeInterval? {
let interval: TimeInterval? let interval: TimeInterval?
if let negInterval = pushReply?.options.keepAliveInterval, negInterval > 0.0 { if let negInterval = pushReply?.options.keepAliveInterval, negInterval > 0.0 {
@ -96,7 +96,7 @@ public class OpenVPNSession: Session {
} }
return interval return interval
} }
private var keepAliveTimeout: TimeInterval { private var keepAliveTimeout: TimeInterval {
if let negTimeout = pushReply?.options.keepAliveTimeout, negTimeout > 0.0 { if let negTimeout = pushReply?.options.keepAliveTimeout, negTimeout > 0.0 {
return negTimeout return negTimeout
@ -106,16 +106,16 @@ public class OpenVPNSession: Session {
return CoreConfiguration.OpenVPN.pingTimeout return CoreConfiguration.OpenVPN.pingTimeout
} }
} }
/// An optional `OpenVPNSessionDelegate` for receiving session events. /// An optional `OpenVPNSessionDelegate` for receiving session events.
public weak var delegate: OpenVPNSessionDelegate? public weak var delegate: OpenVPNSessionDelegate?
// MARK: State // MARK: State
private let queue: DispatchQueue private let queue: DispatchQueue
private var tlsObserver: NSObjectProtocol? private var tlsObserver: NSObjectProtocol?
private var withLocalOptions: Bool private var withLocalOptions: Bool
private var keys: [UInt8: OpenVPN.SessionKey] private var keys: [UInt8: OpenVPN.SessionKey]
@ -123,29 +123,29 @@ public class OpenVPNSession: Session {
private var oldKeys: [OpenVPN.SessionKey] private var oldKeys: [OpenVPN.SessionKey]
private var negotiationKeyIdx: UInt8 private var negotiationKeyIdx: UInt8
private var currentKeyIdx: UInt8? private var currentKeyIdx: UInt8?
private var isRenegotiating: Bool private var isRenegotiating: Bool
private var negotiationKey: OpenVPN.SessionKey { private var negotiationKey: OpenVPN.SessionKey {
guard let key = keys[negotiationKeyIdx] else { guard let key = keys[negotiationKeyIdx] else {
fatalError("Keys are empty or index \(negotiationKeyIdx) not found in \(keys.keys)") fatalError("Keys are empty or index \(negotiationKeyIdx) not found in \(keys.keys)")
} }
return key return key
} }
private var currentKey: OpenVPN.SessionKey? { private var currentKey: OpenVPN.SessionKey? {
guard let i = currentKeyIdx else { guard let i = currentKeyIdx else {
return nil return nil
} }
return keys[i] return keys[i]
} }
private var link: LinkInterface? private var link: LinkInterface?
private var tunnel: TunnelInterface? private var tunnel: TunnelInterface?
private var isReliableLink: Bool { private var isReliableLink: Bool {
return link?.isReliable ?? false return link?.isReliable ?? false
} }
@ -153,24 +153,24 @@ public class OpenVPNSession: Session {
private var continuatedPushReplyMessage: String? private var continuatedPushReplyMessage: String?
private var pushReply: OpenVPN.PushReply? private var pushReply: OpenVPN.PushReply?
private var nextPushRequestDate: Date? private var nextPushRequestDate: Date?
private var connectedDate: Date? private var connectedDate: Date?
private var lastPing: BidirectionalState<Date> private var lastPing: BidirectionalState<Date>
private(set) var isStopping: Bool private(set) var isStopping: Bool
/// The optional reason why the session stopped. /// The optional reason why the session stopped.
public private(set) var stopError: Error? public private(set) var stopError: Error?
// MARK: Control // MARK: Control
private var controlChannel: OpenVPN.ControlChannel private var controlChannel: OpenVPN.ControlChannel
private var authenticator: OpenVPN.Authenticator? private var authenticator: OpenVPN.Authenticator?
// MARK: Caching // MARK: Caching
private let cachesURL: URL private let cachesURL: URL
@ -191,7 +191,7 @@ public class OpenVPNSession: Session {
guard let ca = configuration.ca else { guard let ca = configuration.ca else {
throw OpenVPN.ConfigurationError.missingConfiguration(option: "ca") throw OpenVPN.ConfigurationError.missingConfiguration(option: "ca")
} }
self.queue = queue self.queue = queue
self.configuration = configuration self.configuration = configuration
self.cachesURL = cachesURL self.cachesURL = cachesURL
@ -203,7 +203,7 @@ public class OpenVPNSession: Session {
isRenegotiating = false isRenegotiating = false
lastPing = BidirectionalState(withResetValue: Date.distantPast) lastPing = BidirectionalState(withResetValue: Date.distantPast)
isStopping = false isStopping = false
if let tlsWrap = configuration.tlsWrap { if let tlsWrap = configuration.tlsWrap {
switch tlsWrap.strategy { switch tlsWrap.strategy {
case .auth: case .auth:
@ -219,22 +219,22 @@ public class OpenVPNSession: Session {
// cache CA locally (mandatory for OpenSSL) // cache CA locally (mandatory for OpenSSL)
try ca.pem.write(to: caURL, atomically: true, encoding: .ascii) try ca.pem.write(to: caURL, atomically: true, encoding: .ascii)
} }
deinit { deinit {
cleanup() cleanup()
cleanupCache() cleanupCache()
} }
// MARK: Session // MARK: Session
public func setLink(_ link: LinkInterface) { public func setLink(_ link: LinkInterface) {
guard (self.link == nil) else { guard self.link == nil else {
log.warning("Link interface already set!") log.warning("Link interface already set!")
return return
} }
log.debug("Starting VPN session") log.debug("Starting VPN session")
// WARNING: runs in notification source queue (we know it's "queue", but better be safe than sorry) // WARNING: runs in notification source queue (we know it's "queue", but better be safe than sorry)
tlsObserver = NotificationCenter.default.addObserver(forName: .TLSBoxPeerVerificationError, object: nil, queue: nil) { (notification) in tlsObserver = NotificationCenter.default.addObserver(forName: .TLSBoxPeerVerificationError, object: nil, queue: nil) { (notification) in
let error = notification.userInfo?[OpenVPNErrorKey] as? Error let error = notification.userInfo?[OpenVPNErrorKey] as? Error
@ -242,18 +242,18 @@ public class OpenVPNSession: Session {
self.deferStop(.shutdown, error) self.deferStop(.shutdown, error)
} }
} }
self.link = link self.link = link
start() start()
} }
public func canRebindLink() -> Bool { public func canRebindLink() -> Bool {
// return (pushReply?.peerId != nil) // return (pushReply?.peerId != nil)
// FIXME: floating is currently unreliable // FIXME: floating is currently unreliable
return false return false
} }
public func rebindLink(_ link: LinkInterface) { public func rebindLink(_ link: LinkInterface) {
guard let _ = pushReply?.options.peerId else { guard let _ = pushReply?.options.peerId else {
log.warning("Session doesn't support link rebinding!") log.warning("Session doesn't support link rebinding!")
@ -269,7 +269,7 @@ public class OpenVPNSession: Session {
} }
public func setTunnel(tunnel: TunnelInterface) { public func setTunnel(tunnel: TunnelInterface) {
guard (self.tunnel == nil) else { guard self.tunnel == nil else {
log.warning("Tunnel interface already set!") log.warning("Tunnel interface already set!")
return return
} }
@ -283,11 +283,11 @@ public class OpenVPNSession: Session {
} }
return controlChannel.currentDataCount() return controlChannel.currentDataCount()
} }
public func serverConfiguration() -> Any? { public func serverConfiguration() -> Any? {
return pushReply?.options return pushReply?.options
} }
public func shutdown(error: Error?) { public func shutdown(error: Error?) {
guard !isStopping else { guard !isStopping else {
log.warning("Ignore stop request, already stopping!") log.warning("Ignore stop request, already stopping!")
@ -295,7 +295,7 @@ public class OpenVPNSession: Session {
} }
deferStop(.shutdown, error) deferStop(.shutdown, error)
} }
public func reconnect(error: Error?) { public func reconnect(error: Error?) {
guard !isStopping else { guard !isStopping else {
log.warning("Ignore stop request, already stopping!") log.warning("Ignore stop request, already stopping!")
@ -303,7 +303,7 @@ public class OpenVPNSession: Session {
} }
deferStop(.reconnect, error) deferStop(.reconnect, error)
} }
// Ruby: cleanup // Ruby: cleanup
public func cleanup() { public func cleanup() {
log.info("Cleaning up...") log.info("Cleaning up...")
@ -312,13 +312,13 @@ public class OpenVPNSession: Session {
NotificationCenter.default.removeObserver(observer) NotificationCenter.default.removeObserver(observer)
tlsObserver = nil tlsObserver = nil
} }
keys.removeAll() keys.removeAll()
oldKeys.removeAll() oldKeys.removeAll()
negotiationKeyIdx = 0 negotiationKeyIdx = 0
currentKeyIdx = nil currentKeyIdx = nil
isRenegotiating = false isRenegotiating = false
nextPushRequestDate = nil nextPushRequestDate = nil
connectedDate = nil connectedDate = nil
authenticator = nil authenticator = nil
@ -328,7 +328,7 @@ public class OpenVPNSession: Session {
if !(tunnel?.isPersistent ?? false) { if !(tunnel?.isPersistent ?? false) {
tunnel = nil tunnel = nil
} }
isStopping = false isStopping = false
stopError = nil stopError = nil
} }
@ -347,7 +347,7 @@ public class OpenVPNSession: Session {
loopLink() loopLink()
hardReset() hardReset()
} }
private func loopNegotiation() { private func loopNegotiation() {
guard let link = link else { guard let link = link else {
return return
@ -364,12 +364,12 @@ public class OpenVPNSession: Session {
doShutdown(error: OpenVPNError.negotiationTimeout) doShutdown(error: OpenVPNError.negotiationTimeout)
return return
} }
pushRequest() pushRequest()
if !isReliableLink { if !isReliableLink {
flushControlQueue() flushControlQueue()
} }
guard negotiationKey.controlState == .connected else { guard negotiationKey.controlState == .connected else {
queue.asyncAfter(deadline: .now() + CoreConfiguration.OpenVPN.tickInterval) { [weak self] in queue.asyncAfter(deadline: .now() + CoreConfiguration.OpenVPN.tickInterval) { [weak self] in
self?.loopNegotiation() self?.loopNegotiation()
@ -390,11 +390,11 @@ public class OpenVPNSession: Session {
} }
if let error = error { if let error = error {
log.error("Failed LINK read: \(error)") log.error("Failed LINK read: \(error)")
// XXX: why isn't the tunnel shutting down at this point? // XXX: why isn't the tunnel shutting down at this point?
return return
} }
if let packets = newPackets, !packets.isEmpty { if let packets = newPackets, !packets.isEmpty {
self?.maybeRenegotiate() self?.maybeRenegotiate()
@ -425,11 +425,11 @@ public class OpenVPNSession: Session {
log.warning("Discarding \(packets.count) LINK packets (should not handle)") log.warning("Discarding \(packets.count) LINK packets (should not handle)")
return return
} }
lastPing.inbound = Date() lastPing.inbound = Date()
var dataPacketsByKey = [UInt8: [Data]]() var dataPacketsByKey = [UInt8: [Data]]()
for packet in packets { for packet in packets {
// log.verbose("Received data from LINK (\(packet.count) bytes): \(packet.toHex())") // log.verbose("Received data from LINK (\(packet.count) bytes): \(packet.toHex())")
@ -445,7 +445,7 @@ public class OpenVPNSession: Session {
// log.verbose("Parsed packet with code \(code)") // log.verbose("Parsed packet with code \(code)")
var offset = 1 var offset = 1
if (code == .dataV2) { if code == .dataV2 {
guard packet.count >= offset + PacketPeerIdLength else { guard packet.count >= offset + PacketPeerIdLength else {
log.warning("Dropped malformed packet (missing peerId)") log.warning("Dropped malformed packet (missing peerId)")
continue continue
@ -491,7 +491,7 @@ public class OpenVPNSession: Session {
deferStop(.shutdown, OpenVPNError.staleSession) deferStop(.shutdown, OpenVPNError.staleSession)
return return
} }
case .softResetV1: case .softResetV1:
if !isRenegotiating { if !isRenegotiating {
softReset(isServerInitiated: true) softReset(isServerInitiated: true)
@ -518,7 +518,7 @@ public class OpenVPNSession: Session {
handleDataPackets(dataPackets, key: sessionKey) handleDataPackets(dataPackets, key: sessionKey)
} }
} }
// Ruby: recv_tun // Ruby: recv_tun
private func receiveTunnel(packets: [Data]) { private func receiveTunnel(packets: [Data]) {
guard shouldHandlePackets() else { guard shouldHandlePackets() else {
@ -527,13 +527,13 @@ public class OpenVPNSession: Session {
} }
sendDataPackets(packets) sendDataPackets(packets)
} }
// Ruby: ping // Ruby: ping
private func ping() { private func ping() {
guard currentKey?.controlState == .connected else { guard currentKey?.controlState == .connected else {
return return
} }
let now = Date() let now = Date()
guard now.timeIntervalSince(lastPing.inbound) <= keepAliveTimeout else { guard now.timeIntervalSince(lastPing.inbound) <= keepAliveTimeout else {
deferStop(.shutdown, OpenVPNError.pingTimeout) deferStop(.shutdown, OpenVPNError.pingTimeout)
@ -550,7 +550,7 @@ public class OpenVPNSession: Session {
// schedule even just to check for ping timeout // schedule even just to check for ping timeout
scheduleNextPing() scheduleNextPing()
} }
private func scheduleNextPing() { private func scheduleNextPing() {
let interval: TimeInterval let interval: TimeInterval
if let keepAliveInterval = keepAliveInterval { if let keepAliveInterval = keepAliveInterval {
@ -565,9 +565,9 @@ public class OpenVPNSession: Session {
self?.ping() self?.ping()
} }
} }
// MARK: Handshake // MARK: Handshake
// Ruby: reset_ctrl // Ruby: reset_ctrl
private func resetControlChannel(forNewSession: Bool) { private func resetControlChannel(forNewSession: Bool) {
authenticator = nil authenticator = nil
@ -577,7 +577,7 @@ public class OpenVPNSession: Session {
deferStop(.shutdown, e) deferStop(.shutdown, e)
} }
} }
// Ruby: hard_reset // Ruby: hard_reset
private func hardReset() { private func hardReset() {
log.debug("Send hard reset") log.debug("Send hard reset")
@ -598,7 +598,7 @@ public class OpenVPNSession: Session {
loopNegotiation() loopNegotiation()
enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: payload) enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: payload)
} }
private func hardResetPayload() -> Data? { private func hardResetPayload() -> Data? {
guard !(configuration.usesPIAPatches ?? false) else { guard !(configuration.usesPIAPatches ?? false) else {
guard let _ = configuration.ca else { guard let _ = configuration.ca else {
@ -621,7 +621,7 @@ public class OpenVPNSession: Session {
} }
return nil return nil
} }
// Ruby: soft_reset // Ruby: soft_reset
private func softReset(isServerInitiated: Bool) { private func softReset(isServerInitiated: Bool) {
guard !isRenegotiating else { guard !isRenegotiating else {
@ -633,7 +633,7 @@ public class OpenVPNSession: Session {
} else { } else {
log.debug("Send soft reset") log.debug("Send soft reset")
} }
resetControlChannel(forNewSession: false) resetControlChannel(forNewSession: false)
negotiationKeyIdx = max(1, (negotiationKeyIdx + 1) % OpenVPN.ProtocolMacros.numberOfKeys) negotiationKeyIdx = max(1, (negotiationKeyIdx + 1) % OpenVPN.ProtocolMacros.numberOfKeys)
let newKey = OpenVPN.SessionKey(id: UInt8(negotiationKeyIdx), timeout: CoreConfiguration.OpenVPN.softNegotiationTimeout) let newKey = OpenVPN.SessionKey(id: UInt8(negotiationKeyIdx), timeout: CoreConfiguration.OpenVPN.softNegotiationTimeout)
@ -647,13 +647,13 @@ public class OpenVPNSession: Session {
enqueueControlPackets(code: .softResetV1, key: UInt8(negotiationKeyIdx), payload: Data()) enqueueControlPackets(code: .softResetV1, key: UInt8(negotiationKeyIdx), payload: Data())
} }
} }
// Ruby: on_tls_connect // Ruby: on_tls_connect
private func onTLSConnect() { private func onTLSConnect() {
log.debug("TLS.connect: Handshake is complete") log.debug("TLS.connect: Handshake is complete")
negotiationKey.controlState = .preAuth negotiationKey.controlState = .preAuth
do { do {
authenticator = try OpenVPN.Authenticator(credentials?.username, pushReply?.options.authToken ?? credentials?.password) authenticator = try OpenVPN.Authenticator(credentials?.username, pushReply?.options.authToken ?? credentials?.password)
authenticator?.withLocalOptions = withLocalOptions authenticator?.withLocalOptions = withLocalOptions
@ -679,7 +679,7 @@ public class OpenVPNSession: Session {
log.debug("TLS.auth: Pulled ciphertext (\(cipherTextOut.count) bytes)") log.debug("TLS.auth: Pulled ciphertext (\(cipherTextOut.count) bytes)")
enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut)
} }
// Ruby: push_request // Ruby: push_request
private func pushRequest() { private func pushRequest() {
guard negotiationKey.controlState == .preIfConfig else { guard negotiationKey.controlState == .preIfConfig else {
@ -688,10 +688,10 @@ public class OpenVPNSession: Session {
guard let targetDate = nextPushRequestDate, Date() > targetDate else { guard let targetDate = nextPushRequestDate, Date() > targetDate else {
return return
} }
log.debug("TLS.ifconfig: Put plaintext (PUSH_REQUEST)") log.debug("TLS.ifconfig: Put plaintext (PUSH_REQUEST)")
try? negotiationKey.tls.putPlainText("PUSH_REQUEST\0") try? negotiationKey.tls.putPlainText("PUSH_REQUEST\0")
let cipherTextOut: Data let cipherTextOut: Data
do { do {
cipherTextOut = try negotiationKey.tls.pullCipherText() cipherTextOut = try negotiationKey.tls.pullCipherText()
@ -704,32 +704,32 @@ public class OpenVPNSession: Session {
log.verbose("TLS.ifconfig: Still can't pull ciphertext") log.verbose("TLS.ifconfig: Still can't pull ciphertext")
return return
} }
log.debug("TLS.ifconfig: Send pulled ciphertext (\(cipherTextOut.count) bytes)") log.debug("TLS.ifconfig: Send pulled ciphertext (\(cipherTextOut.count) bytes)")
enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut)
if isRenegotiating { if isRenegotiating {
completeConnection() completeConnection()
isRenegotiating = false isRenegotiating = false
} }
nextPushRequestDate = Date().addingTimeInterval(CoreConfiguration.OpenVPN.pushRequestInterval) nextPushRequestDate = Date().addingTimeInterval(CoreConfiguration.OpenVPN.pushRequestInterval)
} }
private func maybeRenegotiate() { private func maybeRenegotiate() {
guard let renegotiatesAfter = configuration.renegotiatesAfter, renegotiatesAfter > 0 else { guard let renegotiatesAfter = configuration.renegotiatesAfter, renegotiatesAfter > 0 else {
return return
} }
guard (negotiationKeyIdx == currentKeyIdx) else { guard negotiationKeyIdx == currentKeyIdx else {
return return
} }
let elapsed = -negotiationKey.startTime.timeIntervalSinceNow let elapsed = -negotiationKey.startTime.timeIntervalSinceNow
if (elapsed > renegotiatesAfter) { if elapsed > renegotiatesAfter {
log.debug("Renegotiating after \(elapsed.asTimeString)") log.debug("Renegotiating after \(elapsed.asTimeString)")
softReset(isServerInitiated: false) softReset(isServerInitiated: false)
} }
} }
private func completeConnection() { private func completeConnection() {
setupEncryption() setupEncryption()
authenticator?.reset() authenticator?.reset()
@ -737,7 +737,7 @@ public class OpenVPNSession: Session {
connectedDate = Date() connectedDate = Date()
transitionKeys() transitionKeys()
} }
// MARK: Control // MARK: Control
// Ruby: handle_ctrl_pkt // Ruby: handle_ctrl_pkt
@ -747,16 +747,16 @@ public class OpenVPNSession: Session {
// deferStop(.shutdown, OpenVPNError.badKey) // deferStop(.shutdown, OpenVPNError.badKey)
return return
} }
guard let _ = configuration.ca else { guard let _ = configuration.ca else {
log.error("Configuration doesn't have a CA") log.error("Configuration doesn't have a CA")
return return
} }
// start new TLS handshake // start new TLS handshake
if ((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) || if ((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) ||
((packet.code == .softResetV1) && (negotiationKey.state == .softReset)) { ((packet.code == .softResetV1) && (negotiationKey.state == .softReset)) {
if negotiationKey.state == .hardReset { if negotiationKey.state == .hardReset {
controlChannel.remoteSessionId = packet.sessionId controlChannel.remoteSessionId = packet.sessionId
} }
@ -811,7 +811,7 @@ public class OpenVPNSession: Session {
enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut)
} }
// exchange TLS ciphertext // exchange TLS ciphertext
else if ((packet.code == .controlV1) && (negotiationKey.state == .tls)) { else if (packet.code == .controlV1) && (negotiationKey.state == .tls) {
guard let remoteSessionId = controlChannel.remoteSessionId else { guard let remoteSessionId = controlChannel.remoteSessionId else {
log.error("No remote sessionId found in packet (control packets before server HARD_RESET)") log.error("No remote sessionId found in packet (control packets before server HARD_RESET)")
deferStop(.shutdown, OpenVPNError.missingSessionId) deferStop(.shutdown, OpenVPNError.missingSessionId)
@ -822,7 +822,7 @@ public class OpenVPNSession: Session {
deferStop(.shutdown, OpenVPNError.sessionMismatch) deferStop(.shutdown, OpenVPNError.sessionMismatch)
return return
} }
guard let cipherTextIn = packet.payload else { guard let cipherTextIn = packet.payload else {
log.warning("TLS.connect: Control packet with empty payload?") log.warning("TLS.connect: Control packet with empty payload?")
return return
@ -844,7 +844,7 @@ public class OpenVPNSession: Session {
} }
log.verbose("TLS.connect: No available ciphertext to pull") log.verbose("TLS.connect: No available ciphertext to pull")
} }
if negotiationKey.shouldOnTLSConnect() { if negotiationKey.shouldOnTLSConnect() {
onTLSConnect() onTLSConnect()
} }
@ -873,7 +873,7 @@ public class OpenVPNSession: Session {
auth.appendControlData(data) auth.appendControlData(data)
if (negotiationKey.controlState == .preAuth) { if negotiationKey.controlState == .preAuth {
do { do {
guard try auth.parseAuthReply() else { guard try auth.parseAuthReply() else {
return return
@ -882,13 +882,13 @@ public class OpenVPNSession: Session {
deferStop(.shutdown, e) deferStop(.shutdown, e)
return return
} }
negotiationKey.controlState = .preIfConfig negotiationKey.controlState = .preIfConfig
nextPushRequestDate = Date() nextPushRequestDate = Date()
pushRequest() pushRequest()
nextPushRequestDate?.addTimeInterval(isRenegotiating ? CoreConfiguration.OpenVPN.pushRequestInterval : CoreConfiguration.OpenVPN.retransmissionLimit) nextPushRequestDate?.addTimeInterval(isRenegotiating ? CoreConfiguration.OpenVPN.pushRequestInterval : CoreConfiguration.OpenVPN.retransmissionLimit)
} }
for message in auth.parseMessages() { for message in auth.parseMessages() {
if CoreConfiguration.logsSensitiveData { if CoreConfiguration.logsSensitiveData {
log.debug("Parsed control message (\(message.count) bytes): \"\(message)\"") log.debug("Parsed control message (\(message.count) bytes): \"\(message)\"")
@ -919,14 +919,14 @@ public class OpenVPNSession: Session {
deferStop(.shutdown, OpenVPNError.badCredentials) deferStop(.shutdown, OpenVPNError.badCredentials)
return return
} }
// disconnect on remote server restart (--explicit-exit-notify) // disconnect on remote server restart (--explicit-exit-notify)
guard !message.hasPrefix("RESTART") else { guard !message.hasPrefix("RESTART") else {
log.debug("Disconnecting due to server shutdown") log.debug("Disconnecting due to server shutdown")
deferStop(.shutdown, OpenVPNError.serverShutdown) deferStop(.shutdown, OpenVPNError.serverShutdown)
return return
} }
// handle authentication from now on // handle authentication from now on
guard negotiationKey.controlState == .preIfConfig else { guard negotiationKey.controlState == .preIfConfig else {
return return
@ -945,7 +945,7 @@ public class OpenVPNSession: Session {
} }
reply = optionalReply reply = optionalReply
log.debug("Received PUSH_REPLY: \"\(reply)\"") log.debug("Received PUSH_REPLY: \"\(reply)\"")
if let framing = reply.options.compressionFraming, let compression = reply.options.compressionAlgorithm { if let framing = reply.options.compressionFraming, let compression = reply.options.compressionAlgorithm {
switch compression { switch compression {
case .disabled: case .disabled:
@ -970,13 +970,13 @@ public class OpenVPNSession: Session {
deferStop(.shutdown, e) deferStop(.shutdown, e)
return return
} }
pushReply = reply pushReply = reply
guard reply.options.ipv4 != nil || reply.options.ipv6 != nil else { guard reply.options.ipv4 != nil || reply.options.ipv6 != nil else {
deferStop(.shutdown, OpenVPNError.noRouting) deferStop(.shutdown, OpenVPNError.noRouting)
return return
} }
completeConnection() completeConnection()
guard let remoteAddress = link?.remoteAddress else { guard let remoteAddress = link?.remoteAddress else {
@ -991,7 +991,7 @@ public class OpenVPNSession: Session {
scheduleNextPing() scheduleNextPing()
} }
// Ruby: transition_keys // Ruby: transition_keys
private func transitionKeys() { private func transitionKeys() {
if let key = currentKey { if let key = currentKey {
@ -1000,15 +1000,15 @@ public class OpenVPNSession: Session {
currentKeyIdx = negotiationKeyIdx currentKeyIdx = negotiationKeyIdx
cleanKeys() cleanKeys()
} }
// Ruby: clean_keys // Ruby: clean_keys
private func cleanKeys() { private func cleanKeys() {
while (oldKeys.count > 1) { while oldKeys.count > 1 {
let key = oldKeys.removeFirst() let key = oldKeys.removeFirst()
keys.removeValue(forKey: key.id) keys.removeValue(forKey: key.id)
} }
} }
// Ruby: q_ctrl // Ruby: q_ctrl
private func enqueueControlPackets(code: PacketCode, key: UInt8, payload: Data) { private func enqueueControlPackets(code: PacketCode, key: UInt8, payload: Data) {
guard let _ = link else { guard let _ = link else {
@ -1019,7 +1019,7 @@ public class OpenVPNSession: Session {
controlChannel.enqueueOutboundPackets(withCode: code, key: key, payload: payload, maxPacketSize: 1000) controlChannel.enqueueOutboundPackets(withCode: code, key: key, payload: payload, maxPacketSize: 1000)
flushControlQueue() flushControlQueue()
} }
// Ruby: flush_ctrl_q_out // Ruby: flush_ctrl_q_out
private func flushControlQueue() { private func flushControlQueue() {
let rawList: [Data] let rawList: [Data]
@ -1033,7 +1033,7 @@ public class OpenVPNSession: Session {
for raw in rawList { for raw in rawList {
log.debug("Send control packet (\(raw.count) bytes): \(raw.toHex())") log.debug("Send control packet (\(raw.count) bytes): \(raw.toHex())")
} }
// WARNING: runs in Network.framework queue // WARNING: runs in Network.framework queue
let writeLink = link let writeLink = link
link?.writePackets(rawList) { [weak self] (error) in link?.writePackets(rawList) { [weak self] (error) in
@ -1050,7 +1050,7 @@ public class OpenVPNSession: Session {
} }
} }
} }
// Ruby: setup_keys // Ruby: setup_keys
private func setupEncryption() { private func setupEncryption() {
guard let auth = authenticator else { guard let auth = authenticator else {
@ -1081,7 +1081,7 @@ public class OpenVPNSession: Session {
} else { } else {
log.debug("Set up encryption") log.debug("Set up encryption")
} }
let pushedCipher = pushReply.options.cipher let pushedCipher = pushReply.options.cipher
if let negCipher = pushedCipher { if let negCipher = pushedCipher {
log.info("\tNegotiated cipher: \(negCipher.rawValue)") log.info("\tNegotiated cipher: \(negCipher.rawValue)")
@ -1125,7 +1125,7 @@ public class OpenVPNSession: Session {
usesReplayProtection: CoreConfiguration.OpenVPN.usesReplayProtection usesReplayProtection: CoreConfiguration.OpenVPN.usesReplayProtection
) )
} }
// MARK: Data // MARK: Data
// Ruby: handle_data_pkt // Ruby: handle_data_pkt
@ -1149,7 +1149,7 @@ public class OpenVPNSession: Session {
deferStop(.reconnect, e) deferStop(.reconnect, e)
} }
} }
// Ruby: send_data_pkt // Ruby: send_data_pkt
private func sendDataPackets(_ packets: [Data]) { private func sendDataPackets(_ packets: [Data]) {
guard let key = currentKey else { guard let key = currentKey else {
@ -1163,7 +1163,7 @@ public class OpenVPNSession: Session {
guard !encryptedPackets.isEmpty else { guard !encryptedPackets.isEmpty else {
return return
} }
// WARNING: runs in Network.framework queue // WARNING: runs in Network.framework queue
controlChannel.addSentDataCount(encryptedPackets.flatCount) controlChannel.addSentDataCount(encryptedPackets.flatCount)
let writeLink = link let writeLink = link
@ -1189,12 +1189,12 @@ public class OpenVPNSession: Session {
deferStop(.reconnect, e) deferStop(.reconnect, e)
} }
} }
// MARK: Acks // MARK: Acks
private func handleAcks() { private func handleAcks() {
} }
// Ruby: send_ack // Ruby: send_ack
private func sendAck(for controlPacket: ControlPacket) { private func sendAck(for controlPacket: ControlPacket) {
log.debug("Send ack for received packetId \(controlPacket.packetId)") log.debug("Send ack for received packetId \(controlPacket.packetId)")
@ -1210,7 +1210,7 @@ public class OpenVPNSession: Session {
deferStop(.shutdown, e) deferStop(.shutdown, e)
return return
} }
// WARNING: runs in Network.framework queue // WARNING: runs in Network.framework queue
let writeLink = link let writeLink = link
link?.writePacket(raw) { [weak self] (error) in link?.writePacket(raw) { [weak self] (error) in
@ -1228,13 +1228,13 @@ public class OpenVPNSession: Session {
} }
} }
} }
// MARK: Stop // MARK: Stop
private func shouldHandlePackets() -> Bool { private func shouldHandlePackets() -> Bool {
return !isStopping && !keys.isEmpty return !isStopping && !keys.isEmpty
} }
private func deferStop(_ method: StopMethod, _ error: Error?) { private func deferStop(_ method: StopMethod, _ error: Error?) {
guard !isStopping else { guard !isStopping else {
return return
@ -1246,7 +1246,7 @@ public class OpenVPNSession: Session {
case .shutdown: case .shutdown:
self?.doShutdown(error: error) self?.doShutdown(error: error)
self?.cleanupCache() self?.cleanupCache()
case .reconnect: case .reconnect:
self?.doReconnect(error: error) self?.doReconnect(error: error)
} }
@ -1259,7 +1259,7 @@ public class OpenVPNSession: Session {
completion() completion()
return return
} }
link.writePackets(packets) { [weak self] (error) in link.writePackets(packets) { [weak self] (_) in
self?.queue.sync { self?.queue.sync {
completion() completion()
} }
@ -1271,7 +1271,7 @@ public class OpenVPNSession: Session {
completion() completion()
} }
} }
private func doShutdown(error: Error?) { private func doShutdown(error: Error?) {
if let error = error { if let error = error {
log.error("Trigger shutdown (error: \(error))") log.error("Trigger shutdown (error: \(error))")
@ -1281,7 +1281,7 @@ public class OpenVPNSession: Session {
stopError = error stopError = error
delegate?.sessionDidStop(self, withError: error, shouldReconnect: false) delegate?.sessionDidStop(self, withError: error, shouldReconnect: false)
} }
private func doReconnect(error: Error?) { private func doReconnect(error: Error?) {
if let error = error { if let error = error {
log.error("Trigger reconnection (error: \(error))") log.error("Trigger reconnection (error: \(error))")

View File

@ -69,7 +69,7 @@ extension OpenVPN {
enum OCCPacket: UInt8 { enum OCCPacket: UInt8 {
case exit = 0x06 case exit = 0x06
private static let magicString = Data(hex: "287f346bd4ef7a812d56b8d3afc5459c") private static let magicString = Data(hex: "287f346bd4ef7a812d56b8d3afc5459c")
func serialized(_ info: Any? = nil) -> Data { func serialized(_ info: Any? = nil) -> Data {

View File

@ -39,7 +39,7 @@ import TunnelKitOpenVPNCore
extension OpenVPN { extension OpenVPN {
class ProtocolMacros { class ProtocolMacros {
// UInt32(0) + UInt8(KeyMethod = 2) // UInt32(0) + UInt8(KeyMethod = 2)
static let tlsPrefix = Data(hex: "0000000002") static let tlsPrefix = Data(hex: "0000000002")

View File

@ -40,11 +40,11 @@ import TunnelKitOpenVPNCore
extension OpenVPN { extension OpenVPN {
struct PushReply: CustomStringConvertible { struct PushReply: CustomStringConvertible {
private static let prefix = "PUSH_REPLY," private static let prefix = "PUSH_REPLY,"
private let original: String private let original: String
let options: Configuration let options: Configuration
init?(message: String) throws { init?(message: String) throws {
guard message.hasPrefix(PushReply.prefix) else { guard message.hasPrefix(PushReply.prefix) else {
return nil return nil
@ -57,15 +57,15 @@ extension OpenVPN {
let lines = original.components(separatedBy: ",") let lines = original.components(separatedBy: ",")
options = try ConfigurationParser.parsed(fromLines: lines).configuration options = try ConfigurationParser.parsed(fromLines: lines).configuration
} }
// MARK: CustomStringConvertible // MARK: CustomStringConvertible
var description: String { var description: String {
let stripped = NSMutableString(string: original) let stripped = NSMutableString(string: original)
ConfigurationParser.Regex.authToken.replaceMatches( ConfigurationParser.Regex.authToken.replaceMatches(
in: stripped, in: stripped,
options: [], options: [],
range: NSMakeRange(0, stripped.length), range: NSRange(location: 0, length: stripped.length),
withTemplate: "auth-token" withTemplate: "auth-token"
) )
return stripped as String return stripped as String

View File

@ -48,21 +48,21 @@ extension OpenVPN {
enum State { enum State {
case invalid, hardReset, softReset, tls case invalid, hardReset, softReset, tls
} }
enum ControlState { enum ControlState {
case preAuth, preIfConfig, connected case preAuth, preIfConfig, connected
} }
let id: UInt8 // 3-bit let id: UInt8 // 3-bit
let timeout: TimeInterval let timeout: TimeInterval
let startTime: Date let startTime: Date
var state = State.invalid var state = State.invalid
var controlState: ControlState? var controlState: ControlState?
var tlsOptional: TLSBox? var tlsOptional: TLSBox?
var tls: TLSBox { var tls: TLSBox {
@ -71,11 +71,11 @@ extension OpenVPN {
} }
return tls return tls
} }
var dataPath: DataPath? var dataPath: DataPath?
private var isTLSConnected: Bool private var isTLSConnected: Bool
init(id: UInt8, timeout: TimeInterval) { init(id: UInt8, timeout: TimeInterval) {
self.id = id self.id = id
self.timeout = timeout self.timeout = timeout
@ -89,12 +89,12 @@ extension OpenVPN {
func didHardResetTimeOut(link: LinkInterface) -> Bool { func didHardResetTimeOut(link: LinkInterface) -> Bool {
return ((state == .hardReset) && (-startTime.timeIntervalSinceNow > CoreConfiguration.OpenVPN.hardResetTimeout)) return ((state == .hardReset) && (-startTime.timeIntervalSinceNow > CoreConfiguration.OpenVPN.hardResetTimeout))
} }
// Ruby: Key.negotiate_timeout // Ruby: Key.negotiate_timeout
func didNegotiationTimeOut(link: LinkInterface) -> Bool { func didNegotiationTimeOut(link: LinkInterface) -> Bool {
return ((controlState != .connected) && (-startTime.timeIntervalSinceNow > timeout)) return ((controlState != .connected) && (-startTime.timeIntervalSinceNow > timeout))
} }
// Ruby: Key.on_tls_connect // Ruby: Key.on_tls_connect
func shouldOnTLSConnect() -> Bool { func shouldOnTLSConnect() -> Bool {
guard !isTLSConnected else { guard !isTLSConnected else {
@ -105,7 +105,7 @@ extension OpenVPN {
} }
return isTLSConnected return isTLSConnected
} }
func encrypt(packets: [Data]) throws -> [Data]? { func encrypt(packets: [Data]) throws -> [Data]? {
guard let dataPath = dataPath else { guard let dataPath = dataPath else {
log.warning("Data: Set dataPath first") log.warning("Data: Set dataPath first")
@ -113,7 +113,7 @@ extension OpenVPN {
} }
return try dataPath.encryptPackets(packets, key: id) return try dataPath.encryptPackets(packets, key: id)
} }
func decrypt(packets: [Data]) throws -> [Data]? { func decrypt(packets: [Data]) throws -> [Data]? {
guard let dataPath = dataPath else { guard let dataPath = dataPath else {
log.warning("Data: Set dataPath first") log.warning("Data: Set dataPath first")

View File

@ -29,11 +29,11 @@ import TunnelKitOpenVPNCore
/// Processes data packets according to a XOR method. /// Processes data packets according to a XOR method.
public struct XORProcessor { public struct XORProcessor {
private let method: OpenVPN.XORMethod? private let method: OpenVPN.XORMethod?
public init(method: OpenVPN.XORMethod?) { public init(method: OpenVPN.XORMethod?) {
self.method = method self.method = method
} }
/** /**
Returns an array of data packets processed according to XOR method. Returns an array of data packets processed according to XOR method.
@ -49,7 +49,7 @@ public struct XORProcessor {
processPacket($0, outbound: outbound) processPacket($0, outbound: outbound)
} }
} }
/** /**
Returns a data packet processed according to XOR method. Returns a data packet processed according to XOR method.
@ -64,13 +64,13 @@ public struct XORProcessor {
switch method { switch method {
case .xormask(let mask): case .xormask(let mask):
return Self.xormask(packet: packet, mask: mask) return Self.xormask(packet: packet, mask: mask)
case .xorptrpos: case .xorptrpos:
return Self.xorptrpos(packet: packet) return Self.xorptrpos(packet: packet)
case .reverse: case .reverse:
return Self.reverse(packet: packet) return Self.reverse(packet: packet)
case .obfuscate(let mask): case .obfuscate(let mask):
if outbound { if outbound {
return Self.xormask(packet: Self.xorptrpos(packet: Self.reverse(packet: Self.xorptrpos(packet: packet))), mask: mask) return Self.xormask(packet: Self.xorptrpos(packet: Self.reverse(packet: Self.xorptrpos(packet: packet))), mask: mask)
@ -87,13 +87,13 @@ extension XORProcessor {
byte ^ [UInt8](mask)[index % mask.count] byte ^ [UInt8](mask)[index % mask.count]
}) })
} }
private static func xorptrpos(packet: Data) -> Data { private static func xorptrpos(packet: Data) -> Data {
Data(packet.enumerated().map { (index, byte) in Data(packet.enumerated().map { (index, byte) in
byte ^ UInt8(truncatingIfNeeded: index &+ 1) byte ^ UInt8(truncatingIfNeeded: index &+ 1)
}) })
} }
private static func reverse(packet: Data) -> Data { private static func reverse(packet: Data) -> Data {
Data(([UInt8](packet))[0..<1] + ([UInt8](packet)[1...]).reversed()) Data(([UInt8](packet))[0..<1] + ([UInt8](packet)[1...]).reversed())
} }

View File

@ -23,7 +23,7 @@ open class WireGuardTunnelProvider: NEPacketTunnelProvider {
open override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { open override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
// BEGIN: TunnelKit // BEGIN: TunnelKit
guard let tunnelProviderProtocol = protocolConfiguration as? NETunnelProviderProtocol else { guard let tunnelProviderProtocol = protocolConfiguration as? NETunnelProviderProtocol else {
fatalError("Not a NETunnelProviderProtocol") fatalError("Not a NETunnelProviderProtocol")
} }
@ -39,7 +39,7 @@ open class WireGuardTunnelProvider: NEPacketTunnelProvider {
completionHandler(WireGuardProviderError.savedProtocolConfigurationIsInvalid) completionHandler(WireGuardProviderError.savedProtocolConfigurationIsInvalid)
return return
} }
configureLogging() configureLogging()
// END: TunnelKit // END: TunnelKit
@ -128,7 +128,7 @@ extension WireGuardTunnelProvider {
private func configureLogging() { private func configureLogging() {
let logLevel: SwiftyBeaver.Level = (cfg.shouldDebug ? .debug : .info) let logLevel: SwiftyBeaver.Level = (cfg.shouldDebug ? .debug : .info)
let logFormat = cfg.debugLogFormat ?? "$Dyyyy-MM-dd HH:mm:ss.SSS$d $L $N.$F:$l - $M" let logFormat = cfg.debugLogFormat ?? "$Dyyyy-MM-dd HH:mm:ss.SSS$d $L $N.$F:$l - $M"
if cfg.shouldDebug { if cfg.shouldDebug {
let console = ConsoleDestination() let console = ConsoleDestination()
console.useNSLog = true console.useNSLog = true

View File

@ -29,15 +29,15 @@ import NetworkExtension
public protocol WireGuardConfigurationProviding { public protocol WireGuardConfigurationProviding {
var interface: InterfaceConfiguration { get } var interface: InterfaceConfiguration { get }
var peers: [PeerConfiguration] { get } var peers: [PeerConfiguration] { get }
var privateKey: String { get } var privateKey: String { get }
var publicKey: String { get } var publicKey: String { get }
var addresses: [String] { get } var addresses: [String] { get }
var dnsServers: [String] { get } var dnsServers: [String] { get }
var dnsSearchDomains: [String] { get } var dnsSearchDomains: [String] { get }
@ -64,13 +64,13 @@ public protocol WireGuardConfigurationProviding {
extension WireGuard { extension WireGuard {
public struct ConfigurationBuilder: WireGuardConfigurationProviding { public struct ConfigurationBuilder: WireGuardConfigurationProviding {
private static let defaultGateway4 = IPAddressRange(from: "0.0.0.0/0")! private static let defaultGateway4 = IPAddressRange(from: "0.0.0.0/0")!
private static let defaultGateway6 = IPAddressRange(from: "::/0")! private static let defaultGateway6 = IPAddressRange(from: "::/0")!
public private(set) var interface: InterfaceConfiguration public private(set) var interface: InterfaceConfiguration
public private(set) var peers: [PeerConfiguration] public private(set) var peers: [PeerConfiguration]
public init() { public init() {
self.init(PrivateKey()) self.init(PrivateKey())
} }
@ -81,19 +81,19 @@ extension WireGuard {
} }
self.init(privateKey) self.init(privateKey)
} }
private init(_ privateKey: PrivateKey) { private init(_ privateKey: PrivateKey) {
interface = InterfaceConfiguration(privateKey: privateKey) interface = InterfaceConfiguration(privateKey: privateKey)
peers = [] peers = []
} }
public init(_ tunnelConfiguration: TunnelConfiguration) { public init(_ tunnelConfiguration: TunnelConfiguration) {
interface = tunnelConfiguration.interface interface = tunnelConfiguration.interface
peers = tunnelConfiguration.peers peers = tunnelConfiguration.peers
} }
// MARK: WireGuardConfigurationProviding // MARK: WireGuardConfigurationProviding
public var privateKey: String { public var privateKey: String {
get { get {
interface.privateKey.base64Key interface.privateKey.base64Key
@ -114,7 +114,7 @@ extension WireGuard {
interface.addresses = newValue.compactMap(IPAddressRange.init) interface.addresses = newValue.compactMap(IPAddressRange.init)
} }
} }
public var dnsServers: [String] { public var dnsServers: [String] {
get { get {
interface.dns.map(\.stringRepresentation) interface.dns.map(\.stringRepresentation)
@ -159,7 +159,7 @@ extension WireGuard {
interface.mtu = newValue interface.mtu = newValue
} }
} }
// MARK: Modification // MARK: Modification
public mutating func addPeer(_ base64PublicKey: String, endpoint: String, allowedIPs: [String] = []) throws { public mutating func addPeer(_ base64PublicKey: String, endpoint: String, allowedIPs: [String] = []) throws {
@ -198,7 +198,7 @@ extension WireGuard {
$0 == Self.defaultGateway6 $0 == Self.defaultGateway6
} }
} }
public mutating func removeDefaultGateways(fromPeer peerIndex: Int) { public mutating func removeDefaultGateways(fromPeer peerIndex: Int) {
peers[peerIndex].allowedIPs.removeAll { peers[peerIndex].allowedIPs.removeAll {
$0 == Self.defaultGateway4 || $0 == Self.defaultGateway6 $0 == Self.defaultGateway4 || $0 == Self.defaultGateway6
@ -230,7 +230,7 @@ extension WireGuard {
public mutating func setKeepAlive(_ keepAlive: UInt16, forPeer peerIndex: Int) { public mutating func setKeepAlive(_ keepAlive: UInt16, forPeer peerIndex: Int) {
peers[peerIndex].persistentKeepAlive = keepAlive peers[peerIndex].persistentKeepAlive = keepAlive
} }
public func build() -> Configuration { public func build() -> Configuration {
let tunnelConfiguration = TunnelConfiguration(name: nil, interface: interface, peers: peers) let tunnelConfiguration = TunnelConfiguration(name: nil, interface: interface, peers: peers)
return Configuration(tunnelConfiguration: tunnelConfiguration) return Configuration(tunnelConfiguration: tunnelConfiguration)
@ -239,25 +239,25 @@ extension WireGuard {
public struct Configuration: Codable, Equatable, WireGuardConfigurationProviding { public struct Configuration: Codable, Equatable, WireGuardConfigurationProviding {
public let tunnelConfiguration: TunnelConfiguration public let tunnelConfiguration: TunnelConfiguration
public var interface: InterfaceConfiguration { public var interface: InterfaceConfiguration {
tunnelConfiguration.interface tunnelConfiguration.interface
} }
public var peers: [PeerConfiguration] { public var peers: [PeerConfiguration] {
tunnelConfiguration.peers tunnelConfiguration.peers
} }
public init(tunnelConfiguration: TunnelConfiguration) { public init(tunnelConfiguration: TunnelConfiguration) {
self.tunnelConfiguration = tunnelConfiguration self.tunnelConfiguration = tunnelConfiguration
} }
public func builder() -> WireGuard.ConfigurationBuilder { public func builder() -> WireGuard.ConfigurationBuilder {
WireGuard.ConfigurationBuilder(tunnelConfiguration) WireGuard.ConfigurationBuilder(tunnelConfiguration)
} }
// MARK: WireGuardConfigurationProviding // MARK: WireGuardConfigurationProviding
public var privateKey: String { public var privateKey: String {
interface.privateKey.base64Key interface.privateKey.base64Key
} }
@ -269,7 +269,7 @@ extension WireGuard {
public var addresses: [String] { public var addresses: [String] {
interface.addresses.map(\.stringRepresentation) interface.addresses.map(\.stringRepresentation)
} }
public var dnsServers: [String] { public var dnsServers: [String] {
interface.dns.map(\.stringRepresentation) interface.dns.map(\.stringRepresentation)
} }
@ -291,14 +291,14 @@ extension WireGuard {
} }
// MARK: Codable // MARK: Codable
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
let wg = try container.decode(String.self) let wg = try container.decode(String.self)
let cfg = try TunnelConfiguration(fromWgQuickConfig: wg, called: nil) let cfg = try TunnelConfiguration(fromWgQuickConfig: wg, called: nil)
self.init(tunnelConfiguration: cfg) self.init(tunnelConfiguration: cfg)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
let wg = tunnelConfiguration.asWgQuickConfig() let wg = tunnelConfiguration.asWgQuickConfig()
var container = encoder.singleValueContainer() var container = encoder.singleValueContainer()
@ -315,7 +315,7 @@ extension WireGuardConfigurationProviding {
public var peersCount: Int { public var peersCount: Int {
peers.count peers.count
} }
public func publicKey(ofPeer peerIndex: Int) -> String { public func publicKey(ofPeer peerIndex: Int) -> String {
peers[peerIndex].publicKey.base64Key peers[peerIndex].publicKey.base64Key
} }

View File

@ -7,10 +7,10 @@ extension OSLogType {
switch self { switch self {
case .debug: case .debug:
return .debug return .debug
case .info: case .info:
return .info return .info
case .error, .fault: case .error, .fault:
return .error return .error

View File

@ -42,18 +42,18 @@ extension WireGuard {
case lastError = "WireGuard.LastError" case lastError = "WireGuard.LastError"
} }
public let title: String public let title: String
public let appGroup: String public let appGroup: String
public let configuration: WireGuard.Configuration public let configuration: WireGuard.Configuration
public var shouldDebug = false public var shouldDebug = false
public var debugLogPath: String? = nil public var debugLogPath: String?
public var debugLogFormat: String? = nil public var debugLogFormat: String?
public init(_ title: String, appGroup: String, configuration: WireGuard.Configuration) { public init(_ title: String, appGroup: String, configuration: WireGuard.Configuration) {
self.title = title self.title = title
@ -68,7 +68,7 @@ extension WireGuard {
} }
} }
} }
// MARK: NetworkExtensionConfiguration // MARK: NetworkExtensionConfiguration
extension WireGuard.ProviderConfiguration: NetworkExtensionConfiguration { extension WireGuard.ProviderConfiguration: NetworkExtensionConfiguration {
@ -94,7 +94,6 @@ extension WireGuard.ProviderConfiguration {
public var lastError: WireGuardProviderError? { public var lastError: WireGuardProviderError? {
return defaults?.wireGuardLastError return defaults?.wireGuardLastError
} }
public var urlForDebugLog: URL? { public var urlForDebugLog: URL? {
return defaults?.wireGuardURLForDebugLog(appGroup: appGroup) return defaults?.wireGuardURLForDebugLog(appGroup: appGroup)

View File

@ -44,11 +44,9 @@ extension UnicodeScalar {
let value = self.value let value = self.value
if 48 <= value && value <= 57 { if 48 <= value && value <= 57 {
return UInt8(value - 48) return UInt8(value - 48)
} } else if 65 <= value && value <= 70 {
else if 65 <= value && value <= 70 {
return UInt8(value - 55) return UInt8(value - 55)
} } else if 97 <= value && value <= 102 {
else if 97 <= value && value <= 102 {
return UInt8(value - 87) return UInt8(value - 87)
} }
fatalError("\(self) not a legal hex nibble") fatalError("\(self) not a legal hex nibble")
@ -58,7 +56,7 @@ extension UnicodeScalar {
extension Data { extension Data {
public init(hex: String) { public init(hex: String) {
let scalars = hex.unicodeScalars let scalars = hex.unicodeScalars
var bytes = Array<UInt8>(repeating: 0, count: (scalars.count + 1) >> 1) var bytes = [UInt8](repeating: 0, count: (scalars.count + 1) >> 1)
for (index, scalar) in scalars.enumerated() { for (index, scalar) in scalars.enumerated() {
var nibble = scalar.hexNibble var nibble = scalar.hexNibble
if index & 1 == 0 { if index & 1 == 0 {
@ -72,7 +70,7 @@ extension Data {
public func toHex() -> String { public func toHex() -> String {
return map { String(format: "%02hhx", $0) }.joined() return map { String(format: "%02hhx", $0) }.joined()
} }
public mutating func zero() { public mutating func zero() {
resetBytes(in: 0..<count) resetBytes(in: 0..<count)
} }
@ -90,7 +88,7 @@ extension Data {
} }
append(buffer) append(buffer)
} }
public mutating func append(_ value: UInt32) { public mutating func append(_ value: UInt32) {
var localValue = value var localValue = value
let buffer = withUnsafePointer(to: &localValue) { let buffer = withUnsafePointer(to: &localValue) {
@ -98,7 +96,7 @@ extension Data {
} }
append(buffer) append(buffer)
} }
public mutating func append(_ value: UInt64) { public mutating func append(_ value: UInt64) {
var localValue = value var localValue = value
let buffer = withUnsafePointer(to: &localValue) { let buffer = withUnsafePointer(to: &localValue) {
@ -106,7 +104,7 @@ extension Data {
} }
append(buffer) append(buffer)
} }
public mutating func append(nullTerminatedString: String) { public mutating func append(nullTerminatedString: String) {
append(nullTerminatedString.data(using: .ascii)!) append(nullTerminatedString.data(using: .ascii)!)
append(UInt8(0)) append(UInt8(0))
@ -115,7 +113,7 @@ extension Data {
public func nullTerminatedString(from: Int) -> String? { public func nullTerminatedString(from: Int) -> String? {
var nullOffset: Int? var nullOffset: Int?
for i in from..<count { for i in from..<count {
if (self[i] == 0) { if self[i] == 0 {
nullOffset = i nullOffset = i
break break
} }
@ -137,7 +135,7 @@ extension Data {
// print("value: \(String(format: "%x", value))") // print("value: \(String(format: "%x", value))")
return value return value
} }
@available(*, deprecated) @available(*, deprecated)
func UInt16ValueFromPointers(from: Int) -> UInt16 { func UInt16ValueFromPointers(from: Int) -> UInt16 {
return subdata(in: from..<(from + 2)).withUnsafeBytes { $0.pointee } return subdata(in: from..<(from + 2)).withUnsafeBytes { $0.pointee }
@ -155,7 +153,7 @@ extension Data {
// print("value: \(String(format: "%x", value))") // print("value: \(String(format: "%x", value))")
return value return value
} }
@available(*, deprecated) @available(*, deprecated)
func UInt32ValueFromBuffer(from: Int) -> UInt32 { func UInt32ValueFromBuffer(from: Int) -> UInt32 {
var value: UInt32 = 0 var value: UInt32 = 0
@ -167,7 +165,7 @@ extension Data {
// print("value: \(String(format: "%x", value))") // print("value: \(String(format: "%x", value))")
return value return value
} }
// best // best
public func UInt32Value(from: Int) -> UInt32 { public func UInt32Value(from: Int) -> UInt32 {
return subdata(in: from..<(from + 4)).withUnsafeBytes { return subdata(in: from..<(from + 4)).withUnsafeBytes {

View File

@ -29,10 +29,10 @@ extension NSRegularExpression {
public convenience init(_ pattern: String) { public convenience init(_ pattern: String) {
try! self.init(pattern: pattern, options: []) try! self.init(pattern: pattern, options: [])
} }
public func groups(in string: String) -> [String] { public func groups(in string: String) -> [String] {
var results: [String] = [] var results: [String] = []
enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { result, flags, stop in enumerateMatches(in: string, options: [], range: NSRange(location: 0, length: string.count)) { result, _, _ in
guard let result = result else { guard let result = result else {
return return
} }
@ -48,7 +48,7 @@ extension NSRegularExpression {
extension NSRegularExpression { extension NSRegularExpression {
public func enumerateSpacedComponents(in string: String, using block: ([String]) -> Void) { public func enumerateSpacedComponents(in string: String, using block: ([String]) -> Void) {
enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { result, flags, stop in enumerateMatches(in: string, options: [], range: NSRange(location: 0, length: string.count)) { result, _, _ in
guard let range = result?.range else { guard let range = result?.range else {
return return
} }
@ -57,7 +57,7 @@ extension NSRegularExpression {
block(tokens) block(tokens)
} }
} }
public func enumerateSpacedArguments(in string: String, using block: ([String]) -> Void) { public func enumerateSpacedArguments(in string: String, using block: ([String]) -> Void) {
enumerateSpacedComponents(in: string) { (tokens) in enumerateSpacedComponents(in: string) { (tokens) in
var args = tokens var args = tokens

View File

@ -49,30 +49,30 @@ class DataManipulationTests: XCTestCase {
func testUInt() { func testUInt() {
let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66])
XCTAssertEqual(data.UInt16Value(from: 3), 0x55bb) XCTAssertEqual(data.UInt16Value(from: 3), 0x55bb)
XCTAssertEqual(data.UInt32Value(from: 2), 0x6655bbaa) XCTAssertEqual(data.UInt32Value(from: 2), 0x6655bbaa)
XCTAssertEqual(data.UInt16Value(from: 4), 0x6655) XCTAssertEqual(data.UInt16Value(from: 4), 0x6655)
XCTAssertEqual(data.UInt32Value(from: 0), 0xbbaaff22) XCTAssertEqual(data.UInt32Value(from: 0), 0xbbaaff22)
// XCTAssertEqual(data.UInt16Value(from: 3), data.UInt16ValueFromPointers(from: 3)) // XCTAssertEqual(data.UInt16Value(from: 3), data.UInt16ValueFromPointers(from: 3))
// XCTAssertEqual(data.UInt32Value(from: 2), data.UInt32ValueFromBuffer(from: 2)) // XCTAssertEqual(data.UInt32Value(from: 2), data.UInt32ValueFromBuffer(from: 2))
// XCTAssertEqual(data.UInt16Value(from: 4), data.UInt16ValueFromPointers(from: 4)) // XCTAssertEqual(data.UInt16Value(from: 4), data.UInt16ValueFromPointers(from: 4))
// XCTAssertEqual(data.UInt32Value(from: 0), data.UInt32ValueFromBuffer(from: 0)) // XCTAssertEqual(data.UInt32Value(from: 0), data.UInt32ValueFromBuffer(from: 0))
} }
func testZeroingData() { func testZeroingData() {
let z1 = Z() let z1 = Z()
z1.append(Z(Data(hex: "12345678"))) z1.append(Z(Data(hex: "12345678")))
z1.append(Z(Data(hex: "abcdef"))) z1.append(Z(Data(hex: "abcdef")))
let z2 = z1.withOffset(2, count: 3) // 5678ab let z2 = z1.withOffset(2, count: 3) // 5678ab
let z3 = z2.appending(Z(Data(hex: "aaddcc"))) // 5678abaaddcc let z3 = z2.appending(Z(Data(hex: "aaddcc"))) // 5678abaaddcc
XCTAssertEqual(z1.toData(), Data(hex: "12345678abcdef")) XCTAssertEqual(z1.toData(), Data(hex: "12345678abcdef"))
XCTAssertEqual(z2.toData(), Data(hex: "5678ab")) XCTAssertEqual(z2.toData(), Data(hex: "5678ab"))
XCTAssertEqual(z3.toData(), Data(hex: "5678abaaddcc")) XCTAssertEqual(z3.toData(), Data(hex: "5678abaaddcc"))
} }
func testFlatCount() { func testFlatCount() {
var v: [Data] = [] var v: [Data] = []
v.append(Data(hex: "11223344")) v.append(Data(hex: "11223344"))

View File

@ -40,26 +40,26 @@ import XCTest
@testable import TunnelKitCore @testable import TunnelKitCore
class RawPerformanceTests: XCTestCase { class RawPerformanceTests: XCTestCase {
override func setUp() { override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
} }
// 0.434s // 0.434s
func testUInt16FromBuffer() { func testUInt16FromBuffer() {
let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66])
measure { measure {
for _ in 0..<1000000 { for _ in 0..<1000000 {
let _ = data.UInt16Value(from: 3) _ = data.UInt16Value(from: 3)
} }
} }
} }
// // 0.463s // // 0.463s
// func testUInt16FromPointers() { // func testUInt16FromPointers() {
// let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) // let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66])
@ -81,18 +81,18 @@ class RawPerformanceTests: XCTestCase {
// } // }
// } // }
// } // }
// 0.469s // 0.469s
func testUInt32FromPointers() { func testUInt32FromPointers() {
let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66])
measure { measure {
for _ in 0..<1000000 { for _ in 0..<1000000 {
let _ = data.UInt32Value(from: 1) _ = data.UInt32Value(from: 1)
} }
} }
} }
// // 0.071s // // 0.071s
// func testRandomUInt32FromBuffer() { // func testRandomUInt32FromBuffer() {
// measure { // measure {
@ -101,12 +101,12 @@ class RawPerformanceTests: XCTestCase {
// } // }
// } // }
// } // }
// 0.063s // 0.063s
func testRandomUInt32FromPointers() { func testRandomUInt32FromPointers() {
measure { measure {
for _ in 0..<10000 { for _ in 0..<10000 {
let _ = try! SecureRandom.uint32() _ = try! SecureRandom.uint32()
} }
} }
} }
@ -127,7 +127,7 @@ class RawPerformanceTests: XCTestCase {
measure { measure {
for data in suite { for data in suite {
// let _ = UInt32(bigEndian: data.subdata(in: 0..<4).withUnsafeBytes { $0.pointee }) // let _ = UInt32(bigEndian: data.subdata(in: 0..<4).withUnsafeBytes { $0.pointee })
let _ = data.networkUInt32Value(from: 0) _ = data.networkUInt32Value(from: 0)
} }
} }
} }
@ -137,7 +137,7 @@ class RawPerformanceTests: XCTestCase {
let suite = TestUtils.generateDataSuite(1000, 100000) let suite = TestUtils.generateDataSuite(1000, 100000)
measure { measure {
for data in suite { for data in suite {
let _ = data.subdata(in: 5..<data.count) _ = data.subdata(in: 5..<data.count)
} }
} }
} }

View File

@ -35,7 +35,7 @@ class RoutingTests: XCTestCase {
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
} }
func testEntryMatch4() { func testEntryMatch4() {
let entry24 = RoutingTableEntry(iPv4Network: "192.168.1.0/24", gateway: nil, networkInterface: "en0") let entry24 = RoutingTableEntry(iPv4Network: "192.168.1.0/24", gateway: nil, networkInterface: "en0")
print(entry24.networkMask()!) print(entry24.networkMask()!)
@ -65,10 +65,10 @@ class RoutingTests: XCTestCase {
XCTAssertFalse(entry24.matchesDestination("abcd:efef:1233::\(i)")) XCTAssertFalse(entry24.matchesDestination("abcd:efef:1233::\(i)"))
} }
} }
func testFindGatewayLAN4() { func testFindGatewayLAN4() {
let table = RoutingTable() let table = RoutingTable()
for entry in table.ipv4() { for entry in table.ipv4() {
print(entry) print(entry)
} }
@ -83,11 +83,11 @@ class RoutingTests: XCTestCase {
func testFindGatewayLAN6() { func testFindGatewayLAN6() {
let table = RoutingTable() let table = RoutingTable()
for entry in table.ipv6() { for entry in table.ipv6() {
print(entry) print(entry)
} }
if let defaultGateway = table.defaultGateway6()?.gateway() { if let defaultGateway = table.defaultGateway6()?.gateway() {
print("Default gateway: \(defaultGateway)") print("Default gateway: \(defaultGateway)")
if let lan = table.broadestRoute6(matchingDestination: defaultGateway) { if let lan = table.broadestRoute6(matchingDestination: defaultGateway) {
@ -95,13 +95,13 @@ class RoutingTests: XCTestCase {
} }
} }
} }
func testPartitioning() { func testPartitioning() {
let v4 = RoutingTableEntry(iPv4Network: "192.168.1.0/24", gateway: nil, networkInterface: "en0") let v4 = RoutingTableEntry(iPv4Network: "192.168.1.0/24", gateway: nil, networkInterface: "en0")
let v4Boundary = RoutingTableEntry(iPv4Network: "192.168.1.0/31", gateway: nil, networkInterface: "en0") let v4Boundary = RoutingTableEntry(iPv4Network: "192.168.1.0/31", gateway: nil, networkInterface: "en0")
let v6 = RoutingTableEntry(iPv6Network: "abcd:efef:120::/46", gateway: nil, networkInterface: "en0") let v6 = RoutingTableEntry(iPv6Network: "abcd:efef:120::/46", gateway: nil, networkInterface: "en0")
let v6Boundary = RoutingTableEntry(iPv6Network: "abcd:efef:120::/127", gateway: nil, networkInterface: "en0") let v6Boundary = RoutingTableEntry(iPv6Network: "abcd:efef:120::/127", gateway: nil, networkInterface: "en0")
guard let v4parts = v4.partitioned() else { guard let v4parts = v4.partitioned() else {
fatalError() fatalError()
} }

View File

@ -45,7 +45,7 @@ public class TestUtils {
} }
return suite return suite
} }
private init() { private init() {
} }
} }

View File

@ -40,7 +40,7 @@ class CompressionTests: XCTestCase {
} }
func testSymmetric() { func testSymmetric() {
XCTAssertTrue(LZOFactory.isSupported()); XCTAssertTrue(LZOFactory.isSupported())
let lzo = LZOFactory.create() let lzo = LZOFactory.create()
let src = Data([UInt8](repeating: 6, count: 100)) let src = Data([UInt8](repeating: 6, count: 100))
guard let dst = try? lzo.compressedData(with: src) else { guard let dst = try? lzo.compressedData(with: src) else {

View File

@ -44,12 +44,12 @@ import TunnelKitManager
import TunnelKitOpenVPNManager import TunnelKitOpenVPNManager
class AppExtensionTests: XCTestCase { class AppExtensionTests: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown() super.tearDown()
@ -80,7 +80,7 @@ class AppExtensionTests: XCTestCase {
XCTFail(error.localizedDescription) XCTFail(error.localizedDescription)
return return
} }
XCTAssertEqual(proto.providerBundleIdentifier, bundleIdentifier) XCTAssertEqual(proto.providerBundleIdentifier, bundleIdentifier)
XCTAssertEqual(proto.serverAddress, serverAddress) XCTAssertEqual(proto.serverAddress, serverAddress)
XCTAssertEqual(proto.username, credentials.username) XCTAssertEqual(proto.username, credentials.username)
@ -99,7 +99,7 @@ class AppExtensionTests: XCTestCase {
XCTAssertEqual(ovpn?["mtu"] as? Int, cfg.configuration.mtu) XCTAssertEqual(ovpn?["mtu"] as? Int, cfg.configuration.mtu)
XCTAssertEqual(ovpn?["renegotiatesAfter"] as? TimeInterval, cfg.configuration.renegotiatesAfter) XCTAssertEqual(ovpn?["renegotiatesAfter"] as? TimeInterval, cfg.configuration.renegotiatesAfter)
} }
func testDNSResolver() { func testDNSResolver() {
let exp = expectation(description: "DNS") let exp = expectation(description: "DNS")
DNSResolver.resolve("www.google.com", timeout: 1000, queue: .main) { DNSResolver.resolve("www.google.com", timeout: 1000, queue: .main) {
@ -116,7 +116,7 @@ class AppExtensionTests: XCTestCase {
} }
waitForExpectations(timeout: 5.0, handler: nil) waitForExpectations(timeout: 5.0, handler: nil)
} }
func testDNSAddressConversion() { func testDNSAddressConversion() {
let testStrings = [ let testStrings = [
"0.0.0.0", "0.0.0.0",
@ -148,7 +148,7 @@ class AppExtensionTests: XCTestCase {
.init(hostname, .init(.udp4, 3333)) .init(hostname, .init(.udp4, 3333))
] ]
let strategy = ConnectionStrategy(configuration: builder.build()) let strategy = ConnectionStrategy(configuration: builder.build())
let expected = [ let expected = [
"italy.privateinternetaccess.com:TCP6:2222", "italy.privateinternetaccess.com:TCP6:2222",
"italy.privateinternetaccess.com:UDP:1111", "italy.privateinternetaccess.com:UDP:1111",

View File

@ -32,24 +32,24 @@ class ConfigurationParserTests: XCTestCase {
super.setUp() super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown() super.tearDown()
} }
// from lines // from lines
func testCompression() throws { func testCompression() throws {
XCTAssertNil(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo"]).warning) XCTAssertNil(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo"]).warning)
XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo no"])) XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo no"]))
XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo yes"])) XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo yes"]))
// XCTAssertThrowsError(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo yes"])) // XCTAssertThrowsError(try OpenVPN.ConfigurationParser.parsed(fromLines: ["comp-lzo yes"]))
XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["compress"])) XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["compress"]))
XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["compress lzo"])) XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: ["compress lzo"]))
} }
func testKeepAlive() throws { func testKeepAlive() throws {
let cfg1 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["ping 10", "ping-restart 60"]) let cfg1 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["ping 10", "ping-restart 60"])
let cfg2 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["keepalive 10 60"]) let cfg2 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["keepalive 10 60"])
@ -59,7 +59,7 @@ class ConfigurationParserTests: XCTestCase {
XCTAssertNotEqual(cfg1.configuration.keepAliveInterval, cfg3.configuration.keepAliveInterval) XCTAssertNotEqual(cfg1.configuration.keepAliveInterval, cfg3.configuration.keepAliveInterval)
XCTAssertNotEqual(cfg1.configuration.keepAliveTimeout, cfg3.configuration.keepAliveTimeout) XCTAssertNotEqual(cfg1.configuration.keepAliveTimeout, cfg3.configuration.keepAliveTimeout)
} }
func testDHCPOption() throws { func testDHCPOption() throws {
let lines = [ let lines = [
"dhcp-option DNS 8.8.8.8", "dhcp-option DNS 8.8.8.8",
@ -76,7 +76,7 @@ class ConfigurationParserTests: XCTestCase {
"dhcp-option PROXY_BYPASS foo.com bar.org net.chat" "dhcp-option PROXY_BYPASS foo.com bar.org net.chat"
] ]
XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: lines)) XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: lines))
let parsed = try! OpenVPN.ConfigurationParser.parsed(fromLines: lines).configuration let parsed = try! OpenVPN.ConfigurationParser.parsed(fromLines: lines).configuration
XCTAssertEqual(parsed.dnsServers, ["8.8.8.8", "ffff::1"]) XCTAssertEqual(parsed.dnsServers, ["8.8.8.8", "ffff::1"])
XCTAssertEqual(parsed.dnsDomain, "second-domain.org") XCTAssertEqual(parsed.dnsDomain, "second-domain.org")
@ -88,7 +88,7 @@ class ConfigurationParserTests: XCTestCase {
XCTAssertEqual(parsed.proxyAutoConfigurationURL?.absoluteString, "https://pac/") XCTAssertEqual(parsed.proxyAutoConfigurationURL?.absoluteString, "https://pac/")
XCTAssertEqual(parsed.proxyBypassDomains, ["foo.com", "bar.org", "net.chat"]) XCTAssertEqual(parsed.proxyBypassDomains, ["foo.com", "bar.org", "net.chat"])
} }
func testRedirectGateway() throws { func testRedirectGateway() throws {
var parsed: OpenVPN.Configuration var parsed: OpenVPN.Configuration
@ -105,12 +105,12 @@ class ConfigurationParserTests: XCTestCase {
} }
// from file // from file
func testPIA() throws { func testPIA() throws {
let file = try OpenVPN.ConfigurationParser.parsed(fromURL: url(withName: "pia-hungary")) let file = try OpenVPN.ConfigurationParser.parsed(fromURL: url(withName: "pia-hungary"))
XCTAssertEqual(file.configuration.remotes, [ XCTAssertEqual(file.configuration.remotes, [
.init("hungary.privateinternetaccess.com", .init(.udp, 1198)), .init("hungary.privateinternetaccess.com", .init(.udp, 1198)),
.init("hungary.privateinternetaccess.com", .init(.tcp, 502)), .init("hungary.privateinternetaccess.com", .init(.tcp, 502))
]) ])
XCTAssertEqual(file.configuration.cipher, .aes128cbc) XCTAssertEqual(file.configuration.cipher, .aes128cbc)
XCTAssertEqual(file.configuration.digest, .sha1) XCTAssertEqual(file.configuration.digest, .sha1)
@ -121,38 +121,38 @@ class ConfigurationParserTests: XCTestCase {
let stripped = lines.joined(separator: "\n") let stripped = lines.joined(separator: "\n")
print(stripped) print(stripped)
} }
func testEncryptedCertificateKey() throws { func testEncryptedCertificateKey() throws {
try privateTestEncryptedCertificateKey(pkcs: "1") try privateTestEncryptedCertificateKey(pkcs: "1")
try privateTestEncryptedCertificateKey(pkcs: "8") try privateTestEncryptedCertificateKey(pkcs: "8")
} }
func testXOR() throws { func testXOR() throws {
let cfg = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xormask F"]) let cfg = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xormask F"])
XCTAssertNil(cfg.warning) XCTAssertNil(cfg.warning)
XCTAssertEqual(cfg.configuration.xorMethod, OpenVPN.XORMethod.xormask(mask: Data(repeating: Character("F").asciiValue!, count:1))) XCTAssertEqual(cfg.configuration.xorMethod, OpenVPN.XORMethod.xormask(mask: Data(repeating: Character("F").asciiValue!, count: 1)))
let cfg2 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble reverse"]) let cfg2 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble reverse"])
XCTAssertNil(cfg.warning) XCTAssertNil(cfg.warning)
XCTAssertEqual(cfg2.configuration.xorMethod, OpenVPN.XORMethod.reverse) XCTAssertEqual(cfg2.configuration.xorMethod, OpenVPN.XORMethod.reverse)
let cfg3 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xorptrpos"]) let cfg3 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xorptrpos"])
XCTAssertNil(cfg.warning) XCTAssertNil(cfg.warning)
XCTAssertEqual(cfg3.configuration.xorMethod, OpenVPN.XORMethod.xorptrpos) XCTAssertEqual(cfg3.configuration.xorMethod, OpenVPN.XORMethod.xorptrpos)
let cfg4 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble obfuscate FFFF"]) let cfg4 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble obfuscate FFFF"])
XCTAssertNil(cfg.warning) XCTAssertNil(cfg.warning)
XCTAssertEqual(cfg4.configuration.xorMethod, OpenVPN.XORMethod.obfuscate(mask: Data(repeating: Character("F").asciiValue!, count:4))) XCTAssertEqual(cfg4.configuration.xorMethod, OpenVPN.XORMethod.obfuscate(mask: Data(repeating: Character("F").asciiValue!, count: 4)))
} }
private func privateTestEncryptedCertificateKey(pkcs: String) throws { private func privateTestEncryptedCertificateKey(pkcs: String) throws {
let cfgURL = url(withName: "tunnelbear.enc.\(pkcs)") let cfgURL = url(withName: "tunnelbear.enc.\(pkcs)")
XCTAssertThrowsError(try OpenVPN.ConfigurationParser.parsed(fromURL: cfgURL)) XCTAssertThrowsError(try OpenVPN.ConfigurationParser.parsed(fromURL: cfgURL))
XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromURL: cfgURL, passphrase: "foobar")) XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromURL: cfgURL, passphrase: "foobar"))
} }
private func url(withName name: String) -> URL { private func url(withName name: String) -> URL {
return Bundle.module.url(forResource: name, withExtension: "ovpn")! return Bundle.module.url(forResource: name, withExtension: "ovpn")!
} }
} }

View File

@ -30,15 +30,15 @@ import TunnelKitOpenVPNCore
class ConfigurationTests: XCTestCase { class ConfigurationTests: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
CoreConfiguration.masksPrivateData = false CoreConfiguration.masksPrivateData = false
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown() super.tearDown()
} }
func testRandomizeHostnames() { func testRandomizeHostnames() {
var builder = OpenVPN.ConfigurationBuilder() var builder = OpenVPN.ConfigurationBuilder()
let hostname = "my.host.name" let hostname = "my.host.name"
@ -49,7 +49,7 @@ class ConfigurationTests: XCTestCase {
] ]
builder.randomizeHostnames = true builder.randomizeHostnames = true
let cfg = builder.build() let cfg = builder.build()
cfg.processedRemotes?.forEach { cfg.processedRemotes?.forEach {
let comps = $0.address.components(separatedBy: ".") let comps = $0.address.components(separatedBy: ".")
guard let first = comps.first else { guard let first = comps.first else {

View File

@ -39,7 +39,7 @@ class ControlChannelTests: XCTestCase {
super.setUp() super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown() super.tearDown()
@ -55,16 +55,16 @@ class ControlChannelTests: XCTestCase {
let key = OpenVPN.StaticKey(biData: Data(hex: hex)) let key = OpenVPN.StaticKey(biData: Data(hex: hex))
let server = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: OpenVPN.Digest.sha1.rawValue) let server = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: OpenVPN.Digest.sha1.rawValue)
XCTAssertNoThrow(try server.configure(withCipherEncKey: nil, cipherDecKey: nil, hmacEncKey: key.hmacReceiveKey, hmacDecKey: key.hmacSendKey)) XCTAssertNoThrow(try server.configure(withCipherEncKey: nil, cipherDecKey: nil, hmacEncKey: key.hmacReceiveKey, hmacDecKey: key.hmacSendKey))
// let original = Data(hex: "38858fe14742fdae40e67c9137933a412a711c0d0514aca6db6476d17d000000015b96c9470000000000") // let original = Data(hex: "38858fe14742fdae40e67c9137933a412a711c0d0514aca6db6476d17d000000015b96c9470000000000")
let hmac = Data(hex: "e67c9137933a412a711c0d0514aca6db6476d17d") let hmac = Data(hex: "e67c9137933a412a711c0d0514aca6db6476d17d")
let subject = Data(hex: "000000015b96c94738858fe14742fdae400000000000") let subject = Data(hex: "000000015b96c94738858fe14742fdae400000000000")
let data = hmac + subject let data = hmac + subject
print(data.toHex()) print(data.toHex())
XCTAssertNoThrow(try server.decrypter().verifyData(data, flags: nil)) XCTAssertNoThrow(try server.decrypter().verifyData(data, flags: nil))
} }
// 38 // HARD_RESET // 38 // HARD_RESET
// bccfd171ce22e085 // session_id // bccfd171ce22e085 // session_id
// e01a3454c354f3c3093b00fc8d6228a8b69ef503d56f6a572ebd26a800711b4cd4df2b9daf06cb90f82379e7815e39fb73be4ac5461752db4f35120474af82b2 // hmac // e01a3454c354f3c3093b00fc8d6228a8b69ef503d56f6a572ebd26a800711b4cd4df2b9daf06cb90f82379e7815e39fb73be4ac5461752db4f35120474af82b2 // hmac
@ -74,11 +74,11 @@ class ControlChannelTests: XCTestCase {
func testAuth() { func testAuth() {
let client = try! OpenVPN.ControlChannel.AuthSerializer(withKey: OpenVPN.StaticKey(data: Data(hex: hex), direction: .client), digest: .sha512) let client = try! OpenVPN.ControlChannel.AuthSerializer(withKey: OpenVPN.StaticKey(data: Data(hex: hex), direction: .client), digest: .sha512)
let server = try! OpenVPN.ControlChannel.AuthSerializer(withKey: OpenVPN.StaticKey(data: Data(hex: hex), direction: .server), digest: .sha512) let server = try! OpenVPN.ControlChannel.AuthSerializer(withKey: OpenVPN.StaticKey(data: Data(hex: hex), direction: .server), digest: .sha512)
// let original = Data(hex: "38bccfd1") // let original = Data(hex: "38bccfd1")
let original = Data(hex: "38bccfd171ce22e085e01a3454c354f3c3093b00fc8d6228a8b69ef503d56f6a572ebd26a800711b4cd4df2b9daf06cb90f82379e7815e39fb73be4ac5461752db4f35120474af82b2000000015b93b65d0000000000") let original = Data(hex: "38bccfd171ce22e085e01a3454c354f3c3093b00fc8d6228a8b69ef503d56f6a572ebd26a800711b4cd4df2b9daf06cb90f82379e7815e39fb73be4ac5461752db4f35120474af82b2000000015b93b65d0000000000")
let timestamp = UInt32(0x5b93b65d) let timestamp = UInt32(0x5b93b65d)
let packet: ControlPacket let packet: ControlPacket
do { do {
packet = try client.deserialize(data: original, start: 0, end: nil) packet = try client.deserialize(data: original, start: 0, end: nil)
@ -90,7 +90,7 @@ class ControlChannelTests: XCTestCase {
XCTAssertEqual(packet.sessionId, Data(hex: "bccfd171ce22e085")) XCTAssertEqual(packet.sessionId, Data(hex: "bccfd171ce22e085"))
XCTAssertNil(packet.ackIds) XCTAssertNil(packet.ackIds)
XCTAssertEqual(packet.packetId, 0) XCTAssertEqual(packet.packetId, 0)
let raw: Data let raw: Data
do { do {
raw = try server.serialize(packet: packet, timestamp: timestamp) raw = try server.serialize(packet: packet, timestamp: timestamp)
@ -109,7 +109,7 @@ class ControlChannelTests: XCTestCase {
let original = Data(hex: "407bf3d6a260e6476d000000015ba4155887940856ddb70e01693980c5c955cb5506ecf9fd3e0bcee0c802ec269427d43bf1cda1837ffbf30c83cacff852cd0b7f4c") let original = Data(hex: "407bf3d6a260e6476d000000015ba4155887940856ddb70e01693980c5c955cb5506ecf9fd3e0bcee0c802ec269427d43bf1cda1837ffbf30c83cacff852cd0b7f4c")
let timestamp = UInt32(0x5ba41558) let timestamp = UInt32(0x5ba41558)
let packet: ControlPacket let packet: ControlPacket
do { do {
packet = try client.deserialize(data: original, start: 0, end: nil) packet = try client.deserialize(data: original, start: 0, end: nil)

View File

@ -45,28 +45,28 @@ class DataPathEncryptionTests: XCTestCase {
private let hmacKey = try! SecureRandom.safeData(length: 32) private let hmacKey = try! SecureRandom.safeData(length: 32)
private var enc: DataPathEncrypter! private var enc: DataPathEncrypter!
private var dec: DataPathDecrypter! private var dec: DataPathDecrypter!
override func setUp() { override func setUp() {
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
} }
func testCBC() { func testCBC() {
prepareBox(cipher: "aes-128-cbc", digest: "sha256") prepareBox(cipher: "aes-128-cbc", digest: "sha256")
privateTestDataPathHigh(peerId: nil) privateTestDataPathHigh(peerId: nil)
privateTestDataPathLow(peerId: nil) privateTestDataPathLow(peerId: nil)
} }
func testFloatingCBC() { func testFloatingCBC() {
prepareBox(cipher: "aes-128-cbc", digest: "sha256") prepareBox(cipher: "aes-128-cbc", digest: "sha256")
privateTestDataPathHigh(peerId: 0x64385837) privateTestDataPathHigh(peerId: 0x64385837)
privateTestDataPathLow(peerId: 0x64385837) privateTestDataPathLow(peerId: 0x64385837)
} }
func testGCM() { func testGCM() {
prepareBox(cipher: "aes-256-gcm", digest: nil) prepareBox(cipher: "aes-256-gcm", digest: nil)
privateTestDataPathHigh(peerId: nil) privateTestDataPathHigh(peerId: nil)
@ -78,14 +78,14 @@ class DataPathEncryptionTests: XCTestCase {
privateTestDataPathHigh(peerId: 0x64385837) privateTestDataPathHigh(peerId: 0x64385837)
privateTestDataPathLow(peerId: 0x64385837) privateTestDataPathLow(peerId: 0x64385837)
} }
func prepareBox(cipher: String, digest: String?) { func prepareBox(cipher: String, digest: String?) {
let box = CryptoBox(cipherAlgorithm: cipher, digestAlgorithm: digest) let box = CryptoBox(cipherAlgorithm: cipher, digestAlgorithm: digest)
try! box.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey) try! box.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey)
enc = box.encrypter().dataPathEncrypter() enc = box.encrypter().dataPathEncrypter()
dec = box.decrypter().dataPathDecrypter() dec = box.decrypter().dataPathDecrypter()
} }
func privateTestDataPathHigh(peerId: UInt32?) { func privateTestDataPathHigh(peerId: UInt32?) {
let path = DataPath( let path = DataPath(
encrypter: enc, encrypter: enc,

View File

@ -47,15 +47,15 @@ class DataPathPerformanceTests: XCTestCase {
private var encrypter: DataPathEncrypter! private var encrypter: DataPathEncrypter!
private var decrypter: DataPathDecrypter! private var decrypter: DataPathDecrypter!
override func setUp() { override func setUp() {
let ck = try! SecureRandom.safeData(length: 32) let ck = try! SecureRandom.safeData(length: 32)
let hk = try! SecureRandom.safeData(length: 32) let hk = try! SecureRandom.safeData(length: 32)
let crypto = try! OpenVPN.EncryptionBridge(.aes128cbc, .sha1, ck, ck, hk, hk) let crypto = try! OpenVPN.EncryptionBridge(.aes128cbc, .sha1, ck, ck, hk, hk)
encrypter = crypto.encrypter() encrypter = crypto.encrypter()
decrypter = crypto.decrypter() decrypter = crypto.decrypter()
dataPath = DataPath( dataPath = DataPath(
encrypter: encrypter, encrypter: encrypter,
decrypter: decrypter, decrypter: decrypter,
@ -85,18 +85,18 @@ class DataPathPerformanceTests: XCTestCase {
//// print(">>> \(packets?.count) packets") //// print(">>> \(packets?.count) packets")
// XCTAssertEqual(decryptedPackets, packets) // XCTAssertEqual(decryptedPackets, packets)
// } // }
// 16ms // 16ms
func testPointerBased() { func testPointerBased() {
let packets = TestUtils.generateDataSuite(1200, 1000) let packets = TestUtils.generateDataSuite(1200, 1000)
var encryptedPackets: [Data]! var encryptedPackets: [Data]!
var decryptedPackets: [Data]! var decryptedPackets: [Data]!
measure { measure {
encryptedPackets = try! self.dataPath.encryptPackets(packets, key: 0) encryptedPackets = try! self.dataPath.encryptPackets(packets, key: 0)
decryptedPackets = try! self.dataPath.decryptPackets(encryptedPackets, keepAlive: nil) decryptedPackets = try! self.dataPath.decryptPackets(encryptedPackets, keepAlive: nil)
} }
// print(">>> \(packets?.count) packets") // print(">>> \(packets?.count) packets")
XCTAssertEqual(decryptedPackets, packets) XCTAssertEqual(decryptedPackets, packets)
} }

View File

@ -41,17 +41,17 @@ import CTunnelKitOpenVPNProtocol
class EncryptionPerformanceTests: XCTestCase { class EncryptionPerformanceTests: XCTestCase {
private var cbcEncrypter: Encrypter! private var cbcEncrypter: Encrypter!
private var cbcDecrypter: Decrypter! private var cbcDecrypter: Decrypter!
private var gcmEncrypter: Encrypter! private var gcmEncrypter: Encrypter!
private var gcmDecrypter: Decrypter! private var gcmDecrypter: Decrypter!
override func setUp() { override func setUp() {
let cipherKey = try! SecureRandom.safeData(length: 32) let cipherKey = try! SecureRandom.safeData(length: 32)
let hmacKey = try! SecureRandom.safeData(length: 32) let hmacKey = try! SecureRandom.safeData(length: 32)
let cbc = CryptoBox(cipherAlgorithm: "aes-128-cbc", digestAlgorithm: "sha1") let cbc = CryptoBox(cipherAlgorithm: "aes-128-cbc", digestAlgorithm: "sha1")
try! cbc.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey) try! cbc.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey)
cbcEncrypter = cbc.encrypter() cbcEncrypter = cbc.encrypter()
@ -72,7 +72,7 @@ class EncryptionPerformanceTests: XCTestCase {
let suite = TestUtils.generateDataSuite(1000, 100000) let suite = TestUtils.generateDataSuite(1000, 100000)
measure { measure {
for data in suite { for data in suite {
let _ = try! self.cbcEncrypter.encryptData(data, flags: nil) _ = try! self.cbcEncrypter.encryptData(data, flags: nil)
} }
} }
} }
@ -86,7 +86,7 @@ class EncryptionPerformanceTests: XCTestCase {
} }
measure { measure {
for data in suite { for data in suite {
let _ = try! self.gcmEncrypter.encryptData(data, flags: &flags) _ = try! self.gcmEncrypter.encryptData(data, flags: &flags)
} }
} }
} }

View File

@ -44,11 +44,11 @@ class EncryptionTests: XCTestCase {
private var cipherEncKey: ZeroingData! private var cipherEncKey: ZeroingData!
private var cipherDecKey: ZeroingData! private var cipherDecKey: ZeroingData!
private var hmacEncKey: ZeroingData! private var hmacEncKey: ZeroingData!
private var hmacDecKey: ZeroingData! private var hmacDecKey: ZeroingData!
override func setUp() { override func setUp() {
cipherEncKey = try! SecureRandom.safeData(length: 32) cipherEncKey = try! SecureRandom.safeData(length: 32)
cipherDecKey = try! SecureRandom.safeData(length: 32) cipherDecKey = try! SecureRandom.safeData(length: 32)
@ -76,10 +76,10 @@ class EncryptionTests: XCTestCase {
let encrypted = try! client.encrypter().encryptData(plain, flags: nil) let encrypted = try! client.encrypter().encryptData(plain, flags: nil)
XCTAssertNoThrow(try server.decrypter().verifyData(encrypted, flags: nil)) XCTAssertNoThrow(try server.decrypter().verifyData(encrypted, flags: nil))
} }
func testGCM() { func testGCM() {
let (client, server) = clientServer("aes-256-gcm", nil) let (client, server) = clientServer("aes-256-gcm", nil)
let packetId: [UInt8] = [0x56, 0x34, 0x12, 0x00] let packetId: [UInt8] = [0x56, 0x34, 0x12, 0x00]
let ad: [UInt8] = [0x00, 0x12, 0x34, 0x56] let ad: [UInt8] = [0x00, 0x12, 0x34, 0x56]
var flags = packetId.withUnsafeBufferPointer { (iv) in var flags = packetId.withUnsafeBufferPointer { (iv) in
@ -92,7 +92,7 @@ class EncryptionTests: XCTestCase {
let decrypted = try! server.decrypter().decryptData(encrypted, flags: &flags) let decrypted = try! server.decrypter().decryptData(encrypted, flags: &flags)
XCTAssertEqual(plain, decrypted) XCTAssertEqual(plain, decrypted)
} }
func testCTR() { func testCTR() {
let (client, server) = clientServer("aes-256-ctr", "sha256") let (client, server) = clientServer("aes-256-ctr", "sha256")
@ -119,17 +119,17 @@ class EncryptionTests: XCTestCase {
print(md5) print(md5)
XCTAssertEqual(md5, exp) XCTAssertEqual(md5, exp)
} }
func testPrivateKeyDecryption() { func testPrivateKeyDecryption() {
privateTestPrivateKeyDecryption(pkcs: "1") privateTestPrivateKeyDecryption(pkcs: "1")
privateTestPrivateKeyDecryption(pkcs: "8") privateTestPrivateKeyDecryption(pkcs: "8")
} }
private func privateTestPrivateKeyDecryption(pkcs: String) { private func privateTestPrivateKeyDecryption(pkcs: String) {
let bundle = Bundle.module let bundle = Bundle.module
let encryptedPath = bundle.path(forResource: "tunnelbear", ofType: "enc.\(pkcs).key")! let encryptedPath = bundle.path(forResource: "tunnelbear", ofType: "enc.\(pkcs).key")!
let decryptedPath = bundle.path(forResource: "tunnelbear", ofType: "key")! let decryptedPath = bundle.path(forResource: "tunnelbear", ofType: "key")!
XCTAssertThrowsError(try TLSBox.decryptedPrivateKey(fromPath: encryptedPath, passphrase: "wrongone")) XCTAssertThrowsError(try TLSBox.decryptedPrivateKey(fromPath: encryptedPath, passphrase: "wrongone"))
let decryptedViaPath = try! TLSBox.decryptedPrivateKey(fromPath: encryptedPath, passphrase: "foobar") let decryptedViaPath = try! TLSBox.decryptedPrivateKey(fromPath: encryptedPath, passphrase: "foobar")
print(decryptedViaPath) print(decryptedViaPath)
@ -137,17 +137,17 @@ class EncryptionTests: XCTestCase {
let decryptedViaString = try! TLSBox.decryptedPrivateKey(fromPEM: encryptedPEM, passphrase: "foobar") let decryptedViaString = try! TLSBox.decryptedPrivateKey(fromPEM: encryptedPEM, passphrase: "foobar")
print(decryptedViaString) print(decryptedViaString)
XCTAssertEqual(decryptedViaPath, decryptedViaString) XCTAssertEqual(decryptedViaPath, decryptedViaString)
let expDecrypted = try! String(contentsOfFile: decryptedPath) let expDecrypted = try! String(contentsOfFile: decryptedPath)
XCTAssertEqual(decryptedViaPath, expDecrypted) XCTAssertEqual(decryptedViaPath, expDecrypted)
} }
func testCertificatePreamble() { func testCertificatePreamble() {
let url = Bundle.module.url(forResource: "tunnelbear", withExtension: "crt")! let url = Bundle.module.url(forResource: "tunnelbear", withExtension: "crt")!
let cert = OpenVPN.CryptoContainer(pem: try! String(contentsOf: url)) let cert = OpenVPN.CryptoContainer(pem: try! String(contentsOf: url))
XCTAssert(cert.pem.hasPrefix("-----BEGIN")) XCTAssert(cert.pem.hasPrefix("-----BEGIN"))
} }
private func clientServer(_ c: String?, _ d: String?) -> (CryptoBox, CryptoBox) { private func clientServer(_ c: String?, _ d: String?) -> (CryptoBox, CryptoBox) {
let client = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d) let client = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d)
let server = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d) let server = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d)

View File

@ -49,19 +49,19 @@ class LinkTests: XCTestCase {
} }
// UDP // UDP
func testUnreliableControlQueue() { func testUnreliableControlQueue() {
let seq1 = [0, 5, 2, 1, 4, 3] let seq1 = [0, 5, 2, 1, 4, 3]
let seq2 = [5, 2, 1, 9, 4, 3, 0, 8, 7, 10, 4, 3, 5, 6] let seq2 = [5, 2, 1, 9, 4, 3, 0, 8, 7, 10, 4, 3, 5, 6]
let seq3 = [5, 2, 11, 1, 2, 9, 4, 5, 5, 3, 8, 0, 6, 8, 2, 7, 10, 4, 3, 5, 6] let seq3 = [5, 2, 11, 1, 2, 9, 4, 5, 5, 3, 8, 0, 6, 8, 2, 7, 10, 4, 3, 5, 6]
for seq in [seq1, seq2, seq3] { for seq in [seq1, seq2, seq3] {
XCTAssertEqual(TestUtils.uniqArray(seq.sorted()), handleControlSequence(seq)) XCTAssertEqual(TestUtils.uniqArray(seq.sorted()), handleControlSequence(seq))
} }
} }
// TCP // TCP
// private func testPacketStream() { // private func testPacketStream() {
// var bytes: [UInt8] = [] // var bytes: [UInt8] = []
// var until: Int // var until: Int
@ -156,25 +156,25 @@ class LinkTests: XCTestCase {
} }
return hdl return hdl
} }
private func enqueueControl(_ q: inout [Int], _ id: inout Int, _ p: Int, _ h: (Int) -> Void) { private func enqueueControl(_ q: inout [Int], _ id: inout Int, _ p: Int, _ h: (Int) -> Void) {
q.append(p) q.append(p)
q.sort { (p1, p2) -> Bool in q.sort { (p1, p2) -> Bool in
return (p1 < p2) return (p1 < p2)
} }
print("q = \(q)") print("q = \(q)")
print("id = \(id)") print("id = \(id)")
for p in q { for p in q {
print("test(\(p))") print("test(\(p))")
if (p < id) { if p < id {
q.removeFirst() q.removeFirst()
continue continue
} }
if (p != id) { if p != id {
return return
} }
h(p) h(p)
print("handle(\(p))") print("handle(\(p))")
id += 1 id += 1

View File

@ -28,17 +28,17 @@ import XCTest
import CTunnelKitOpenVPNProtocol import CTunnelKitOpenVPNProtocol
class PacketTests: XCTestCase { class PacketTests: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown() super.tearDown()
} }
func testControlPacket() { func testControlPacket() {
let id: UInt32 = 0x1456 let id: UInt32 = 0x1456
let code: PacketCode = .controlV1 let code: PacketCode = .controlV1
@ -64,7 +64,7 @@ class PacketTests: XCTestCase {
let expected = Data(hex: "2b112233445566778805000000aa000000bb000000cc000000dd000000eea639328cbf03490e") let expected = Data(hex: "2b112233445566778805000000aa000000bb000000cc000000dd000000eea639328cbf03490e")
print("Serialized: \(serialized.toHex())") print("Serialized: \(serialized.toHex())")
print("Expected : \(expected.toHex())") print("Expected : \(expected.toHex())")
XCTAssertEqual(serialized, expected) XCTAssertEqual(serialized, expected)
} }
} }

View File

@ -40,15 +40,15 @@ private extension OpenVPN.PushReply {
} }
class PushTests: XCTestCase { class PushTests: XCTestCase {
override func setUp() { override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
} }
func testNet30() { func testNet30() {
let msg = "PUSH_REPLY,redirect-gateway def1,dhcp-option DNS 209.222.18.222,dhcp-option DNS 209.222.18.218,ping 10,comp-lzo no,route 10.5.10.1,topology net30,ifconfig 10.5.10.6 10.5.10.5,auth-token AUkQf/b3nj3L+CH4RJPP0Vuq8/gpntr7uPqzjQhncig=" let msg = "PUSH_REPLY,redirect-gateway def1,dhcp-option DNS 209.222.18.222,dhcp-option DNS 209.222.18.218,ping 10,comp-lzo no,route 10.5.10.1,topology net30,ifconfig 10.5.10.6 10.5.10.5,auth-token AUkQf/b3nj3L+CH4RJPP0Vuq8/gpntr7uPqzjQhncig="
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
@ -59,25 +59,25 @@ class PushTests: XCTestCase {
XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.5.10.5") XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.5.10.5")
XCTAssertEqual(reply.options.dnsServers, ["209.222.18.222", "209.222.18.218"]) XCTAssertEqual(reply.options.dnsServers, ["209.222.18.222", "209.222.18.218"])
} }
func testSubnet() { func testSubnet() {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route-gateway 10.8.0.1,topology subnet,ping 10,ping-restart 120,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route-gateway 10.8.0.1,topology subnet,ping 10,ping-restart 120,ifconfig 10.8.0.2 255.255.255.0,peer-id 0"
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
reply.debug() reply.debug()
XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2") XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2")
XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0") XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0")
XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1") XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1")
XCTAssertEqual(reply.options.dnsServers, ["8.8.8.8", "4.4.4.4"]) XCTAssertEqual(reply.options.dnsServers, ["8.8.8.8", "4.4.4.4"])
} }
func testRoute() { func testRoute() {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route-gateway 10.8.0.1,route 192.168.0.0 255.255.255.0 10.8.0.12,topology subnet,ping 10,ping-restart 120,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route-gateway 10.8.0.1,route 192.168.0.0 255.255.255.0 10.8.0.12,topology subnet,ping 10,ping-restart 120,ifconfig 10.8.0.2 255.255.255.0,peer-id 0"
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
reply.debug() reply.debug()
let route = reply.options.routes4!.first! let route = reply.options.routes4!.first!
XCTAssertEqual(route.destination, "192.168.0.0") XCTAssertEqual(route.destination, "192.168.0.0")
XCTAssertEqual(route.mask, "255.255.255.0") XCTAssertEqual(route.mask, "255.255.255.0")
XCTAssertEqual(route.gateway, "10.8.0.12") XCTAssertEqual(route.gateway, "10.8.0.12")
@ -87,7 +87,7 @@ class PushTests: XCTestCase {
let msg = "PUSH_REPLY,dhcp-option DNS6 2001:4860:4860::8888,dhcp-option DNS6 2001:4860:4860::8844,tun-ipv6,route-gateway 10.8.0.1,topology subnet,ping 10,ping-restart 120,ifconfig-ipv6 fe80::601:30ff:feb7:ec01/64 fe80::601:30ff:feb7:dc02,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" let msg = "PUSH_REPLY,dhcp-option DNS6 2001:4860:4860::8888,dhcp-option DNS6 2001:4860:4860::8844,tun-ipv6,route-gateway 10.8.0.1,topology subnet,ping 10,ping-restart 120,ifconfig-ipv6 fe80::601:30ff:feb7:ec01/64 fe80::601:30ff:feb7:dc02,ifconfig 10.8.0.2 255.255.255.0,peer-id 0"
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
reply.debug() reply.debug()
XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2") XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2")
XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0") XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0")
XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1") XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1")
@ -96,19 +96,19 @@ class PushTests: XCTestCase {
XCTAssertEqual(reply.options.ipv6?.defaultGateway, "fe80::601:30ff:feb7:dc02") XCTAssertEqual(reply.options.ipv6?.defaultGateway, "fe80::601:30ff:feb7:dc02")
XCTAssertEqual(reply.options.dnsServers, ["2001:4860:4860::8888", "2001:4860:4860::8844"]) XCTAssertEqual(reply.options.dnsServers, ["2001:4860:4860::8888", "2001:4860:4860::8844"])
} }
func testCompressionFraming() { func testCompressionFraming() {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC" let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC"
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
reply.debug() reply.debug()
XCTAssertEqual(reply.options.compressionFraming, .compLZO) XCTAssertEqual(reply.options.compressionFraming, .compLZO)
} }
func testCompression() { func testCompression() {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC" let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC"
var reply: OpenVPN.PushReply var reply: OpenVPN.PushReply
reply = try! OpenVPN.PushReply(message: msg.appending(",comp-lzo no"))! reply = try! OpenVPN.PushReply(message: msg.appending(",comp-lzo no"))!
reply.debug() reply.debug()
XCTAssertEqual(reply.options.compressionFraming, .compLZO) XCTAssertEqual(reply.options.compressionFraming, .compLZO)
@ -134,7 +134,7 @@ class PushTests: XCTestCase {
XCTAssertEqual(reply.options.compressionFraming, .compress) XCTAssertEqual(reply.options.compressionFraming, .compress)
XCTAssertEqual(reply.options.compressionAlgorithm, .other) XCTAssertEqual(reply.options.compressionAlgorithm, .other)
} }
func testNCP() { func testNCP() {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM" let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM"
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
@ -147,32 +147,32 @@ class PushTests: XCTestCase {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.18 10.8.0.17,peer-id 3,cipher AES-256-GCM,auth-token" let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.18 10.8.0.17,peer-id 3,cipher AES-256-GCM,auth-token"
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
reply.debug() reply.debug()
XCTAssertEqual(reply.options.cipher, .aes256gcm) XCTAssertEqual(reply.options.cipher, .aes256gcm)
} }
func testPing() { func testPing() {
let msg = "PUSH_REPLY,route 192.168.1.0 255.255.255.0,route 10.0.2.0 255.255.255.0,dhcp-option DNS 192.168.1.99,dhcp-option DNS 176.103.130.130,route 10.0.2.1,topology net30,ping 10,ping-restart 60,ifconfig 10.0.2.14 10.0.2.13" let msg = "PUSH_REPLY,route 192.168.1.0 255.255.255.0,route 10.0.2.0 255.255.255.0,dhcp-option DNS 192.168.1.99,dhcp-option DNS 176.103.130.130,route 10.0.2.1,topology net30,ping 10,ping-restart 60,ifconfig 10.0.2.14 10.0.2.13"
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
reply.debug() reply.debug()
XCTAssertEqual(reply.options.keepAliveInterval, 10) XCTAssertEqual(reply.options.keepAliveInterval, 10)
} }
func testPingRestart() { func testPingRestart() {
let msg = "PUSH_REPLY,route 192.168.1.0 255.255.255.0,route 10.0.2.0 255.255.255.0,dhcp-option DNS 192.168.1.99,dhcp-option DNS 176.103.130.130,route 10.0.2.1,topology net30,ping 10,ping-restart 60,ifconfig 10.0.2.14 10.0.2.13" let msg = "PUSH_REPLY,route 192.168.1.0 255.255.255.0,route 10.0.2.0 255.255.255.0,dhcp-option DNS 192.168.1.99,dhcp-option DNS 176.103.130.130,route 10.0.2.1,topology net30,ping 10,ping-restart 60,ifconfig 10.0.2.14 10.0.2.13"
let reply = try! OpenVPN.PushReply(message: msg)! let reply = try! OpenVPN.PushReply(message: msg)!
reply.debug() reply.debug()
XCTAssertEqual(reply.options.keepAliveTimeout, 60) XCTAssertEqual(reply.options.keepAliveTimeout, 60)
} }
func testProvost() { func testProvost() {
let msg = "PUSH_REPLY,route 87.233.192.218,route 87.233.192.219,route 87.233.192.220,route 87.248.186.252,route 92.241.171.245,route 103.246.200.0 255.255.252.0,route 109.239.140.0 255.255.255.0,route 128.199.0.0 255.255.0.0,route 13.125.0.0 255.255.0.0,route 13.230.0.0 255.254.0.0,route 13.56.0.0 255.252.0.0,route 149.154.160.0 255.255.252.0,route 149.154.164.0 255.255.252.0,route 149.154.168.0 255.255.252.0,route 149.154.172.0 255.255.252.0,route 159.122.128.0 255.255.192.0,route 159.203.0.0 255.255.0.0,route 159.65.0.0 255.255.0.0,route 159.89.0.0 255.255.0.0,route 165.227.0.0 255.255.0.0,route 167.99.0.0 255.255.0.0,route 174.138.0.0 255.255.128.0,route 176.67.169.0 255.255.255.0,route 178.239.88.0 255.255.248.0,route 178.63.0.0 255.255.0.0,route 18.130.0.0 255.255.0.0,route 18.144.0.0 255.255.0.0,route 18.184.0.0 255.254.0.0,route 18.194.0.0 255.254.0.0,route 18.196.0.0 255.254.0.0,route 18.204.0.0 255.252.0.0,push-continuation 2" let msg = "PUSH_REPLY,route 87.233.192.218,route 87.233.192.219,route 87.233.192.220,route 87.248.186.252,route 92.241.171.245,route 103.246.200.0 255.255.252.0,route 109.239.140.0 255.255.255.0,route 128.199.0.0 255.255.0.0,route 13.125.0.0 255.255.0.0,route 13.230.0.0 255.254.0.0,route 13.56.0.0 255.252.0.0,route 149.154.160.0 255.255.252.0,route 149.154.164.0 255.255.252.0,route 149.154.168.0 255.255.252.0,route 149.154.172.0 255.255.252.0,route 159.122.128.0 255.255.192.0,route 159.203.0.0 255.255.0.0,route 159.65.0.0 255.255.0.0,route 159.89.0.0 255.255.0.0,route 165.227.0.0 255.255.0.0,route 167.99.0.0 255.255.0.0,route 174.138.0.0 255.255.128.0,route 176.67.169.0 255.255.255.0,route 178.239.88.0 255.255.248.0,route 178.63.0.0 255.255.0.0,route 18.130.0.0 255.255.0.0,route 18.144.0.0 255.255.0.0,route 18.184.0.0 255.254.0.0,route 18.194.0.0 255.254.0.0,route 18.196.0.0 255.254.0.0,route 18.204.0.0 255.252.0.0,push-continuation 2"
let reply = try? OpenVPN.PushReply(message: msg)! let reply = try? OpenVPN.PushReply(message: msg)!
reply?.debug() reply?.debug()
} }
func testPeerInfo() { func testPeerInfo() {
let peerInfo = CoreConfiguration.OpenVPN.peerInfo() let peerInfo = CoreConfiguration.OpenVPN.peerInfo()
print(peerInfo) print(peerInfo)

View File

@ -51,22 +51,22 @@ dccdb953cdf32bea03f365760b0ed800
7aed27125592a7148d25c87fdbe0a3f6 7aed27125592a7148d25c87fdbe0a3f6
-----END OpenVPN Static key V1----- -----END OpenVPN Static key V1-----
""" """
override func setUp() { override func setUp() {
super.setUp() super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
} }
override func tearDown() { override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown() super.tearDown()
} }
func testFileBidirectional() { func testFileBidirectional() {
let expected = Data(hex: "cf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e6") let expected = Data(hex: "cf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e6")
let key = OpenVPN.StaticKey(file: content, direction: nil) let key = OpenVPN.StaticKey(file: content, direction: nil)
XCTAssertNotNil(key) XCTAssertNotNil(key)
XCTAssertEqual(key?.hmacSendKey.toData(), expected) XCTAssertEqual(key?.hmacSendKey.toData(), expected)
XCTAssertEqual(key?.hmacReceiveKey.toData(), expected) XCTAssertEqual(key?.hmacReceiveKey.toData(), expected)
} }
@ -76,7 +76,7 @@ dccdb953cdf32bea03f365760b0ed800
let receive = Data(hex: "cf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e6") let receive = Data(hex: "cf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e6")
let key = OpenVPN.StaticKey(file: content, direction: .client) let key = OpenVPN.StaticKey(file: content, direction: .client)
XCTAssertNotNil(key) XCTAssertNotNil(key)
XCTAssertEqual(key?.hmacSendKey.toData(), send) XCTAssertEqual(key?.hmacSendKey.toData(), send)
XCTAssertEqual(key?.hmacReceiveKey.toData(), receive) XCTAssertEqual(key?.hmacReceiveKey.toData(), receive)
} }

View File

@ -41,9 +41,9 @@ import CTunnelKitOpenVPNProtocol
public class TestUtils { public class TestUtils {
public static func uniqArray(_ v: [Int]) -> [Int] { public static func uniqArray(_ v: [Int]) -> [Int] {
return v.reduce([]){ $0.contains($1) ? $0 : $0 + [$1] } return v.reduce([]) { $0.contains($1) ? $0 : $0 + [$1] }
} }
public static func generateDataSuite(_ size: Int, _ count: Int) -> [Data] { public static func generateDataSuite(_ size: Int, _ count: Int) -> [Data] {
var suite = [Data]() var suite = [Data]()
for _ in 0..<count { for _ in 0..<count {
@ -51,7 +51,7 @@ public class TestUtils {
} }
return suite return suite
} }
private init() { private init() {
} }
} }
@ -80,7 +80,7 @@ extension Decrypter {
dest.removeSubrange(destLength..<dest.count) dest.removeSubrange(destLength..<dest.count)
return Data(dest) return Data(dest)
} }
func verifyData(_ data: Data, flags: UnsafePointer<CryptoFlags>?) throws { func verifyData(_ data: Data, flags: UnsafePointer<CryptoFlags>?) throws {
let srcLength = data.count let srcLength = data.count
try data.withUnsafeBytes { try data.withUnsafeBytes {

View File

@ -30,7 +30,7 @@ import CTunnelKitOpenVPNProtocol
final class XORTests: XCTestCase { final class XORTests: XCTestCase {
private let mask = Data(hex: "f76dab30") private let mask = Data(hex: "f76dab30")
override func setUpWithError() throws { override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
} }