]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
TunnelsManager: Report activation errors through the activationDelegate
authorRoopesh Chander <roop@roopc.net>
Thu, 13 Dec 2018 13:25:20 +0000 (18:55 +0530)
committerRoopesh Chander <roop@roopc.net>
Thu, 13 Dec 2018 13:26:07 +0000 (18:56 +0530)
Don't report activation errors through completion handlers

Signed-off-by: Roopesh Chander <roop@roopc.net>
WireGuard/WireGuard/Tunnel/TunnelsManager.swift
WireGuard/WireGuard/UI/iOS/MainViewController.swift
WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift
WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift

index 1eb84fd2979db13281374ca6a337ccd76dd55dcc..1e4823f39c319abc499f22be97ce96b9de72495a 100644 (file)
@@ -13,7 +13,37 @@ protocol TunnelsManagerListDelegate: class {
 }
 
 protocol TunnelsManagerActivationDelegate: class {
-    func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerError)
+    func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) // startTunnel wasn't called or failed
+    func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) // startTunnel succeeded
+    func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) // status didn't change to connected
+    func tunnelActivationSucceeded(tunnel: TunnelContainer) // status changed to connected
+}
+
+enum TunnelsManagerActivationAttemptError: WireGuardAppError {
+    case tunnelIsNotInactive
+    case anotherTunnelIsOperational(otherTunnelName: String)
+    case failedWhileStarting // startTunnel() throwed
+    case failedWhileSaving // save config after re-enabling throwed
+    case failedWhileLoading // reloading config throwed
+    case failedBecauseOfTooManyErrors // recursion limit reached
+
+    func alertText() -> AlertText {
+        switch self {
+        case .tunnelIsNotInactive:
+            return ("Activation failure", "The tunnel is already active or in the process of being activated")
+        case .anotherTunnelIsOperational(let otherTunnelName):
+            return ("Activation failure", "Please disconnect '\(otherTunnelName)' before enabling this tunnel.")
+        case .failedWhileStarting, .failedWhileSaving, .failedWhileLoading, .failedBecauseOfTooManyErrors:
+            return ("Activation failure", "The tunnel could not be activated due to an internal error")
+        }
+    }
+}
+
+enum TunnelsManagerActivationError: WireGuardAppError {
+    case activationFailed
+    func alertText() -> AlertText {
+        return ("Activation failure", "The tunnel could not be activated")
+    }
 }
 
 enum TunnelsManagerError: WireGuardAppError {
@@ -25,14 +55,6 @@ enum TunnelsManagerError: WireGuardAppError {
     case systemErrorOnModifyTunnel
     case systemErrorOnRemoveTunnel
 
-    // 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:
@@ -47,16 +69,6 @@ enum TunnelsManagerError: WireGuardAppError {
             return ("Unable to modify tunnel", "Internal error")
         case .systemErrorOnRemoveTunnel:
             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")
         }
     }
 }
@@ -239,20 +251,21 @@ class TunnelsManager {
         return self.tunnels.first { $0.name == tunnelName }
     }
 
-    func startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
+    func startActivation(of tunnel: TunnelContainer) {
         guard tunnels.contains(tunnel) else { return } // Ensure it's not deleted
         guard tunnel.status == .inactive else {
-            completionHandler(TunnelsManagerError.attemptingActivationWhenTunnelIsNotInactive)
+            self.activationDelegate?.tunnelActivationAttemptFailed(tunnel: tunnel, error: .tunnelIsNotInactive)
             return
         }
 
         if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) {
-            completionHandler(TunnelsManagerError.attemptingActivationWhenAnotherTunnelIsOperational(otherTunnelName: tunnelInOperation.name))
+            self.activationDelegate?.tunnelActivationAttemptFailed(tunnel: tunnel, error: .anotherTunnelIsOperational(otherTunnelName: tunnelInOperation.name))
+            // FIXME: Switches in the UI won't be reset, but we'll be reintroducing waiting, so that's fine
             return
         }
 
         tunnelBeingActivated = tunnel
