]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
NE: Change handling of bad domain names and Activate On Demand
authorRoopesh Chander <roop@roopc.net>
Fri, 21 Dec 2018 10:10:04 +0000 (15:40 +0530)
committerRoopesh Chander <roop@roopc.net>
Fri, 21 Dec 2018 10:22:47 +0000 (15:52 +0530)
The solution implemented in commit b8c331c causes the tunnel to
remain in 'Activating' state, without the ability to cancel that.

So, in this commit, instead of retrying DNS silently on
Activated-On-Demand tunnels, we fail the startTunnel() silently.

To summarize, if activate-on-demand is on:
- If started from the WireGuard app, show error using lastErrorFile
mechanism, suggesting a way to turn off Activate On Demand
- If not started from WireGuard app, don't call displayMessage()
(don't show error to user) and silently fail starting the tunnel

Signed-off-by: Roopesh Chander <roop@roopc.net>
WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift

index 02fbd4c7f6acb813f6dde3aca3319bc46481ccfe..1b74d5d4be7688483679e5d38dfc189765bfe4aa 100644 (file)
@@ -8,6 +8,9 @@ class ErrorNotifier {
     let activationAttemptId: String?
     weak var tunnelProvider: NEPacketTunnelProvider?
 
+    var tunnelName: String?
+    var isActivateOnDemandEnabled = false
+
     init(activationAttemptId: String?, tunnelProvider: NEPacketTunnelProvider) {
         self.activationAttemptId = activationAttemptId
         self.tunnelProvider = tunnelProvider
@@ -17,17 +20,13 @@ class ErrorNotifier {
     func errorMessage(for error: PacketTunnelProviderError) -> (String, String)? {
         switch error {
         case .savedProtocolConfigurationIsInvalid:
-            return ("Activation failure", "Could not retrieve tunnel information from the saved configuration")
-        case .dnsResolutionFailure(let tunnelName, let isActivateOnDemandEnabled):
-            if isActivateOnDemandEnabled {
-                return ("DNS resolution failure", "This tunnel has Activate On Demand enabled, so activation might be retried. You may turn off Activate On Demand in the WireGuard app by navigating to: '\(tunnelName)' > Edit")
-            } else {
-                return ("DNS resolution failure", "One or more endpoint domains could not be resolved")
-            }
+            return ("Activation failure", "Could not retrieve tunnel information from the saved configuration.")
+        case .dnsResolutionFailure:
+            return ("DNS resolution failure", "One or more endpoint domains could not be resolved.")
         case .couldNotStartWireGuard:
-            return ("Activation failure", "WireGuard backend could not be started")
+            return ("Activation failure", "WireGuard backend could not be started.")
         case .coultNotSetNetworkSettings:
-            return ("Activation failure", "Error applying network settings on the tunnel")
+            return ("Activation failure", "Error applying network settings on the tunnel.")
         }
     }
 
@@ -35,13 +34,16 @@ class ErrorNotifier {
         guard let (title, message) = errorMessage(for: error) else { return }
         if let activationAttemptId = activationAttemptId, let lastErrorFilePath = FileManager.networkExtensionLastErrorFileURL?.path {
             // The tunnel was started from the app
-            let errorMessageData = "\(activationAttemptId)\n\(title)\n\(message)".data(using: .utf8)
+            let onDemandMessage = isActivateOnDemandEnabled ? " This tunnel has Activate On Demand enabled, so this tunnel might be activated automatically. You may turn off Activate On Demand in the WireGuard app by navigating to: '\(tunnelName ?? "tunnel")' > Edit." : ""
+            let errorMessageData = "\(activationAttemptId)\n\(title)\n\(message)\(onDemandMessage)".data(using: .utf8)
             FileManager.default.createFile(atPath: lastErrorFilePath, contents: errorMessageData, attributes: nil)
         } else {
-            // The tunnel was probably started from iOS Settings app
+            // The tunnel was probably started from iOS Settings app or activated on-demand
             if let tunnelProvider = self.tunnelProvider {
                 // displayMessage() is deprecated, but there's no better alternative if invoked from iOS Settings
-                tunnelProvider.displayMessage("\(title): \(message)") { _ in }
+                if !isActivateOnDemandEnabled { // If using activate-on-demand, don't use displayMessage
+                    tunnelProvider.displayMessage("\(title): \(message)") { _ in }
+                }
             }
         }
     }
index f0ae3e7f67a6cd66e3e971826505766cb014b57a..f678ca7afe2df128f698b79505d47a1c8fb3d592 100644 (file)
@@ -8,7 +8,7 @@ import os.log
 
 enum PacketTunnelProviderError: Error {
     case savedProtocolConfigurationIsInvalid
-    case dnsResolutionFailure(tunnelName: String, isActivateOnDemandEnabled: Bool)
+    case dnsResolutionFailure
     case couldNotStartWireGuard
     case coultNotSetNetworkSettings
 }
@@ -54,9 +54,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
             wg_log(.info, staticMessage: "Tunnel has Activate On Demand disabled")
         }
 
+        errorNotifier.isActivateOnDemandEnabled = isActivateOnDemandEnabled
+        errorNotifier.tunnelName = tunnelName
+
         let endpoints = tunnelConfiguration.peers.map { $0.endpoint }
-        guard let resolvedEndpoints = resolveDomainNames(endpoints: endpoints, isActivateOnDemandEnabled: isActivateOnDemandEnabled) else {
-            let dnsError = PacketTunnelProviderError.dnsResolutionFailure(tunnelName: tunnelName, isActivateOnDemandEnabled: isActivateOnDemandEnabled)
+        guard let resolvedEndpoints = resolveDomainNames(endpoints: endpoints) else {
+            wg_log(.error, staticMessage: "Starting tunnel failed: DNS resolution failure")
+            let dnsError = PacketTunnelProviderError.dnsResolutionFailure
             errorNotifier.notify(dnsError)
             startTunnelCompletionHandler(dnsError)
             return
@@ -148,34 +152,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
         }
     }
 
-    private func resolveDomainNames(endpoints: [Endpoint?], isActivateOnDemandEnabled: Bool) -> [Endpoint?]? {
-        var resolvedEndpoints = [Endpoint?]()
-        let dnsResolutionAttemptsCount = isActivateOnDemandEnabled ? 10 : 1
-        var isDNSResolved = false
-
-        for attemptIndex in 0 ..< dnsResolutionAttemptsCount {
-            do {
-                resolvedEndpoints = try DNSResolver.resolveSync(endpoints: endpoints)
-                isDNSResolved = true
-            } catch DNSResolverError.dnsResolutionFailed(let hostnames) {
-                wg_log(.error, staticMessage: "Starting tunnel failed: DNS resolution failure")
-                wg_log(.error, message: "Hostnames for which DNS resolution failed: \(hostnames.joined(separator: ", "))")
-            } catch {
-                // There can be no other errors from DNSResolver.resolveSync()
-                fatalError()
-            }
-            if isDNSResolved {
-                break
-            } else {
-                let isLastAttempt = attemptIndex == dnsResolutionAttemptsCount - 1
-                if !isLastAttempt {
-                    Thread.sleep(forTimeInterval: 4 /* seconds */)
-                    wg_log(.error, message: "Retrying DNS resolution (Attempt \(attemptIndex + 2))")
-                }
-            }
+    private func resolveDomainNames(endpoints: [Endpoint?]) -> [Endpoint?]? {
+        do {
+            return try DNSResolver.resolveSync(endpoints: endpoints)
+        } catch DNSResolverError.dnsResolutionFailed(let hostnames) {
+            wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnames.joined(separator: ", "))")
+        } catch {
+            // There can be no other errors from DNSResolver.resolveSync()
+            fatalError()
         }
-
-        return isDNSResolved ? resolvedEndpoints : nil
+        return nil
     }
 
     private func connect(interfaceName: String, settings: String, fileDescriptor: Int32) -> Int32 {