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
class PacketTunnelProvider: OpenVPNTunnelProvider {
override func startTunnel(options: [String : NSObject]? = nil) async throws {
override func startTunnel(options: [String: NSObject]? = nil) async throws {
dataCountInterval = 3
try await super.startTunnel(options: options)
}

View File

@ -31,38 +31,37 @@ private let log = SwiftyBeaver.self
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let logDestination = ConsoleDestination()
logDestination.minLevel = .debug
logDestination.format = "$DHH:mm:ss$d $L $N.$F:$l - $M"
log.addDestination(logDestination)
// Override point for customization after application launch.
return true
}
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.
// 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) {
// 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.
}
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.
}
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.
}
func applicationWillTerminate(_ application: UIApplication) {
// 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 {
@IBOutlet var textUsername: UITextField!
@IBOutlet var textPassword: UITextField!
@IBOutlet var textServer: UITextField!
@IBOutlet var textDomain: UITextField!
@IBOutlet var textPort: UITextField!
@IBOutlet var switchTCP: UISwitch!
@IBOutlet var buttonConnection: UIButton!
@IBOutlet var textLog: UITextView!
private let vpn = NetworkExtensionVPN()
private var vpnStatus: VPNStatus = .disconnected
private let keychain = Keychain(group: appGroup)
private var cfg: OpenVPN.ProviderConfiguration?
override func viewDidLoad() {
super.viewDidLoad()
textServer.text = "nl-free-50"
textDomain.text = "protonvpn.net"
textPort.text = "80"
switchTCP.isOn = false
textUsername.text = ""
textPassword.text = ""
NotificationCenter.default.addObserver(
self,
selector: #selector(VPNStatusDidChange(notification:)),
@ -86,20 +86,20 @@ class OpenVPNViewController: UIViewController {
// testFetchRef()
}
@IBAction func connectionClicked(_ sender: Any) {
switch vpnStatus {
case .disconnected:
connect()
case .connected, .connecting, .disconnecting:
disconnect()
}
}
@IBAction func tcpClicked(_ sender: Any) {
}
func connect() {
let server = textServer.text!
let domain = textDomain.text!
@ -136,7 +136,7 @@ class OpenVPNViewController: UIViewController {
)
}
}
func disconnect() {
Task {
await vpn.disconnect()
@ -152,20 +152,20 @@ class OpenVPNViewController: UIViewController {
}
textLog.text = try? String(contentsOf: url)
}
func updateButton() {
switch vpnStatus {
case .connected, .connecting:
buttonConnection.setTitle("Disconnect", for: .normal)
case .disconnected:
buttonConnection.setTitle("Connect", for: .normal)
case .disconnecting:
buttonConnection.setTitle("Disconnecting", for: .normal)
}
}
@objc private func VPNStatusDidChange(notification: Notification) {
vpnStatus = notification.vpnStatus
print("VPNStatusDidChange: \(vpnStatus)")

View File

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

View File

@ -31,8 +31,6 @@ private let log = SwiftyBeaver.self
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let logDestination = ConsoleDestination()
logDestination.minLevel = .debug
@ -46,6 +44,4 @@ class AppDelegate: NSObject, NSApplicationDelegate {
// 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 {
@IBOutlet var textUsername: NSTextField!
@IBOutlet var textPassword: NSTextField!
@IBOutlet var textServer: NSTextField!
@IBOutlet var textDomain: NSTextField!
@IBOutlet var textPort: NSTextField!
@IBOutlet var buttonConnection: NSButton!
private let vpn = NetworkExtensionVPN()
private var vpnStatus: VPNStatus = .disconnected
private let keychain = Keychain(group: appGroup)
private var cfg: OpenVPN.ProviderConfiguration?
override func viewDidLoad() {
super.viewDidLoad()
textServer.stringValue = "nl-free-50"
textDomain.stringValue = "protonvpn.net"
textPort.stringValue = "80"
textUsername.stringValue = ""
textPassword.stringValue = ""
NotificationCenter.default.addObserver(
self,
selector: #selector(VPNStatusDidChange(notification:)),
@ -78,20 +78,20 @@ class OpenVPNViewController: NSViewController {
Task {
await vpn.prepare()
}
// testFetchRef()
}
@IBAction func connectionClicked(_ sender: Any) {
switch vpnStatus {
case .disconnected:
connect()
case .connected, .connecting, .disconnecting:
disconnect()
}
}
func connect() {
let server = textServer.stringValue
let domain = textDomain.stringValue
@ -127,7 +127,7 @@ class OpenVPNViewController: NSViewController {
)
}
}
func disconnect() {
Task {
await vpn.disconnect()
@ -138,15 +138,15 @@ class OpenVPNViewController: NSViewController {
switch vpnStatus {
case .connected, .connecting:
buttonConnection.title = "Disconnect"
case .disconnected:
buttonConnection.title = "Connect"
case .disconnecting:
buttonConnection.title = "Disconnecting"
}
}
@objc private func VPNStatusDidChange(notification: Notification) {
vpnStatus = notification.vpnStatus
print("VPNStatusDidChange: \(vpnStatus)")
@ -175,4 +175,3 @@ class OpenVPNViewController: NSViewController {
// print("\(username) -> \(fetchedPassword)")
// }
}

View File

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

View File

@ -41,7 +41,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
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:.
}
}

View File

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

View File

@ -418,6 +418,7 @@
0E05416725A232FD00EFC5FF /* Resources */,
0E0541AD25A2343500EFC5FF /* Embed App Extensions */,
0E05438525A240E400EFC5FF /* Embed Frameworks */,
0EB5A56B29F1C9C8005313B3 /* SwiftLint */,
);
buildRules = (
);
@ -463,6 +464,7 @@
0E05422A25A236EB00EFC5FF /* Resources */,
0E05428425A239C600EFC5FF /* Embed App Extensions */,
0E05438825A240E900EFC5FF /* Embed Frameworks */,
0EB5A56A29F1C8FC005313B3 /* SwiftLint */,
);
buildRules = (
);
@ -700,6 +702,45 @@
};
/* 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 */
0E05416525A232FD00EFC5FF /* Sources */ = {
isa = PBXSourcesBuildPhase;

View File

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

View File

@ -67,10 +67,10 @@ public protocol GenericSocket {
/// The address of the remote endpoint.
var remoteAddress: String? { get }
/// `true` if the socket has a better path.
var hasBetterPath: Bool { get }
/// `true` if the socket was shut down.
var isShutdown: Bool { get }
@ -94,7 +94,7 @@ public protocol GenericSocket {
Shuts down the socket
**/
func shutdown()
/**
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")
private var queue: DispatchQueue?
private var timer: DispatchSourceTimer?
private var lastWifiName: String?
/**
@ -89,7 +89,7 @@ public class InterfaceObserver: NSObject {
self.fireWifiChange(withSSID: $0)
}
}
private func fireWifiChange(withSSID ssid: String?) {
if ssid != lastWifiName {
if let current = ssid {

View File

@ -48,7 +48,7 @@ public class MemoryDestination: BaseDestination, CustomStringConvertible {
super.init()
asynchronously = false
}
/**
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)
}
}
// MARK: BaseDestination
// 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
public var description: String {
return executeSynchronously {
return self.buffer.joined(separator: "\n")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,29 +28,29 @@ import Foundation
/// Helps expressing integers in bytes, kB, MB, GB.
public enum DataUnit: UInt, CustomStringConvertible {
case byte = 1
case kilobyte = 1024
case megabyte = 1048576
case gigabyte = 1073741824
fileprivate var showsDecimals: Bool {
switch self {
case .byte, .kilobyte:
return false
case .megabyte, .gigabyte:
return true
}
}
fileprivate var boundary: UInt {
return UInt(0.1 * Double(rawValue))
}
// MARK: CustomStringConvertible
public var description: String {
switch self {
case .byte:
@ -82,7 +82,7 @@ extension UInt: DataUnitRepresentable {
.kilobyte,
.byte
]
public var descriptionAsDataUnit: String {
if self == 0 {
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+)$")
public let address: String
public let proto: EndpointProtocol
public init(_ address: String, _ proto: EndpointProtocol) {
@ -60,7 +60,7 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
public var isHostname: Bool {
!isIPv4 && !isIPv6
}
public func withRandomPrefixLength(_ length: Int) throws -> Endpoint {
guard isHostname else {
return self
@ -69,9 +69,9 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
let prefixedAddress = "\(prefix.toHex()).\(address)"
return Endpoint(prefixedAddress, proto)
}
// MARK: RawRepresentable
public init?(rawValue: String) {
let components = Self.rx.groups(in: rawValue)
guard components.count == 3 else {
@ -90,9 +90,9 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
public var rawValue: String {
"\(address):\(proto.socketType.rawValue):\(proto.port)"
}
// MARK: CustomStringConvertible
public var description: String {
"\(address.maskedDescription):\(proto.rawValue)"
}
@ -100,10 +100,10 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
/// Defines the communication protocol of an endpoint.
public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvertible {
/// The socket type.
public let socketType: SocketType
/// The remote port.
public let port: UInt16
@ -111,9 +111,9 @@ public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvert
self.socketType = socketType
self.port = port
}
// MARK: RawRepresentable
public init?(rawValue: String) {
let components = rawValue.components(separatedBy: ":")
guard components.count == 2 else {
@ -127,13 +127,13 @@ public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvert
}
self.init(socketType, port)
}
public var rawValue: String {
"\(socketType.rawValue):\(port)"
}
// MARK: CustomStringConvertible
public var description: String {
rawValue
}
@ -146,7 +146,7 @@ extension EndpointProtocol: Codable {
let proto = EndpointProtocol(rawValue: rawValue) ?? EndpointProtocol(.udp, 1198)
self.init(proto.socketType, proto.port)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
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.
*/
func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void)
/**
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.
*/
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?)
/**
Writes some packets to the interface.

View File

@ -27,9 +27,9 @@ import Foundation
/// Helper for handling IP headers.
public struct IPHeader {
private static let ipV4: UInt8 = 4
private static let ipV6: UInt8 = 6
private static let ipV4ProtocolNumber = AF_INET 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.
public struct IPv4Settings: Codable, Equatable, CustomStringConvertible {
/// Represents an IPv4 route in the routing table.
public struct Route: Codable, Hashable, CustomStringConvertible {
/// The destination host or subnet.
public let destination: String
/// The address mask.
public let mask: String
/// The address of the gateway (falls back to global gateway).
public let gateway: String?
public init(_ destination: String, _ mask: String?, _ gateway: String?) {
self.destination = destination
self.mask = mask ?? "255.255.255.255"
self.gateway = gateway
}
// MARK: CustomStringConvertible
public var description: String {
"{\(destination)/\(mask) \(gateway?.description ?? "*")}"
}
}
/// The address.
public let address: String
/// The address mask.
public let addressMask: String
/// The address of the default gateway.
public let defaultGateway: String
/// The additional routes.
@available(*, deprecated, message: "Store routes separately")
public let routes: [Route]
public init(address: String, addressMask: String, defaultGateway: String) {
self.address = address
self.addressMask = addressMask
@ -80,9 +80,9 @@ public struct IPv4Settings: Codable, Equatable, CustomStringConvertible {
self.defaultGateway = defaultGateway
self.routes = routes
}
// MARK: CustomStringConvertible
public var description: String {
"addr \(address) netmask \(addressMask) gw \(defaultGateway)"
}

View File

@ -27,45 +27,45 @@ import Foundation
/// Encapsulates the IPv6 settings for the tunnel.
public struct IPv6Settings: Codable, Equatable, CustomStringConvertible {
/// Represents an IPv6 route in the routing table.
public struct Route: Codable, Hashable, CustomStringConvertible {
/// The destination host or subnet.
public let destination: String
/// The address prefix length.
public let prefixLength: UInt8
/// The address of the gateway (falls back to global gateway).
public let gateway: String?
public init(_ destination: String, _ prefixLength: UInt8?, _ gateway: String?) {
self.destination = destination
self.prefixLength = prefixLength ?? 3
self.gateway = gateway
}
// MARK: CustomStringConvertible
public var description: String {
"{\(destination.maskedDescription)/\(prefixLength) \(gateway?.maskedDescription ?? "*")}"
}
}
/// The address.
public let address: String
/// The address prefix length.
public let addressPrefixLength: UInt8
/// The address of the default gateway.
public let defaultGateway: String
/// The additional routes.
@available(*, deprecated, message: "Store routes separately")
public let routes: [Route]
public init(address: String, addressPrefixLength: UInt8, defaultGateway: String) {
self.address = address
self.addressPrefixLength = addressPrefixLength
@ -82,7 +82,7 @@ public struct IPv6Settings: Codable, Equatable, CustomStringConvertible {
}
// MARK: CustomStringConvertible
public var description: String {
"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).
public protocol LinkInterface: IOInterface {
/// When `true`, packets delivery is guaranteed.
var isReliable: Bool { get }

View File

@ -27,24 +27,24 @@ import Foundation
/// Encapsulates a proxy setting.
public struct Proxy: Codable, Equatable, RawRepresentable, CustomStringConvertible {
/// The proxy address.
public let address: String
/// The proxy port.
public let port: UInt16
public init(_ address: String, _ port: UInt16) {
self.address = address
self.port = port
}
// MARK: RawRepresentable
public var rawValue: String {
return "\(address):\(port)"
}
public init?(rawValue: String) {
let comps = rawValue.components(separatedBy: ":")
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)
}
// MARK: CustomStringConvertible
public var description: String {
return rawValue
}

View File

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

View File

@ -27,7 +27,7 @@ import Foundation
/// Defines the basics of a VPN session.
public protocol Session {
/**
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.
*/
func setLink(_ link: LinkInterface)
/**
Returns `true` if the current session can rebind to a new link with `rebindLink(...)`.
- Returns: `true` if supports link rebinding.
*/
func canRebindLink() -> Bool
/**
Rebinds the session to a new link if supported.
@ -53,7 +53,7 @@ public protocol Session {
- Seealso: `canRebindLink()`
*/
func rebindLink(_ link: LinkInterface)
/**
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.
*/
func setTunnel(tunnel: TunnelInterface)
/**
Returns the current data bytes count.
- Returns: The current data bytes count.
*/
func dataCount() -> DataCount?
/**
Returns the current server configuration.
@ -83,7 +83,7 @@ public protocol Session {
- Parameter error: An optional `Error` being the reason of the shutdown.
*/
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.
@ -91,7 +91,7 @@ public protocol Session {
- Seealso: `OpenVPNSessionDelegate.sessionDidStop(...)`
*/
func reconnect(error: Error?)
/**
Cleans up the session resources.
*/

View File

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

View File

@ -61,9 +61,9 @@ public func Z(_ data: Data) -> ZeroingData {
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)
//}
// }
public func Z(_ string: String, nullTerminated: Bool) -> ZeroingData {
return ZeroingData(string: string, nullTerminated: nullTerminated)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,34 +46,34 @@ extension OpenVPN {
case lastError = "OpenVPN.LastError"
}
/// Optional version identifier about the client pushed to server in peer-info as `IV_UI_VER`.
public var versionIdentifier: String?
/// The configuration title.
public let title: String
/// The access group for shared data.
public let appGroup: String
/// The client configuration.
public let configuration: OpenVPN.Configuration
/// The optional username.
public var username: String?
/// Enables debugging.
public var shouldDebug = false
/// Debug log path.
public var debugLogPath: String? = nil
public var debugLogPath: String?
/// Optional debug log format (SwiftyBeaver format).
public var debugLogFormat: String? = nil
public var debugLogFormat: String?
/// Mask private data in debug log (default is `true`).
public var masksPrivateData = true
public init(_ title: String, appGroup: String, configuration: OpenVPN.Configuration) {
self.title = title
self.appGroup = appGroup
@ -142,7 +142,7 @@ extension OpenVPN.ProviderConfiguration {
public var lastError: OpenVPNProviderError? {
return defaults?.openVPNLastError
}
/**
The URL of the latest debug log.
*/
@ -208,7 +208,7 @@ extension UserDefaults {
openVPNDataCountArray = [newValue.received, newValue.sent]
}
}
@objc private var openVPNDataCountArray: [UInt]? {
get {
return array(forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue) as? [UInt]
@ -217,7 +217,7 @@ extension UserDefaults {
set(newValue, forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue)
}
}
private func openVPNRemoveDataCountArray() {
removeObject(forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue)
}
@ -249,7 +249,7 @@ extension UserDefaults {
}
}
}
public fileprivate(set) var openVPNLastError: OpenVPNProviderError? {
get {
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.
public enum OpenVPNProviderConfigurationError: Error {
/// A field in the `OpenVPNProvider.Configuration` provided is incorrect or incomplete.
case parameter(name: String)
/// Credentials are missing or inaccessible.
case credentials(details: String)
/// The pseudo-random number generator could not be initialized.
case prngInitialization
/// The TLS certificate could not be serialized.
case certificateSerialization
}
/// The errors causing a tunnel disconnection.
public enum OpenVPNProviderError: String, Error {
/// Socket endpoint could not be resolved.
case dnsFailure
/// No more endpoints available to try.
case exhaustedEndpoints
/// Socket failed to reach active state.
case socketActivity
/// Credentials authentication failed.
case authentication
/// TLS could not be initialized (e.g. malformed CA or client PEMs).
case tlsInitialization
@ -75,37 +75,37 @@ public enum OpenVPNProviderError: String, Error {
/// TLS handshake failed.
case tlsHandshake
/// The encryption logic could not be initialized (e.g. PRNG, algorithms).
case encryptionInitialization
/// Data encryption/decryption failed.
case encryptionData
/// The LZO engine failed.
case lzo
/// Server uses an unsupported compression algorithm.
case serverCompression
/// Tunnel timed out.
case timeout
/// An error occurred at the link level.
case linkError
/// Network routing information is missing or incomplete.
case routing
/// The current network changed (e.g. switched from WiFi to data connection).
case networkChanged
/// Default gateway could not be attained.
case gatewayUnattainable
/// Remove server has shut down.
case serverShutdown
/// The server replied in an unexpected way.
case unexpectedReply
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,11 +29,11 @@ import TunnelKitOpenVPNCore
/// Processes data packets according to a XOR method.
public struct XORProcessor {
private let method: OpenVPN.XORMethod?
public init(method: OpenVPN.XORMethod?) {
self.method = method
}
/**
Returns an array of data packets processed according to XOR method.
@ -49,7 +49,7 @@ public struct XORProcessor {
processPacket($0, outbound: outbound)
}
}
/**
Returns a data packet processed according to XOR method.
@ -64,13 +64,13 @@ public struct XORProcessor {
switch method {
case .xormask(let mask):
return Self.xormask(packet: packet, mask: mask)
case .xorptrpos:
return Self.xorptrpos(packet: packet)
case .reverse:
return Self.reverse(packet: packet)
case .obfuscate(let mask):
if outbound {
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]
})
}
private static func xorptrpos(packet: Data) -> Data {
Data(packet.enumerated().map { (index, byte) in
byte ^ UInt8(truncatingIfNeeded: index &+ 1)
})
}
private static func reverse(packet: Data) -> Data {
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) {
// BEGIN: TunnelKit
guard let tunnelProviderProtocol = protocolConfiguration as? NETunnelProviderProtocol else {
fatalError("Not a NETunnelProviderProtocol")
}
@ -39,7 +39,7 @@ open class WireGuardTunnelProvider: NEPacketTunnelProvider {
completionHandler(WireGuardProviderError.savedProtocolConfigurationIsInvalid)
return
}
configureLogging()
// END: TunnelKit
@ -128,7 +128,7 @@ extension WireGuardTunnelProvider {
private func configureLogging() {
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"
if cfg.shouldDebug {
let console = ConsoleDestination()
console.useNSLog = true

View File

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

View File

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

View File

@ -42,18 +42,18 @@ extension WireGuard {
case lastError = "WireGuard.LastError"
}
public let title: String
public let appGroup: String
public let configuration: WireGuard.Configuration
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) {
self.title = title
@ -68,7 +68,7 @@ extension WireGuard {
}
}
}
// MARK: NetworkExtensionConfiguration
extension WireGuard.ProviderConfiguration: NetworkExtensionConfiguration {
@ -94,7 +94,6 @@ extension WireGuard.ProviderConfiguration {
public var lastError: WireGuardProviderError? {
return defaults?.wireGuardLastError
}
public var urlForDebugLog: URL? {
return defaults?.wireGuardURLForDebugLog(appGroup: appGroup)

View File

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

View File

@ -29,10 +29,10 @@ extension NSRegularExpression {
public convenience init(_ pattern: String) {
try! self.init(pattern: pattern, options: [])
}
public func groups(in string: String) -> [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 {
return
}
@ -48,7 +48,7 @@ extension NSRegularExpression {
extension NSRegularExpression {
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 {
return
}
@ -57,7 +57,7 @@ extension NSRegularExpression {
block(tokens)
}
}
public func enumerateSpacedArguments(in string: String, using block: ([String]) -> Void) {
enumerateSpacedComponents(in: string) { (tokens) in
var args = tokens

View File

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

View File

@ -40,26 +40,26 @@ import XCTest
@testable import TunnelKitCore
class RawPerformanceTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
// 0.434s
func testUInt16FromBuffer() {
let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66])
measure {
for _ in 0..<1000000 {
let _ = data.UInt16Value(from: 3)
_ = data.UInt16Value(from: 3)
}
}
}
// // 0.463s
// func testUInt16FromPointers() {
// let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66])
@ -81,18 +81,18 @@ class RawPerformanceTests: XCTestCase {
// }
// }
// }
// 0.469s
func testUInt32FromPointers() {
let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66])
measure {
for _ in 0..<1000000 {
let _ = data.UInt32Value(from: 1)
_ = data.UInt32Value(from: 1)
}
}
}
// // 0.071s
// func testRandomUInt32FromBuffer() {
// measure {
@ -101,12 +101,12 @@ class RawPerformanceTests: XCTestCase {
// }
// }
// }
// 0.063s
func testRandomUInt32FromPointers() {
measure {
for _ in 0..<10000 {
let _ = try! SecureRandom.uint32()
_ = try! SecureRandom.uint32()
}
}
}
@ -127,7 +127,7 @@ class RawPerformanceTests: XCTestCase {
measure {
for data in suite {
// 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)
measure {
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() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testEntryMatch4() {
let entry24 = RoutingTableEntry(iPv4Network: "192.168.1.0/24", gateway: nil, networkInterface: "en0")
print(entry24.networkMask()!)
@ -65,10 +65,10 @@ class RoutingTests: XCTestCase {
XCTAssertFalse(entry24.matchesDestination("abcd:efef:1233::\(i)"))
}
}
func testFindGatewayLAN4() {
let table = RoutingTable()
for entry in table.ipv4() {
print(entry)
}
@ -83,11 +83,11 @@ class RoutingTests: XCTestCase {
func testFindGatewayLAN6() {
let table = RoutingTable()
for entry in table.ipv6() {
print(entry)
}
if let defaultGateway = table.defaultGateway6()?.gateway() {
print("Default gateway: \(defaultGateway)")
if let lan = table.broadestRoute6(matchingDestination: defaultGateway) {
@ -95,13 +95,13 @@ class RoutingTests: XCTestCase {
}
}
}
func testPartitioning() {
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 v6 = RoutingTableEntry(iPv6Network: "abcd:efef:120::/46", gateway: nil, networkInterface: "en0")
let v6Boundary = RoutingTableEntry(iPv6Network: "abcd:efef:120::/127", gateway: nil, networkInterface: "en0")
guard let v4parts = v4.partitioned() else {
fatalError()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@ class ControlChannelTests: XCTestCase {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
@ -55,16 +55,16 @@ class ControlChannelTests: XCTestCase {
let key = OpenVPN.StaticKey(biData: Data(hex: hex))
let server = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: OpenVPN.Digest.sha1.rawValue)
XCTAssertNoThrow(try server.configure(withCipherEncKey: nil, cipherDecKey: nil, hmacEncKey: key.hmacReceiveKey, hmacDecKey: key.hmacSendKey))
// let original = Data(hex: "38858fe14742fdae40e67c9137933a412a711c0d0514aca6db6476d17d000000015b96c9470000000000")
let hmac = Data(hex: "e67c9137933a412a711c0d0514aca6db6476d17d")
let subject = Data(hex: "000000015b96c94738858fe14742fdae400000000000")
let data = hmac + subject
print(data.toHex())
XCTAssertNoThrow(try server.decrypter().verifyData(data, flags: nil))
}
// 38 // HARD_RESET
// bccfd171ce22e085 // session_id
// e01a3454c354f3c3093b00fc8d6228a8b69ef503d56f6a572ebd26a800711b4cd4df2b9daf06cb90f82379e7815e39fb73be4ac5461752db4f35120474af82b2 // hmac
@ -74,11 +74,11 @@ class ControlChannelTests: XCTestCase {
func testAuth() {
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 original = Data(hex: "38bccfd1")
let original = Data(hex: "38bccfd171ce22e085e01a3454c354f3c3093b00fc8d6228a8b69ef503d56f6a572ebd26a800711b4cd4df2b9daf06cb90f82379e7815e39fb73be4ac5461752db4f35120474af82b2000000015b93b65d0000000000")
let timestamp = UInt32(0x5b93b65d)
let packet: ControlPacket
do {
packet = try client.deserialize(data: original, start: 0, end: nil)
@ -90,7 +90,7 @@ class ControlChannelTests: XCTestCase {
XCTAssertEqual(packet.sessionId, Data(hex: "bccfd171ce22e085"))
XCTAssertNil(packet.ackIds)
XCTAssertEqual(packet.packetId, 0)
let raw: Data
do {
raw = try server.serialize(packet: packet, timestamp: timestamp)
@ -109,7 +109,7 @@ class ControlChannelTests: XCTestCase {
let original = Data(hex: "407bf3d6a260e6476d000000015ba4155887940856ddb70e01693980c5c955cb5506ecf9fd3e0bcee0c802ec269427d43bf1cda1837ffbf30c83cacff852cd0b7f4c")
let timestamp = UInt32(0x5ba41558)
let packet: ControlPacket
do {
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 var enc: DataPathEncrypter!
private var dec: DataPathDecrypter!
override func setUp() {
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testCBC() {
prepareBox(cipher: "aes-128-cbc", digest: "sha256")
privateTestDataPathHigh(peerId: nil)
privateTestDataPathLow(peerId: nil)
}
func testFloatingCBC() {
prepareBox(cipher: "aes-128-cbc", digest: "sha256")
privateTestDataPathHigh(peerId: 0x64385837)
privateTestDataPathLow(peerId: 0x64385837)
}
func testGCM() {
prepareBox(cipher: "aes-256-gcm", digest: nil)
privateTestDataPathHigh(peerId: nil)
@ -78,14 +78,14 @@ class DataPathEncryptionTests: XCTestCase {
privateTestDataPathHigh(peerId: 0x64385837)
privateTestDataPathLow(peerId: 0x64385837)
}
func prepareBox(cipher: String, digest: String?) {
let box = CryptoBox(cipherAlgorithm: cipher, digestAlgorithm: digest)
try! box.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey)
enc = box.encrypter().dataPathEncrypter()
dec = box.decrypter().dataPathDecrypter()
}
func privateTestDataPathHigh(peerId: UInt32?) {
let path = DataPath(
encrypter: enc,

View File

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

View File

@ -41,17 +41,17 @@ import CTunnelKitOpenVPNProtocol
class EncryptionPerformanceTests: XCTestCase {
private var cbcEncrypter: Encrypter!
private var cbcDecrypter: Decrypter!
private var gcmEncrypter: Encrypter!
private var gcmDecrypter: Decrypter!
override func setUp() {
let cipherKey = try! SecureRandom.safeData(length: 32)
let hmacKey = try! SecureRandom.safeData(length: 32)
let cbc = CryptoBox(cipherAlgorithm: "aes-128-cbc", digestAlgorithm: "sha1")
try! cbc.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey)
cbcEncrypter = cbc.encrypter()
@ -72,7 +72,7 @@ class EncryptionPerformanceTests: XCTestCase {
let suite = TestUtils.generateDataSuite(1000, 100000)
measure {
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 {
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 cipherDecKey: ZeroingData!
private var hmacEncKey: ZeroingData!
private var hmacDecKey: ZeroingData!
override func setUp() {
cipherEncKey = 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)
XCTAssertNoThrow(try server.decrypter().verifyData(encrypted, flags: nil))
}
func testGCM() {
let (client, server) = clientServer("aes-256-gcm", nil)
let packetId: [UInt8] = [0x56, 0x34, 0x12, 0x00]
let ad: [UInt8] = [0x00, 0x12, 0x34, 0x56]
var flags = packetId.withUnsafeBufferPointer { (iv) in
@ -92,7 +92,7 @@ class EncryptionTests: XCTestCase {
let decrypted = try! server.decrypter().decryptData(encrypted, flags: &flags)
XCTAssertEqual(plain, decrypted)
}
func testCTR() {
let (client, server) = clientServer("aes-256-ctr", "sha256")
@ -119,17 +119,17 @@ class EncryptionTests: XCTestCase {
print(md5)
XCTAssertEqual(md5, exp)
}
func testPrivateKeyDecryption() {
privateTestPrivateKeyDecryption(pkcs: "1")
privateTestPrivateKeyDecryption(pkcs: "8")
}
private func privateTestPrivateKeyDecryption(pkcs: String) {
let bundle = Bundle.module
let encryptedPath = bundle.path(forResource: "tunnelbear", ofType: "enc.\(pkcs).key")!
let decryptedPath = bundle.path(forResource: "tunnelbear", ofType: "key")!
XCTAssertThrowsError(try TLSBox.decryptedPrivateKey(fromPath: encryptedPath, passphrase: "wrongone"))
let decryptedViaPath = try! TLSBox.decryptedPrivateKey(fromPath: encryptedPath, passphrase: "foobar")
print(decryptedViaPath)
@ -137,17 +137,17 @@ class EncryptionTests: XCTestCase {
let decryptedViaString = try! TLSBox.decryptedPrivateKey(fromPEM: encryptedPEM, passphrase: "foobar")
print(decryptedViaString)
XCTAssertEqual(decryptedViaPath, decryptedViaString)
let expDecrypted = try! String(contentsOfFile: decryptedPath)
XCTAssertEqual(decryptedViaPath, expDecrypted)
}
func testCertificatePreamble() {
let url = Bundle.module.url(forResource: "tunnelbear", withExtension: "crt")!
let cert = OpenVPN.CryptoContainer(pem: try! String(contentsOf: url))
XCTAssert(cert.pem.hasPrefix("-----BEGIN"))
}
private func clientServer(_ c: String?, _ d: String?) -> (CryptoBox, CryptoBox) {
let client = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d)
let server = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d)

View File

@ -49,19 +49,19 @@ class LinkTests: XCTestCase {
}
// UDP
func testUnreliableControlQueue() {
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 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] {
XCTAssertEqual(TestUtils.uniqArray(seq.sorted()), handleControlSequence(seq))
}
}
// TCP
// private func testPacketStream() {
// var bytes: [UInt8] = []
// var until: Int
@ -156,25 +156,25 @@ class LinkTests: XCTestCase {
}
return hdl
}
private func enqueueControl(_ q: inout [Int], _ id: inout Int, _ p: Int, _ h: (Int) -> Void) {
q.append(p)
q.sort { (p1, p2) -> Bool in
return (p1 < p2)
}
print("q = \(q)")
print("id = \(id)")
for p in q {
print("test(\(p))")
if (p < id) {
if p < id {
q.removeFirst()
continue
}
if (p != id) {
if p != id {
return
}
h(p)
print("handle(\(p))")
id += 1

View File

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

View File

@ -40,15 +40,15 @@ private extension OpenVPN.PushReply {
}
class PushTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
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 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.dnsServers, ["209.222.18.222", "209.222.18.218"])
}
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 reply = try! OpenVPN.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2")
XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0")
XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1")
XCTAssertEqual(reply.options.dnsServers, ["8.8.8.8", "4.4.4.4"])
}
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 reply = try! OpenVPN.PushReply(message: msg)!
reply.debug()
let route = reply.options.routes4!.first!
XCTAssertEqual(route.destination, "192.168.0.0")
XCTAssertEqual(route.mask, "255.255.255.0")
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 reply = try! OpenVPN.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2")
XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0")
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.dnsServers, ["2001:4860:4860::8888", "2001:4860:4860::8844"])
}
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 reply = try! OpenVPN.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.options.compressionFraming, .compLZO)
}
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"
var reply: OpenVPN.PushReply
reply = try! OpenVPN.PushReply(message: msg.appending(",comp-lzo no"))!
reply.debug()
XCTAssertEqual(reply.options.compressionFraming, .compLZO)
@ -134,7 +134,7 @@ class PushTests: XCTestCase {
XCTAssertEqual(reply.options.compressionFraming, .compress)
XCTAssertEqual(reply.options.compressionAlgorithm, .other)
}
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 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 reply = try! OpenVPN.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.options.cipher, .aes256gcm)
}
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 reply = try! OpenVPN.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.options.keepAliveInterval, 10)
}
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 reply = try! OpenVPN.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.options.keepAliveTimeout, 60)
}
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 reply = try? OpenVPN.PushReply(message: msg)!
reply?.debug()
}
func testPeerInfo() {
let peerInfo = CoreConfiguration.OpenVPN.peerInfo()
print(peerInfo)

View File

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

View File

@ -41,9 +41,9 @@ import CTunnelKitOpenVPNProtocol
public class TestUtils {
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] {
var suite = [Data]()
for _ in 0..<count {
@ -51,7 +51,7 @@ public class TestUtils {
}
return suite
}
private init() {
}
}
@ -80,7 +80,7 @@ extension Decrypter {
dest.removeSubrange(destLength..<dest.count)
return Data(dest)
}
func verifyData(_ data: Data, flags: UnsafePointer<CryptoFlags>?) throws {
let srcLength = data.count
try data.withUnsafeBytes {

View File

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