-        tunnel.startActivation(completionHandler: completionHandler)
+        tunnel.startActivation(activationDelegate: self.activationDelegate)
     }
 
     func startDeactivation(of tunnel: TunnelContainer) {
@@ -277,16 +290,14 @@ class TunnelsManager {
 
             wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(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
+            // Track what happened to our attempt to start the tunnel
+            if tunnel.isAttemptingActivation {
+                if session.status == .connected {
+                    tunnel.isAttemptingActivation = false
+                    self.activationDelegate?.tunnelActivationSucceeded(tunnel: tunnel)
+                } else if session.status == .disconnected {
+                    tunnel.isAttemptingActivation = false
+                    self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailed)
                 }
             }
 
@@ -295,7 +306,7 @@ class TunnelsManager {
                 // Don't change tunnel.status when disconnecting for a restart
                 if session.status == .disconnected {
                     self.tunnelBeingActivated = tunnel
-                    tunnel.startActivation { _ in }
+                    tunnel.startActivation(activationDelegate: self.activationDelegate)
                 }
                 return
             }
@@ -318,8 +329,6 @@ class TunnelContainer: NSObject {
     @objc dynamic var isActivateOnDemandEnabled: Bool
 
     var isAttemptingActivation: Bool = false
-    var onActivationCommitted: ((Bool) -> Void)?
-    var onDeactivationComplete: (() -> Void)?
 
     fileprivate let tunnelProvider: NETunnelProviderManager
     private var lastTunnelConnectionStatus: NEVPNStatus?
@@ -347,21 +356,21 @@ class TunnelContainer: NSObject {
         self.isActivateOnDemandEnabled = self.tunnelProvider.isOnDemandEnabled
     }
 
-    fileprivate func startActivation(completionHandler: @escaping (TunnelsManagerError?) -> Void) {
+    fileprivate func startActivation(activationDelegate: TunnelsManagerActivationDelegate?) {
         assert(status == .inactive || status == .restarting)
 
         guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() }
 
-        startActivation(tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
+        startActivation(tunnelConfiguration: tunnelConfiguration, activationDelegate: activationDelegate)
     }
 
     fileprivate func startActivation(recursionCount: UInt = 0,
                                      lastError: Error? = nil,
                                      tunnelConfiguration: TunnelConfiguration,
-                                     completionHandler: @escaping (TunnelsManagerError?) -> Void) {
+                                     activationDelegate: TunnelsManagerActivationDelegate?) {
         if recursionCount >= 8 {
             wg_log(.error, message: "startActivation: Failed after 8 attempts. Giving up with \(lastError!)")
-            completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
+            activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedBecauseOfTooManyErrors)
             return
         }
 
@@ -373,15 +382,16 @@ class TunnelContainer: NSObject {
             wg_log(.debug, staticMessage: "startActivation: Tunnel is disabled. Re-enabling and saving")
             tunnelProvider.isEnabled = true
             tunnelProvider.saveToPreferences { [weak self] error in
+                guard let self = self else { return }
                 if error != nil {
                     wg_log(.error, message: "Error saving tunnel after re-enabling: \(error!)")
-                    completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
+                    activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedWhileSaving)
                     return
                 }
                 wg_log(.debug, staticMessage: "startActivation: Tunnel saved after re-enabling")
                 wg_log(.debug, staticMessage: "startActivation: Invoking startActivation")
-                self?.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown),
-                                      tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
+                self.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown),
+                                      tunnelConfiguration: tunnelConfiguration, activationDelegate: activationDelegate)
             }
             return
         }
