From e1b258353c312a87129d771405148e83f0af5a04 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Mon, 3 Dec 2018 15:34:58 +0530 Subject: VPN: Error out when tunnel activation fails because there's no internet Signed-off-by: Roopesh Chander --- WireGuard/WireGuard/VPN/InternetReachability.swift | 51 ++++++++++++++++++++++ WireGuard/WireGuard/VPN/TunnelsManager.swift | 46 ++++++++++++++++--- 2 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 WireGuard/WireGuard/VPN/InternetReachability.swift (limited to 'WireGuard/WireGuard/VPN') diff --git a/WireGuard/WireGuard/VPN/InternetReachability.swift b/WireGuard/WireGuard/VPN/InternetReachability.swift new file mode 100644 index 0000000..7298d3f --- /dev/null +++ b/WireGuard/WireGuard/VPN/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) -> SCNetworkReachability? in + addrInPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { (addrPtr) -> SCNetworkReachability? 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 index 0a3dd87..4306fd1 100644 --- a/WireGuard/WireGuard/VPN/TunnelsManager.swift +++ b/WireGuard/WireGuard/VPN/TunnelsManager.swift @@ -12,8 +12,14 @@ protocol TunnelsManagerDelegate: class { func tunnelRemoved(at: Int) } +protocol TunnelActivationDelegate: class { + func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelActivationError) +} + enum TunnelActivationError: Error { - case tunnelActivationFailed + case tunnelActivationAttemptFailed // startTunnel() throwed + case tunnelActivationFailedInternalError // startTunnel() succeeded, but activation failed + case tunnelActivationFailedNoInternetConnection // startTunnel() succeeded, but activation failed since no internet case attemptingActivationWhenTunnelIsNotInactive case attemptingDeactivationWhenTunnelIsInactive } @@ -30,6 +36,7 @@ class TunnelsManager { private var tunnels: [TunnelContainer] weak var delegate: TunnelsManagerDelegate? + weak var activationDelegate: TunnelActivationDelegate? private var isAddingTunnel: Bool = false private var isModifyingTunnel: Bool = false @@ -212,14 +219,27 @@ class TunnelsManager { completionHandler(TunnelActivationError.attemptingActivationWhenTunnelIsNotInactive) return } + + func _startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (Error?) -> Void) { + tunnel.onActivationCommitted = { [weak self] (success) in + if (!success) { + let error = (InternetReachability.currentStatus() == .notReachable ? + TunnelActivationError.tunnelActivationFailedNoInternetConnection : + TunnelActivationError.tunnelActivationFailedInternalError) + self?.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error) + } + } + tunnel.startActivation(completionHandler: completionHandler) + } + if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) { tunnel.status = .waiting tunnelInOperation.onDeactivationComplete = { - tunnel.startActivation(completionHandler: completionHandler) + _startActivation(of: tunnel, completionHandler: completionHandler) } startDeactivation(of: tunnelInOperation) } else { - tunnel.startActivation(completionHandler: completionHandler) + _startActivation(of: tunnel, completionHandler: completionHandler) } } @@ -249,6 +269,8 @@ class TunnelContainer: NSObject { } } + var isAttemptingActivation: Bool = false + var onActivationCommitted: ((Bool) -> Void)? var onDeactivationComplete: (() -> Void)? fileprivate let tunnelProvider: NETunnelProviderManager @@ -289,8 +311,8 @@ class TunnelContainer: NSObject { guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() } onDeactivationComplete = nil - startActivation(tunnelConfiguration: tunnelConfiguration, - completionHandler: completionHandler) + isAttemptingActivation = true + startActivation(tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler) } fileprivate func startActivation(recursionCount: UInt = 0, @@ -299,7 +321,7 @@ class TunnelContainer: NSObject { completionHandler: @escaping (Error?) -> Void) { if (recursionCount >= 8) { os_log("startActivation: Failed after 8 attempts. Giving up with %{public}@", log: OSLog.default, type: .error, "\(lastError!)") - completionHandler(TunnelActivationError.tunnelActivationFailed) + completionHandler(TunnelActivationError.tunnelActivationAttemptFailed) return } @@ -386,6 +408,18 @@ class TunnelContainer: NSObject { object: connection, queue: nil) { [weak self] (_) in guard let s = self else { return } + if (s.isAttemptingActivation) { + if (connection.status == .connecting || connection.status == .connected) { + // We tried to start the tunnel, and that attempt is on track to become succeessful + s.onActivationCommitted?(true) + s.onActivationCommitted = nil + } else if (connection.status == .disconnecting || connection.status == .disconnected) { + // We tried to start the tunnel, but that attempt didn't succeed + s.onActivationCommitted?(false) + s.onActivationCommitted = nil + } + s.isAttemptingActivation = false + } if ((s.status == .restarting) && (connection.status == .disconnected || connection.status == .disconnecting)) { // Don't change s.status when disconnecting for a restart if (connection.status == .disconnected) { -- cgit v1.2.3-59-g8ed1b