From 6528a581de3e33067f9955e6362b6fd97ee17ef6 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Thu, 13 Dec 2018 12:14:21 +0530 Subject: mv WireGuard/WireGuard/VPN/ WireGuard/WireGuard/Tunnel/ Signed-off-by: Roopesh Chander --- WireGuard/WireGuard.xcodeproj/project.pbxproj | 6 +- .../WireGuard/Tunnel/ActivateOnDemandSetting.swift | 75 ++++ .../WireGuard/Tunnel/InternetReachability.swift | 51 +++ WireGuard/WireGuard/Tunnel/TunnelsManager.swift | 480 +++++++++++++++++++++ .../WireGuard/VPN/ActivateOnDemandSetting.swift | 75 ---- WireGuard/WireGuard/VPN/InternetReachability.swift | 51 --- WireGuard/WireGuard/VPN/TunnelsManager.swift | 480 --------------------- 7 files changed, 609 insertions(+), 609 deletions(-) create mode 100644 WireGuard/WireGuard/Tunnel/ActivateOnDemandSetting.swift create mode 100644 WireGuard/WireGuard/Tunnel/InternetReachability.swift create mode 100644 WireGuard/WireGuard/Tunnel/TunnelsManager.swift delete mode 100644 WireGuard/WireGuard/VPN/ActivateOnDemandSetting.swift delete mode 100644 WireGuard/WireGuard/VPN/InternetReachability.swift delete mode 100644 WireGuard/WireGuard/VPN/TunnelsManager.swift diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 4177cbe..93ed1b0 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -252,14 +252,14 @@ path = Model; sourceTree = ""; }; - 6F7774ED21722D0C006A79B3 /* VPN */ = { + 6F7774ED21722D0C006A79B3 /* Tunnel */ = { isa = PBXGroup; children = ( 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */, 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */, 6FF717E421B2CB1E0045A474 /* InternetReachability.swift */, ); - path = VPN; + path = Tunnel; sourceTree = ""; }; 6F919ED3218C65C50023B400 /* Resources */ = { @@ -340,7 +340,7 @@ 6F6899A32180445A0012E523 /* Crypto */, 6F6899AA218099D00012E523 /* ConfigFile */, 6F7774DD217181B1006A79B3 /* UI */, - 6F7774ED21722D0C006A79B3 /* VPN */, + 6F7774ED21722D0C006A79B3 /* Tunnel */, 6FDEF7E72186320E00D8FBF6 /* ZipArchive */, 6F61F1E821B932F700483816 /* WireGuardAppError.swift */, 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */, diff --git a/WireGuard/WireGuard/Tunnel/ActivateOnDemandSetting.swift b/WireGuard/WireGuard/Tunnel/ActivateOnDemandSetting.swift new file mode 100644 index 0000000..0aeda6f --- /dev/null +++ b/WireGuard/WireGuard/Tunnel/ActivateOnDemandSetting.swift @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import NetworkExtension + +struct ActivateOnDemandSetting { + var isActivateOnDemandEnabled: Bool + var activateOnDemandOption: ActivateOnDemandOption +} + +enum ActivateOnDemandOption { + case none // Valid only when isActivateOnDemandEnabled is false + case useOnDemandOverWiFiOrCellular + case useOnDemandOverWiFiOnly + case useOnDemandOverCellularOnly +} + +extension ActivateOnDemandSetting { + func apply(on tunnelProviderManager: NETunnelProviderManager) { + tunnelProviderManager.isOnDemandEnabled = isActivateOnDemandEnabled + let rules: [NEOnDemandRule]? + let connectRule = NEOnDemandRuleConnect() + let disconnectRule = NEOnDemandRuleDisconnect() + switch activateOnDemandOption { + case .none: + rules = nil + case .useOnDemandOverWiFiOrCellular: + rules = [connectRule] + case .useOnDemandOverWiFiOnly: + connectRule.interfaceTypeMatch = .wiFi + disconnectRule.interfaceTypeMatch = .cellular + rules = [connectRule, disconnectRule] + case .useOnDemandOverCellularOnly: + connectRule.interfaceTypeMatch = .cellular + disconnectRule.interfaceTypeMatch = .wiFi + rules = [connectRule, disconnectRule] + } + tunnelProviderManager.onDemandRules = rules + } + + init(from tunnelProviderManager: NETunnelProviderManager) { + let rules = tunnelProviderManager.onDemandRules ?? [] + let activateOnDemandOption: ActivateOnDemandOption + switch rules.count { + case 0: + activateOnDemandOption = .none + case 1: + let rule = rules[0] + precondition(rule.action == .connect) + activateOnDemandOption = .useOnDemandOverWiFiOrCellular + case 2: + let connectRule = rules.first(where: { $0.action == .connect })! + let disconnectRule = rules.first(where: { $0.action == .disconnect })! + if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == .cellular { + activateOnDemandOption = .useOnDemandOverWiFiOnly + } else if connectRule.interfaceTypeMatch == .cellular && disconnectRule.interfaceTypeMatch == .wiFi { + activateOnDemandOption = .useOnDemandOverCellularOnly + } else { + fatalError("Unexpected onDemandRules set on tunnel provider manager") + } + default: + fatalError("Unexpected number of onDemandRules set on tunnel provider manager") + } + self.activateOnDemandOption = activateOnDemandOption + if activateOnDemandOption == .none { + self.isActivateOnDemandEnabled = false + } else { + self.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled + } + } +} + +extension ActivateOnDemandSetting { + static var defaultSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: false, activateOnDemandOption: .none) +} diff --git a/WireGuard/WireGuard/Tunnel/InternetReachability.swift b/WireGuard/WireGuard/Tunnel/InternetReachability.swift new file mode 100644 index 0000000..2e50852 --- /dev/null +++ b/WireGuard/WireGuard/Tunnel/InternetReachability.swift @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import SystemConfiguration + +class InternetReachability { + + enum Status { + case unknown + case notReachable + case reachableOverWiFi + case reachableOverCellular + } + + static func currentStatus() -> Status { + var status: Status = .unknown + if let reachabilityRef = InternetReachability.reachabilityRef() { + var flags = SCNetworkReachabilityFlags(rawValue: 0) + SCNetworkReachabilityGetFlags(reachabilityRef, &flags) + status = Status(reachabilityFlags: flags) + } + return status + } + + private static func reachabilityRef() -> SCNetworkReachability? { + let addrIn = sockaddr_in(sin_len: UInt8(MemoryLayout.size), + sin_family: sa_family_t(AF_INET), + sin_port: 0, + sin_addr: in_addr(s_addr: 0), + sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) + return withUnsafePointer(to: addrIn) { addrInPtr in + addrInPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { addrPtr in + return SCNetworkReachabilityCreateWithAddress(nil, addrPtr) + } + } + } +} + +extension InternetReachability.Status { + init(reachabilityFlags flags: SCNetworkReachabilityFlags) { + var status: InternetReachability.Status = .notReachable + if flags.contains(.reachable) { + if flags.contains(.isWWAN) { + status = .reachableOverCellular + } else { + status = .reachableOverWiFi + } + } + self = status + } +} diff --git a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift new file mode 100644 index 0000000..95ab071 --- /dev/null +++ b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import Foundation +import NetworkExtension +import os.log + +protocol TunnelsManagerListDelegate: class { + func tunnelAdded(at index: Int) + func tunnelModified(at index: Int) + func tunnelMoved(from oldIndex: Int, to newIndex: Int) + func tunnelRemoved(at index: Int) +} + +protocol TunnelsManagerActivationDelegate: class { + func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerError) +} + +enum TunnelsManagerError: WireGuardAppError { + // Tunnels list management + case tunnelNameEmpty + case tunnelAlreadyExistsWithThatName + case vpnSystemErrorOnListingTunnels + case vpnSystemErrorOnAddTunnel + case vpnSystemErrorOnModifyTunnel + case vpnSystemErrorOnRemoveTunnel + + // Tunnel activation + case attemptingActivationWhenTunnelIsNotInactive + case attemptingActivationWhenAnotherTunnelIsOperational(otherTunnelName: String) + case tunnelActivationAttemptFailed // startTunnel() throwed + case tunnelActivationFailedInternalError // startTunnel() succeeded, but activation failed + case tunnelActivationFailedNoInternetConnection // startTunnel() succeeded, but activation failed since no internet + + //swiftlint:disable:next cyclomatic_complexity + func alertText() -> AlertText { + switch self { + case .tunnelNameEmpty: + return ("No name provided", "Can't create tunnel with an empty name") + case .tunnelAlreadyExistsWithThatName: + return ("Name already exists", "A tunnel with that name already exists") + case .vpnSystemErrorOnListingTunnels: + return ("Unable to list tunnels", "Internal error") + case .vpnSystemErrorOnAddTunnel: + return ("Unable to create tunnel", "Internal error") + case .vpnSystemErrorOnModifyTunnel: + return ("Unable to modify tunnel", "Internal error") + case .vpnSystemErrorOnRemoveTunnel: + return ("Unable to remove tunnel", "Internal error") + case .attemptingActivationWhenTunnelIsNotInactive: + return ("Activation failure", "The tunnel is already active or in the process of being activated") + case .attemptingActivationWhenAnotherTunnelIsOperational(let otherTunnelName): + return ("Activation failure", "Please disconnect '\(otherTunnelName)' before enabling this tunnel.") + case .tunnelActivationAttemptFailed: + return ("Activation failure", "The tunnel could not be activated due to an internal error") + case .tunnelActivationFailedInternalError: + return ("Activation failure", "The tunnel could not be activated due to an internal error") + case .tunnelActivationFailedNoInternetConnection: + return ("Activation failure", "No internet connection") + } + } +} + +class TunnelsManager { + + private var tunnels: [TunnelContainer] + weak var tunnelsListDelegate: TunnelsManagerListDelegate? + weak var activationDelegate: TunnelsManagerActivationDelegate? + private var statusObservationToken: AnyObject? + + var tunnelBeingActivated: TunnelContainer? + + init(tunnelProviders: [NETunnelProviderManager]) { + self.tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name } + self.startObservingTunnelStatuses() + } + + static func create(completionHandler: @escaping (WireGuardResult) -> Void) { + #if targetEnvironment(simulator) + // NETunnelProviderManager APIs don't work on the simulator + completionHandler(.success(TunnelsManager(tunnelProviders: []))) + #else + NETunnelProviderManager.loadAllFromPreferences { managers, error in + if let error = error { + os_log("Failed to load tunnel provider managers: %{public}@", log: OSLog.default, type: .debug, "\(error)") + completionHandler(.failure(TunnelsManagerError.vpnSystemErrorOnListingTunnels)) + return + } + completionHandler(.success(TunnelsManager(tunnelProviders: managers ?? []))) + } + #endif + } + + func add(tunnelConfiguration: TunnelConfiguration, + activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting, + completionHandler: @escaping (WireGuardResult) -> Void) { + let tunnelName = tunnelConfiguration.interface.name + if tunnelName.isEmpty { + completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty)) + return + } + + if self.tunnels.contains(where: { $0.name == tunnelName }) { + completionHandler(.failure(TunnelsManagerError.tunnelAlreadyExistsWithThatName)) + return + } + + let tunnelProviderManager = NETunnelProviderManager() + tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration) + tunnelProviderManager.localizedDescription = tunnelName + tunnelProviderManager.isEnabled = true + + activateOnDemandSetting.apply(on: tunnelProviderManager) + + tunnelProviderManager.saveToPreferences { [weak self] error in + guard error == nil else { + os_log("Add: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") + completionHandler(.failure(TunnelsManagerError.vpnSystemErrorOnAddTunnel)) + return + } + if let self = self { + let tunnel = TunnelContainer(tunnel: tunnelProviderManager) + self.tunnels.append(tunnel) + self.tunnels.sort { $0.name < $1.name } + self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!) + completionHandler(.success(tunnel)) + } + } + } + + func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt) -> Void) { + addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, completionHandler: completionHandler) + } + + private func addMultiple(tunnelConfigurations: ArraySlice, numberSuccessful: UInt, completionHandler: @escaping (UInt) -> Void) { + guard let head = tunnelConfigurations.first else { + completionHandler(numberSuccessful) + return + } + let tail = tunnelConfigurations.dropFirst() + self.add(tunnelConfiguration: head) { [weak self, tail] result in + DispatchQueue.main.async { + self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful + (result.isSuccess ? 1 : 0), completionHandler: completionHandler) + } + } + } + + func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration, + activateOnDemandSetting: ActivateOnDemandSetting, completionHandler: @escaping (TunnelsManagerError?) -> Void) { + let tunnelName = tunnelConfiguration.interface.name + if tunnelName.isEmpty { + completionHandler(TunnelsManagerError.tunnelNameEmpty) + return + } + + let tunnelProviderManager = tunnel.tunnelProvider + let isNameChanged = (tunnelName != tunnelProviderManager.localizedDescription) + if isNameChanged { + if self.tunnels.contains(where: { $0.name == tunnelName }) { + completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName) + return + } + tunnel.name = tunnelName + } + tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration) + tunnelProviderManager.localizedDescription = tunnelName + tunnelProviderManager.isEnabled = true + + let isActivatingOnDemand = (!tunnelProviderManager.isOnDemandEnabled && activateOnDemandSetting.isActivateOnDemandEnabled) + activateOnDemandSetting.apply(on: tunnelProviderManager) + + tunnelProviderManager.saveToPreferences { [weak self] error in + guard error == nil else { + os_log("Modify: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") + completionHandler(TunnelsManagerError.vpnSystemErrorOnModifyTunnel) + return + } + if let self = self { + if isNameChanged { + let oldIndex = self.tunnels.firstIndex(of: tunnel)! + self.tunnels.sort { $0.name < $1.name } + let newIndex = self.tunnels.firstIndex(of: tunnel)! + self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex) + } + self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!) + + if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting { + // Turn off the tunnel, and then turn it back on, so the changes are made effective + tunnel.status = .restarting + (tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel() + } + + if isActivatingOnDemand { + // Reload tunnel after saving. + // Without this, the tunnel stopes getting updates on the tunnel status from iOS. + tunnelProviderManager.loadFromPreferences { error in + tunnel.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled + guard error == nil else { + os_log("Modify: Re-loading after saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") + completionHandler(TunnelsManagerError.vpnSystemErrorOnModifyTunnel) + return + } + completionHandler(nil) + } + } else { + completionHandler(nil) + } + } + } + } + + func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) { + let tunnelProviderManager = tunnel.tunnelProvider + + tunnelProviderManager.removeFromPreferences { [weak self] error in + guard error == nil else { + os_log("Remove: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") + completionHandler(TunnelsManagerError.vpnSystemErrorOnRemoveTunnel) + return + } + if let self = self { + let index = self.tunnels.firstIndex(of: tunnel)! + self.tunnels.remove(at: index) + self.tunnelsListDelegate?.tunnelRemoved(at: index) + } + completionHandler(nil) + } + } + + func numberOfTunnels() -> Int { + return tunnels.count + } + + func tunnel(at index: Int) -> TunnelContainer { + return tunnels[index] + } + + func tunnel(named tunnelName: String) -> TunnelContainer? { + return self.tunnels.first { $0.name == tunnelName } + } + + func startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) { + guard tunnels.contains(tunnel) else { return } // Ensure it's not deleted + guard tunnel.status == .inactive else { + completionHandler(TunnelsManagerError.attemptingActivationWhenTunnelIsNotInactive) + return + } + + if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) { + completionHandler(TunnelsManagerError.attemptingActivationWhenAnotherTunnelIsOperational(otherTunnelName: tunnelInOperation.name)) + return + } + + tunnelBeingActivated = tunnel + tunnel.startActivation(completionHandler: completionHandler) + } + + func startDeactivation(of tunnel: TunnelContainer) { + if tunnel.status == .inactive || tunnel.status == .deactivating { + return + } + tunnel.startDeactivation() + } + + func refreshStatuses() { + tunnels.forEach { $0.refreshStatus() } + } + + private func startObservingTunnelStatuses() { + guard statusObservationToken == nil else { return } + + statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in + guard let self = self else { return } + guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return } + guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return } + guard let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return } + + os_log("Tunnel '%{public}@' connection status changed to '%{public}@'", + log: OSLog.default, type: .debug, tunnel.name, "\(tunnel.tunnelProvider.connection.status)") + + // In case our attempt to start the tunnel, didn't succeed + if tunnel == self.tunnelBeingActivated { + if session.status == .disconnected { + if InternetReachability.currentStatus() == .notReachable { + let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection + self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error) + } + self.tunnelBeingActivated = nil + } else if session.status == .connected { + self.tunnelBeingActivated = nil + } + } + + // In case we're restarting the tunnel + if (tunnel.status == .restarting) && (session.status == .disconnected || session.status == .disconnecting) { + // Don't change tunnel.status when disconnecting for a restart + if session.status == .disconnected { + self.tunnelBeingActivated = tunnel + tunnel.startActivation { _ in } + } + return + } + + tunnel.refreshStatus() + } + } + + deinit { + if let statusObservationToken = self.statusObservationToken { + NotificationCenter.default.removeObserver(statusObservationToken) + } + } +} + +class TunnelContainer: NSObject { + @objc dynamic var name: String + @objc dynamic var status: TunnelStatus + + @objc dynamic var isActivateOnDemandEnabled: Bool + + var isAttemptingActivation: Bool = false + var onActivationCommitted: ((Bool) -> Void)? + var onDeactivationComplete: (() -> Void)? + + fileprivate let tunnelProvider: NETunnelProviderManager + private var lastTunnelConnectionStatus: NEVPNStatus? + + init(tunnel: NETunnelProviderManager) { + self.name = tunnel.localizedDescription ?? "Unnamed" + let status = TunnelStatus(from: tunnel.connection.status) + self.status = status + self.isActivateOnDemandEnabled = tunnel.isOnDemandEnabled + self.tunnelProvider = tunnel + super.init() + } + + func tunnelConfiguration() -> TunnelConfiguration? { + return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.tunnelConfiguration() + } + + func activateOnDemandSetting() -> ActivateOnDemandSetting { + return ActivateOnDemandSetting(from: tunnelProvider) + } + + func refreshStatus() { + let status = TunnelStatus(from: self.tunnelProvider.connection.status) + self.status = status + self.isActivateOnDemandEnabled = self.tunnelProvider.isOnDemandEnabled + } + + fileprivate func startActivation(completionHandler: @escaping (TunnelsManagerError?) -> Void) { + assert(status == .inactive || status == .restarting) + + guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() } + + startActivation(tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler) + } + + fileprivate func startActivation(recursionCount: UInt = 0, + lastError: Error? = nil, + tunnelConfiguration: TunnelConfiguration, + completionHandler: @escaping (TunnelsManagerError?) -> Void) { + if recursionCount >= 8 { + os_log("startActivation: Failed after 8 attempts. Giving up with %{public}@", log: OSLog.default, type: .error, "\(lastError!)") + completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) + return + } + + os_log("startActivation: Entering (tunnel: %{public}@)", log: OSLog.default, type: .debug, self.name) + + guard tunnelProvider.isEnabled else { + // In case the tunnel had gotten disabled, re-enable and save it, + // then call this function again. + os_log("startActivation: Tunnel is disabled. Re-enabling and saving", log: OSLog.default, type: .info) + tunnelProvider.isEnabled = true + tunnelProvider.saveToPreferences { [weak self] error in + if error != nil { + os_log("Error saving tunnel after re-enabling: %{public}@", log: OSLog.default, type: .error, "\(error!)") + completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) + return + } + os_log("startActivation: Tunnel saved after re-enabling", log: OSLog.default, type: .info) + os_log("startActivation: Invoking startActivation", log: OSLog.default, type: .debug) + self?.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown), + tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler) + } + return + } + + // Start the tunnel + do { + os_log("startActivation: Starting tunnel", log: OSLog.default, type: .debug) + try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel() + os_log("startActivation: Success", log: OSLog.default, type: .debug) + completionHandler(nil) + } catch let error { + guard let vpnError = error as? NEVPNError else { + os_log("Failed to activate tunnel: Error: %{public}@", log: OSLog.default, type: .debug, "\(error)") + status = .inactive + completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) + return + } + guard vpnError.code == NEVPNError.configurationInvalid || vpnError.code == NEVPNError.configurationStale else { + os_log("Failed to activate tunnel: VPN Error: %{public}@", log: OSLog.default, type: .debug, "\(error)") + status = .inactive + completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) + return + } + os_log("startActivation: Will reload tunnel and then try to start it. ", log: OSLog.default, type: .info) + tunnelProvider.loadFromPreferences { [weak self] error in + if error != nil { + os_log("startActivation: Error reloading tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error!)") + self?.status = .inactive + completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) + return + } + os_log("startActivation: Tunnel reloaded", log: OSLog.default, type: .info) + os_log("startActivation: Invoking startActivation", log: OSLog.default, type: .debug) + self?.startActivation(recursionCount: recursionCount + 1, lastError: vpnError, tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler) + } + } + } + + fileprivate func startDeactivation() { + (tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel() + } +} + +@objc enum TunnelStatus: Int { + case inactive + case activating + case active + case deactivating + case reasserting // Not a possible state at present + + case restarting // Restarting tunnel (done after saving modifications to an active tunnel) + + init(from vpnStatus: NEVPNStatus) { + switch vpnStatus { + case .connected: + self = .active + case .connecting: + self = .activating + case .disconnected: + self = .inactive + case .disconnecting: + self = .deactivating + case .reasserting: + self = .reasserting + case .invalid: + self = .inactive + } + } +} + +extension TunnelStatus: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .inactive: return "inactive" + case .activating: return "activating" + case .active: return "active" + case .deactivating: return "deactivating" + case .reasserting: return "reasserting" + case .restarting: return "restarting" + } + } +} + +extension NEVPNStatus: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .connected: return "connected" + case .connecting: return "connecting" + case .disconnected: return "disconnected" + case .disconnecting: return "disconnecting" + case .reasserting: return "reasserting" + case .invalid: return "invalid" + } + } +} diff --git a/WireGuard/WireGuard/VPN/ActivateOnDemandSetting.swift b/WireGuard/WireGuard/VPN/ActivateOnDemandSetting.swift deleted file mode 100644 index 0aeda6f..0000000 --- a/WireGuard/WireGuard/VPN/ActivateOnDemandSetting.swift +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018 WireGuard LLC. All Rights Reserved. - -import NetworkExtension - -struct ActivateOnDemandSetting { - var isActivateOnDemandEnabled: Bool - var activateOnDemandOption: ActivateOnDemandOption -} - -enum ActivateOnDemandOption { - case none // Valid only when isActivateOnDemandEnabled is false - case useOnDemandOverWiFiOrCellular - case useOnDemandOverWiFiOnly - case useOnDemandOverCellularOnly -} - -extension ActivateOnDemandSetting { - func apply(on tunnelProviderManager: NETunnelProviderManager) { - tunnelProviderManager.isOnDemandEnabled = isActivateOnDemandEnabled - let rules: [NEOnDemandRule]? - let connectRule = NEOnDemandRuleConnect() - let disconnectRule = NEOnDemandRuleDisconnect() - switch activateOnDemandOption { - case .none: - rules = nil - case .useOnDemandOverWiFiOrCellular: - rules = [connectRule] - case .useOnDemandOverWiFiOnly: - connectRule.interfaceTypeMatch = .wiFi - disconnectRule.interfaceTypeMatch = .cellular - rules = [connectRule, disconnectRule] - case .useOnDemandOverCellularOnly: - connectRule.interfaceTypeMatch = .cellular - disconnectRule.interfaceTypeMatch = .wiFi - rules = [connectRule, disconnectRule] - } - tunnelProviderManager.onDemandRules = rules - } - - init(from tunnelProviderManager: NETunnelProviderManager) { - let rules = tunnelProviderManager.onDemandRules ?? [] - let activateOnDemandOption: ActivateOnDemandOption - switch rules.count { - case 0: - activateOnDemandOption = .none - case 1: - let rule = rules[0] - precondition(rule.action == .connect) - activateOnDemandOption = .useOnDemandOverWiFiOrCellular - case 2: - let connectRule = rules.first(where: { $0.action == .connect })! - let disconnectRule = rules.first(where: { $0.action == .disconnect })! - if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == .cellular { - activateOnDemandOption = .useOnDemandOverWiFiOnly - } else if connectRule.interfaceTypeMatch == .cellular && disconnectRule.interfaceTypeMatch == .wiFi { - activateOnDemandOption = .useOnDemandOverCellularOnly - } else { - fatalError("Unexpected onDemandRules set on tunnel provider manager") - } - default: - fatalError("Unexpected number of onDemandRules set on tunnel provider manager") - } - self.activateOnDemandOption = activateOnDemandOption - if activateOnDemandOption == .none { - self.isActivateOnDemandEnabled = false - } else { - self.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled - } - } -} - -extension ActivateOnDemandSetting { - static var defaultSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: false, activateOnDemandOption: .none) -} diff --git a/WireGuard/WireGuard/VPN/InternetReachability.swift b/WireGuard/WireGuard/VPN/InternetReachability.swift deleted file mode 100644 index 2e50852..0000000 --- a/WireGuard/WireGuard/VPN/InternetReachability.swift +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018 WireGuard LLC. All Rights Reserved. - -import SystemConfiguration - -class InternetReachability { - - enum Status { - case unknown - case notReachable - case reachableOverWiFi - case reachableOverCellular - } - - static func currentStatus() -> Status { - var status: Status = .unknown - if let reachabilityRef = InternetReachability.reachabilityRef() { - var flags = SCNetworkReachabilityFlags(rawValue: 0) - SCNetworkReachabilityGetFlags(reachabilityRef, &flags) - status = Status(reachabilityFlags: flags) - } - return status - } - - private static func reachabilityRef() -> SCNetworkReachability? { - let addrIn = sockaddr_in(sin_len: UInt8(MemoryLayout.size), - sin_family: sa_family_t(AF_INET), - sin_port: 0, - sin_addr: in_addr(s_addr: 0), - sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) - return withUnsafePointer(to: addrIn) { addrInPtr in - addrInPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { addrPtr in - return SCNetworkReachabilityCreateWithAddress(nil, addrPtr) - } - } - } -} - -extension InternetReachability.Status { - init(reachabilityFlags flags: SCNetworkReachabilityFlags) { - var status: InternetReachability.Status = .notReachable - if flags.contains(.reachable) { - if flags.contains(.isWWAN) { - status = .reachableOverCellular - } else { - status = .reachableOverWiFi - } - } - self = status - } -} diff --git a/WireGuard/WireGuard/VPN/TunnelsManager.swift b/WireGuard/WireGuard/VPN/TunnelsManager.swift deleted file mode 100644 index 95ab071..0000000 --- a/WireGuard/WireGuard/VPN/TunnelsManager.swift +++ /dev/null @@ -1,480 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018 WireGuard LLC. All Rights Reserved. - -import Foundation -import NetworkExtension -import os.log - -protocol TunnelsManagerListDelegate: class { - func tunnelAdded(at index: Int) - func tunnelModified(at index: Int) - func tunnelMoved(from oldIndex: Int, to newIndex: Int) - func tunnelRemoved(at index: Int) -} - -protocol TunnelsManagerActivationDelegate: class { - func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerError) -} - -enum TunnelsManagerError: WireGuardAppError { - // Tunnels list management - case tunnelNameEmpty - case tunnelAlreadyExistsWithThatName - case vpnSystemErrorOnListingTunnels - case vpnSystemErrorOnAddTunnel - case vpnSystemErrorOnModifyTunnel - case vpnSystemErrorOnRemoveTunnel - - // Tunnel activation - case attemptingActivationWhenTunnelIsNotInactive - case attemptingActivationWhenAnotherTunnelIsOperational(otherTunnelName: String) - case tunnelActivationAttemptFailed // startTunnel() throwed - case tunnelActivationFailedInternalError // startTunnel() succeeded, but activation failed - case tunnelActivationFailedNoInternetConnection // startTunnel() succeeded, but activation failed since no internet - - //swiftlint:disable:next cyclomatic_complexity - func alertText() -> AlertText { - switch self { - case .tunnelNameEmpty: - return ("No name provided", "Can't create tunnel with an empty name") - case .tunnelAlreadyExistsWithThatName: - return ("Name already exists", "A tunnel with that name already exists") - case .vpnSystemErrorOnListingTunnels: - return ("Unable to list tunnels", "Internal error") - case .vpnSystemErrorOnAddTunnel: - return ("Unable to create tunnel", "Internal error") - case .vpnSystemErrorOnModifyTunnel: - return ("Unable to modify tunnel", "Internal error") - case .vpnSystemErrorOnRemoveTunnel: - return ("Unable to remove tunnel", "Internal error") - case .attemptingActivationWhenTunnelIsNotInactive: - return ("Activation failure", "The tunnel is already active or in the process of being activated") - case .attemptingActivationWhenAnotherTunnelIsOperational(let otherTunnelName): - return ("Activation failure", "Please disconnect '\(otherTunnelName)' before enabling this tunnel.") - case .tunnelActivationAttemptFailed: - return ("Activation failure", "The tunnel could not be activated due to an internal error") - case .tunnelActivationFailedInternalError: - return ("Activation failure", "The tunnel could not be activated due to an internal error") - case .tunnelActivationFailedNoInternetConnection: - return ("Activation failure", "No internet connection") - } - } -} - -class TunnelsManager { - - private var tunnels: [TunnelContainer] - weak var tunnelsListDelegate: TunnelsManagerListDelegate? - weak var activationDelegate: TunnelsManagerActivationDelegate? - private var statusObservationToken: AnyObject? - - var tunnelBeingActivated: TunnelContainer? - - init(tunnelProviders: [NETunnelProviderManager]) { - self.tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name } - self.startObservingTunnelStatuses() - } - - static func create(completionHandler: @escaping (WireGuardResult) -> Void) { - #if targetEnvironment(simulator) - // NETunnelProviderManager APIs don't work on the simulator - completionHandler(.success(TunnelsManager(tunnelProviders: []))) - #else - NETunnelProviderManager.loadAllFromPreferences { managers, error in - if let error = error { - os_log("Failed to load tunnel provider managers: %{public}@", log: OSLog.default, type: .debug, "\(error)") - completionHandler(.failure(TunnelsManagerError.vpnSystemErrorOnListingTunnels)) - return - } - completionHandler(.success(TunnelsManager(tunnelProviders: managers ?? []))) - } - #endif - } - - func add(tunnelConfiguration: TunnelConfiguration, - activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting, - completionHandler: @escaping (WireGuardResult) -> Void) { - let tunnelName = tunnelConfiguration.interface.name - if tunnelName.isEmpty { - completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty)) - return - } - - if self.tunnels.contains(where: { $0.name == tunnelName }) { - completionHandler(.failure(TunnelsManagerError.tunnelAlreadyExistsWithThatName)) - return - } - - let tunnelProviderManager = NETunnelProviderManager() - tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration) - tunnelProviderManager.localizedDescription = tunnelName - tunnelProviderManager.isEnabled = true - - activateOnDemandSetting.apply(on: tunnelProviderManager) - - tunnelProviderManager.saveToPreferences { [weak self] error in - guard error == nil else { - os_log("Add: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") - completionHandler(.failure(TunnelsManagerError.vpnSystemErrorOnAddTunnel)) - return - } - if let self = self { - let tunnel = TunnelContainer(tunnel: tunnelProviderManager) - self.tunnels.append(tunnel) - self.tunnels.sort { $0.name < $1.name } - self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!) - completionHandler(.success(tunnel)) - } - } - } - - func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt) -> Void) { - addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, completionHandler: completionHandler) - } - - private func addMultiple(tunnelConfigurations: ArraySlice, numberSuccessful: UInt, completionHandler: @escaping (UInt) -> Void) { - guard let head = tunnelConfigurations.first else { - completionHandler(numberSuccessful) - return - } - let tail = tunnelConfigurations.dropFirst() - self.add(tunnelConfiguration: head) { [weak self, tail] result in - DispatchQueue.main.async { - self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful + (result.isSuccess ? 1 : 0), completionHandler: completionHandler) - } - } - } - - func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration, - activateOnDemandSetting: ActivateOnDemandSetting, completionHandler: @escaping (TunnelsManagerError?) -> Void) { - let tunnelName = tunnelConfiguration.interface.name - if tunnelName.isEmpty { - completionHandler(TunnelsManagerError.tunnelNameEmpty) - return - } - - let tunnelProviderManager = tunnel.tunnelProvider - let isNameChanged = (tunnelName != tunnelProviderManager.localizedDescription) - if isNameChanged { - if self.tunnels.contains(where: { $0.name == tunnelName }) { - completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName) - return - } - tunnel.name = tunnelName - } - tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration) - tunnelProviderManager.localizedDescription = tunnelName - tunnelProviderManager.isEnabled = true - - let isActivatingOnDemand = (!tunnelProviderManager.isOnDemandEnabled && activateOnDemandSetting.isActivateOnDemandEnabled) - activateOnDemandSetting.apply(on: tunnelProviderManager) - - tunnelProviderManager.saveToPreferences { [weak self] error in - guard error == nil else { - os_log("Modify: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") - completionHandler(TunnelsManagerError.vpnSystemErrorOnModifyTunnel) - return - } - if let self = self { - if isNameChanged { - let oldIndex = self.tunnels.firstIndex(of: tunnel)! - self.tunnels.sort { $0.name < $1.name } - let newIndex = self.tunnels.firstIndex(of: tunnel)! - self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex) - } - self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!) - - if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting { - // Turn off the tunnel, and then turn it back on, so the changes are made effective - tunnel.status = .restarting - (tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel() - } - - if isActivatingOnDemand { - // Reload tunnel after saving. - // Without this, the tunnel stopes getting updates on the tunnel status from iOS. - tunnelProviderManager.loadFromPreferences { error in - tunnel.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled - guard error == nil else { - os_log("Modify: Re-loading after saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") - completionHandler(TunnelsManagerError.vpnSystemErrorOnModifyTunnel) - return - } - completionHandler(nil) - } - } else { - completionHandler(nil) - } - } - } - } - - func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) { - let tunnelProviderManager = tunnel.tunnelProvider - - tunnelProviderManager.removeFromPreferences { [weak self] error in - guard error == nil else { - os_log("Remove: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)") - completionHandler(TunnelsManagerError.vpnSystemErrorOnRemoveTunnel) - return - } - if let self = self { - let index = self.tunnels.firstIndex(of: tunnel)! - self.tunnels.remove(at: index) - self.tunnelsListDelegate?.tunnelRemoved(at: index) - } - completionHandler(nil) - } - } - - func numberOfTunnels() -> Int { - return tunnels.count - } - - func tunnel(at index: Int) -> TunnelContainer { - return tunnels[index] - } - - func tunnel(named tunnelName: String) -> TunnelContainer? { - return self.tunnels.first { $0.name == tunnelName } - } - - func startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) { - guard tunnels.contains(tunnel) else { return } // Ensure it's not deleted - guard tunnel.status == .inactive else { - completionHandler(TunnelsManagerError.attemptingActivationWhenTunnelIsNotInactive) - return - } - - if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) { - completionHandler(TunnelsManagerError.attemptingActivationWhenAnotherTunnelIsOperational(otherTunnelName: tunnelInOperation.name)) - return - } - - tunnelBeingActivated = tunnel - tunnel.startActivation(completionHandler: completionHandler) - } - - func startDeactivation(of tunnel: TunnelContainer) { - if tunnel.status == .inactive || tunnel.status == .deactivating { - return - } - tunnel.startDeactivation() - } - - func refreshStatuses() { - tunnels.forEach { $0.refreshStatus() } - } - - private func startObservingTunnelStatuses() { - guard statusObservationToken == nil else { return } - - statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in - guard let self = self else { return } - guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return } - guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return } - guard let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return } - - os_log("Tunnel '%{public}@' connection status changed to '%{public}@'", - log: OSLog.default, type: .debug, tunnel.name, "\(tunnel.tunnelProvider.connection.status)") - - // In case our attempt to start the tunnel, didn't succeed - if tunnel == self.tunnelBeingActivated { - if session.status == .disconnected { - if InternetReachability.currentStatus() == .notReachable { - let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection - self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error) - } - self.tunnelBeingActivated = nil - } else if session.status == .connected { - self.tunnelBeingActivated = nil - } - } - - // In case we're restarting the tunnel - if (tunnel.status == .restarting) && (session.status == .disconnected || session.status == .disconnecting) { - // Don't change tunnel.status when disconnecting for a restart - if session.status == .disconnected { - self.tunnelBeingActivated = tunnel - tunnel.startActivation { _ in } - } - return - } - - tunnel.refreshStatus() - } - } - - deinit { - if let statusObservationToken = self.statusObservationToken { - NotificationCenter.default.removeObserver(statusObservationToken) - } - } -} - -class TunnelContainer: NSObject { - @objc dynamic var name: String - @objc dynamic var status: TunnelStatus - - @objc dynamic var isActivateOnDemandEnabled: Bool - - var isAttemptingActivation: Bool = false - var onActivationCommitted: ((Bool) -> Void)? - var onDeactivationComplete: (() -> Void)? - - fileprivate let tunnelProvider: NETunnelProviderManager - private var lastTunnelConnectionStatus: NEVPNStatus? - - init(tunnel: NETunnelProviderManager) { - self.name = tunnel.localizedDescription ?? "Unnamed" - let status = TunnelStatus(from: tunnel.connection.status) - self.status = status - self.isActivateOnDemandEnabled = tunnel.isOnDemandEnabled - self.tunnelProvider = tunnel - super.init() - } - - func tunnelConfiguration() -> TunnelConfiguration? { - return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.tunnelConfiguration() - } - - func activateOnDemandSetting() -> ActivateOnDemandSetting { - return ActivateOnDemandSetting(from: tunnelProvider) - } - - func refreshStatus() { - let status = TunnelStatus(from: self.tunnelProvider.connection.status) - self.status = status - self.isActivateOnDemandEnabled = self.tunnelProvider.isOnDemandEnabled - } - - fileprivate func startActivation(completionHandler: @escaping (TunnelsManagerError?) -> Void) { - assert(status == .inactive || status == .restarting) - - guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() } - - startActivation(tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler) - } - - fileprivate func startActivation(recursionCount: UInt = 0, - lastError: Error? = nil, - tunnelConfiguration: TunnelConfiguration, - completionHandler: @escaping (TunnelsManagerError?) -> Void) { - if recursionCount >= 8 { - os_log("startActivation: Failed after 8 attempts. Giving up with %{public}@", log: OSLog.default, type: .error, "\(lastError!)") - completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) - return - } - - os_log("startActivation: Entering (tunnel: %{public}@)", log: OSLog.default, type: .debug, self.name) - - guard tunnelProvider.isEnabled else { - // In case the tunnel had gotten disabled, re-enable and save it, - // then call this function again. - os_log("startActivation: Tunnel is disabled. Re-enabling and saving", log: OSLog.default, type: .info) - tunnelProvider.isEnabled = true - tunnelProvider.saveToPreferences { [weak self] error in - if error != nil { - os_log("Error saving tunnel after re-enabling: %{public}@", log: OSLog.default, type: .error, "\(error!)") - completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) - return - } - os_log("startActivation: Tunnel saved after re-enabling", log: OSLog.default, type: .info) - os_log("startActivation: Invoking startActivation", log: OSLog.default, type: .debug) - self?.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown), - tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler) - } - return - } - - // Start the tunnel - do { - os_log("startActivation: Starting tunnel", log: OSLog.default, type: .debug) - try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel() - os_log("startActivation: Success", log: OSLog.default, type: .debug) - completionHandler(nil) - } catch let error { - guard let vpnError = error as? NEVPNError else { - os_log("Failed to activate tunnel: Error: %{public}@", log: OSLog.default, type: .debug, "\(error)") - status = .inactive - completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) - return - } - guard vpnError.code == NEVPNError.configurationInvalid || vpnError.code == NEVPNError.configurationStale else { - os_log("Failed to activate tunnel: VPN Error: %{public}@", log: OSLog.default, type: .debug, "\(error)") - status = .inactive - completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) - return - } - os_log("startActivation: Will reload tunnel and then try to start it. ", log: OSLog.default, type: .info) - tunnelProvider.loadFromPreferences { [weak self] error in - if error != nil { - os_log("startActivation: Error reloading tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error!)") - self?.status = .inactive - completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed) - return - } - os_log("startActivation: Tunnel reloaded", log: OSLog.default, type: .info) - os_log("startActivation: Invoking startActivation", log: OSLog.default, type: .debug) - self?.startActivation(recursionCount: recursionCount + 1, lastError: vpnError, tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler) - } - } - } - - fileprivate func startDeactivation() { - (tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel() - } -} - -@objc enum TunnelStatus: Int { - case inactive - case activating - case active - case deactivating - case reasserting // Not a possible state at present - - case restarting // Restarting tunnel (done after saving modifications to an active tunnel) - - init(from vpnStatus: NEVPNStatus) { - switch vpnStatus { - case .connected: - self = .active - case .connecting: - self = .activating - case .disconnected: - self = .inactive - case .disconnecting: - self = .deactivating - case .reasserting: - self = .reasserting - case .invalid: - self = .inactive - } - } -} - -extension TunnelStatus: CustomDebugStringConvertible { - public var debugDescription: String { - switch self { - case .inactive: return "inactive" - case .activating: return "activating" - case .active: return "active" - case .deactivating: return "deactivating" - case .reasserting: return "reasserting" - case .restarting: return "restarting" - } - } -} - -extension NEVPNStatus: CustomDebugStringConvertible { - public var debugDescription: String { - switch self { - case .connected: return "connected" - case .connecting: return "connecting" - case .disconnected: return "disconnected" - case .disconnecting: return "disconnecting" - case .reasserting: return "reasserting" - case .invalid: return "invalid" - } - } -} -- cgit v1.2.3-59-g8ed1b