@@ -389,33 +399,36 @@ class TunnelContainer: NSObject {
         // Start the tunnel
         do {
             wg_log(.debug, staticMessage: "startActivation: Starting tunnel")
+            self.isAttemptingActivation = true
             try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel()
             wg_log(.debug, staticMessage: "startActivation: Success")
-            completionHandler(nil)
+            activationDelegate?.tunnelActivationAttemptSucceeded(tunnel: self)
         } catch let error {
+            self.isAttemptingActivation = false
             guard let systemError = error as? NEVPNError else {
                 wg_log(.error, message: "Failed to activate tunnel: Error: \(error)")
                 status = .inactive
-                completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
+                activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedWhileStarting)
                 return
             }
             guard systemError.code == NEVPNError.configurationInvalid || systemError.code == NEVPNError.configurationStale else {
                 wg_log(.error, message: "Failed to activate tunnel: VPN Error: \(error)")
                 status = .inactive
-                completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
+                activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedWhileStarting)
                 return
             }
             wg_log(.debug, staticMessage: "startActivation: Will reload tunnel and then try to start it.")
             tunnelProvider.loadFromPreferences { [weak self] error in
+                guard let self = self else { return }
                 if error != nil {
                     wg_log(.error, message: "startActivation: Error reloading tunnel: \(error!)")
-                    self?.status = .inactive
-                    completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
+                    self.status = .inactive
+                    activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedWhileLoading)
                     return
                 }
                 wg_log(.debug, staticMessage: "startActivation: Tunnel reloaded")
                 wg_log(.debug, staticMessage: "startActivation: Invoking startActivation")
-                self?.startActivation(recursionCount: recursionCount + 1, lastError: systemError, tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
+                self.startActivation(recursionCount: recursionCount + 1, lastError: systemError, tunnelConfiguration: tunnelConfiguration, activationDelegate: activationDelegate)
             }
         }
     }
index 6822263d857656f19f6c257ad3eea5c874805f46..2fc46b2f9bd2df8b1187bd6ec396948198130644 100644 (file)
@@ -62,9 +62,21 @@ class MainViewController: UISplitViewController {
 }
 
 extension MainViewController: TunnelsManagerActivationDelegate {
-    func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerError) {
+    func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) {
         ErrorPresenter.showErrorAlert(error: error, from: self)
     }
+
+    func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) {
+        // Nothing to do
+    }
+
+    func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) {
+        ErrorPresenter.showErrorAlert(error: error, from: self)
+    }
+
+    func tunnelActivationSucceeded(tunnel: TunnelContainer) {
+        // Nothing to do
+    }
 }
 
 extension MainViewController {
index c5816e82c567971d1fb66ec2cef9e120e6106b14..93b35e4a2dc1077b0688cf8a4780d10637c3d1c2 100644 (file)
@@ -166,15 +166,7 @@ extension TunnelDetailTableViewController {
         cell.onSwitchToggled = { [weak self] isOn in
             guard let self = self else { return }
             if isOn {
-                self.tunnelsManager.startActivation(of: self.tunnel) { [weak self] error in
-                    if let error = error {
-                        ErrorPresenter.showErrorAlert(error: error, from: self) {
-                            DispatchQueue.main.async {
-                                cell.statusSwitch.isOn = false
-                            }
-                        }
-                    }
-                }
+                self.tunnelsManager.startActivation(of: self.tunnel)
             } else {
                 self.tunnelsManager.startDeactivation(of: self.tunnel)
             }
index f24b452df66d861727a014ea5dbba7639d17317c..efa85e61f013e6dc5c43d794e534d86b8c3cde73 100644 (file)
@@ -248,15 +248,7 @@ extension TunnelsListTableViewController: UITableViewDataSource {
             cell.onSwitchToggled = { [weak self] isOn in
                 guard let self = self, let tunnelsManager = self.tunnelsManager else { return }
                 if isOn {
-                    tunnelsManager.startActivation(of: tunnel) { [weak self] error in
-                        if let error = error {
-                            ErrorPresenter.showErrorAlert(error: error, from: self) {
-                                DispatchQueue.main.async {
-                                    cell.statusSwitch.isOn = false
-                                }
-                            }
-                        }
-                    }
+                    tunnelsManager.startActivation(of: tunnel)
                 } else {
                     tunnelsManager.startDeactivation(of: tunnel)
                 }