]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
VPN: Error out when tunnel activation fails because there's no internet
authorRoopesh Chander <roop@roopc.net>
Mon, 3 Dec 2018 10:04:58 +0000 (15:34 +0530)
committerRoopesh Chander <roop@roopc.net>
Mon, 3 Dec 2018 13:21:42 +0000 (18:51 +0530)
Signed-off-by: Roopesh Chander <roop@roopc.net>
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift
WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift
WireGuard/WireGuard/VPN/InternetReachability.swift [new file with mode: 0644]
WireGuard/WireGuard/VPN/TunnelsManager.swift

index c41bedec89645b7f4dc195435c1577b146df4d7b..d3080759028c447b1bb16a3d69d6fe94bdab97f9 100644 (file)
@@ -44,6 +44,7 @@
                6FE254FF219C60290028284D /* ZipExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FE219C60290028284D /* ZipExporter.swift */; };
                6FF4AC1F211EC472002C96EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC1E211EC472002C96EB /* Assets.xcassets */; };
                6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */; };
+               6FF717E521B2CB1E0045A474 /* InternetReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF717E421B2CB1E0045A474 /* InternetReachability.swift */; };
                6FFA5D8921942F320001E2F7 /* PacketTunnelSettingsGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */; };
                6FFA5D8E2194370D0001E2F7 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E72172020C006A79B3 /* Configuration.swift */; };
                6FFA5D8F2194370D0001E2F7 /* IPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E9217229DB006A79B3 /* IPAddressRange.swift */; };
                6FF4AC2B211EC776002C96EB /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Config.xcconfig; path = Config/Config.xcconfig; sourceTree = "<group>"; };
                6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
                6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuard.entitlements; sourceTree = "<group>"; };
+               6FF717E421B2CB1E0045A474 /* InternetReachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetReachability.swift; sourceTree = "<group>"; };
                6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; };
                6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; };
                6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = "<group>"; };
                        children = (
                                6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
                                6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
+                               6FF717E421B2CB1E0045A474 /* InternetReachability.swift */,
                        );
                        path = VPN;
                        sourceTree = "<group>";
                                6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
                                6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
                                6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
+                               6FF717E521B2CB1E0045A474 /* InternetReachability.swift in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 8aa0c1a3d8289a8fe5726cf7aad46374b7c31859..1116f6145ab54f2c452b6a56e3fb2cf53f2a1b09 100644 (file)
@@ -21,8 +21,12 @@ class ErrorPresenter {
             return ("Unable to remove tunnel", "Internal error")
 
         // TunnelActivationError
-        case TunnelActivationError.tunnelActivationFailed:
+        case TunnelActivationError.tunnelActivationAttemptFailed:
             return ("Activation failure", "The tunnel could not be activated due to an internal error")
+        case TunnelActivationError.tunnelActivationFailedInternalError:
+            return ("Activation failure", "The tunnel could not be activated due to an internal error")
+        case TunnelActivationError.tunnelActivationFailedNoInternetConnection:
+            return ("Activation failure", "No internet connection")
 
         // Importing a zip file
         case ZipArchiveError.cantOpenInputZipFile:
index 6e774338165974d7d54cff3bb7d9e655b5c2f631..c2c7b58e0ce7da9282cf4e1da0d407688f9c51a1 100644 (file)
@@ -80,6 +80,7 @@ class TunnelsListTableViewController: UIViewController {
             busyIndicator.stopAnimating()
 
             tunnelsManager.delegate = s
+            tunnelsManager.activationDelegate = s
             s.tunnelsManager = tunnelsManager
             s.onTunnelsManagerReady?(tunnelsManager)
             s.onTunnelsManagerReady = nil
@@ -312,6 +313,14 @@ extension TunnelsListTableViewController: TunnelsManagerDelegate {
     }
 }
 
+// MARK: TunnelActivationDelegate
+
+extension TunnelsListTableViewController: TunnelActivationDelegate {
+    func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelActivationError) {
+        ErrorPresenter.showErrorAlert(error: error, from: self)
+    }
+}
+
 class TunnelsListTableViewCell: UITableViewCell {
     static let id: String = "TunnelsListTableViewCell"
     var tunnel: TunnelContainer? {
diff --git a/WireGuard/WireGuard/VPN/InternetReachability.swift b/WireGuard/WireGuard/VPN/InternetReachability.swift
new file mode 100644 (file)
index 0000000..7298d3f
--- /dev/null
@@ -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<sockaddr_in>.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
+    }
+}
index 0a3dd8707b3bae447cc7a6b754951afa513291ef..4306fd11ce7c219f92e707423fabe75ff5719a36 100644 (file)
@@ -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) {