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/VPN/TunnelsManager.swift | 480 --------------------------- 1 file changed, 480 deletions(-) delete mode 100644 WireGuard/WireGuard/VPN/TunnelsManager.swift (limited to 'WireGuard/WireGuard/VPN/TunnelsManager.swift') 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