]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
NE: Handle bad domain names and Activate On Demand
authorRoopesh Chander <roop@roopc.net>
Wed, 19 Dec 2018 10:08:00 +0000 (15:38 +0530)
committerRoopesh Chander <roop@roopc.net>
Wed, 19 Dec 2018 10:08:00 +0000 (15:38 +0530)
This combination causes iOS to keep trying to bring up the tunnel,
leading to a lot of displayMessage() alerts.

In this fix, if we get a DNS resolution error in an Activate On Demand
enabled tunnel, we silently retry 9 times (with a 4-second delay before
each retry) and then show the displayMessage() alert.

Signed-off-by: Roopesh Chander <roop@roopc.net>
WireGuard/Shared/NETunnelProviderProtocol+Extension.swift
WireGuard/WireGuard/Tunnel/TunnelsManager.swift
WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift

index 960bf228cd4131b838eb34273d1564bcce11398f..9f4af774daa99e1d12818b12386e32eb248e44fc 100644 (file)
@@ -4,7 +4,7 @@
 import NetworkExtension
 
 extension NETunnelProviderProtocol {
-    convenience init?(tunnelConfiguration: TunnelConfiguration) {
+    convenience init?(tunnelConfiguration: TunnelConfiguration, isActivateOnDemandEnabled: Bool) {
         assert(!tunnelConfiguration.interface.name.isEmpty)
         guard let serializedTunnelConfiguration = try? JSONEncoder().encode(tunnelConfiguration) else { return nil }
 
@@ -14,7 +14,8 @@ extension NETunnelProviderProtocol {
         providerBundleIdentifier = "\(appId).network-extension"
         providerConfiguration = [
             "tunnelConfiguration": serializedTunnelConfiguration,
-            "tunnelConfigurationVersion": 1
+            "tunnelConfigurationVersion": 1,
+            "isActivateOnDemandEnabled": isActivateOnDemandEnabled
         ]
 
         let endpoints = tunnelConfiguration.peers.compactMap {$0.endpoint}
@@ -32,4 +33,8 @@ extension NETunnelProviderProtocol {
         guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return nil }
         return try? JSONDecoder().decode(TunnelConfiguration.self, from: serializedTunnelConfiguration)
     }
+
+    var isActivateOnDemandEnabled: Bool {
+        return (providerConfiguration?["isActivateOnDemandEnabled"] as? Bool) ?? false
+    }
 }
index 26e134f6991877e8770a71abdae0e4bf87ccaceb..10ef1ef43bbabc64d02e0fcdcec779a9ca7aafd1 100644 (file)
@@ -59,7 +59,7 @@ class TunnelsManager {
         }
 
         let tunnelProviderManager = NETunnelProviderManager()
-        tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration)
+        tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration, isActivateOnDemandEnabled: activateOnDemandSetting.isActivateOnDemandEnabled)
         tunnelProviderManager.localizedDescription = tunnelName
         tunnelProviderManager.isEnabled = true
 
@@ -115,7 +115,7 @@ class TunnelsManager {
             }
             tunnel.name = tunnelName
         }
-        tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration)
+        tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration, isActivateOnDemandEnabled: activateOnDemandSetting.isActivateOnDemandEnabled)
         tunnelProviderManager.localizedDescription = tunnelName
         tunnelProviderManager.isEnabled = true
 
index c392233b042a57cfc0bf8e824b1c1875570038a7..02fbd4c7f6acb813f6dde3aca3319bc46481ccfe 100644 (file)
@@ -18,8 +18,12 @@ class ErrorNotifier {
         switch error {
         case .savedProtocolConfigurationIsInvalid:
             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 .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")
+            }
         case .couldNotStartWireGuard:
             return ("Activation failure", "WireGuard backend could not be started")
         case .coultNotSetNetworkSettings:
index 12a2e4361af677c84db0fb6f77a27954f6290305..9a3aede054d2d4149f470ee30b55b1dcff96740c 100644 (file)
@@ -8,7 +8,7 @@ import os.log
 
 enum PacketTunnelProviderError: Error {
     case savedProtocolConfigurationIsInvalid
-    case dnsResolutionFailure(hostnames: [String])
+    case dnsResolutionFailure(tunnelName: String, isActivateOnDemandEnabled: Bool)
     case couldNotStartWireGuard
     case coultNotSetNetworkSettings
 }
@@ -38,21 +38,22 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
 
         configureLogger()
 
-        wg_log(.info, message: "Starting tunnel '\(tunnelConfiguration.interface.name)'")
+        let tunnelName = tunnelConfiguration.interface.name
+        wg_log(.info, message: "Starting tunnel '\(tunnelName)'")
+
+        let isActivateOnDemandEnabled = tunnelProviderProtocol.isActivateOnDemandEnabled
+        if isActivateOnDemandEnabled {
+            wg_log(.info, staticMessage: "Tunnel has Activate On Demand enabled")
+        } else {
+            wg_log(.info, staticMessage: "Tunnel has Activate On Demand disabled")
+        }
 
         let endpoints = tunnelConfiguration.peers.map { $0.endpoint }
-        var resolvedEndpoints = [Endpoint?]()
-        do {
-            resolvedEndpoints = try DNSResolver.resolveSync(endpoints: endpoints)
-        } 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: ", "))")
-            errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure(hostnames: hostnames))
-            startTunnelCompletionHandler(PacketTunnelProviderError.dnsResolutionFailure(hostnames: hostnames))
+        guard let resolvedEndpoints = resolveDomainNames(endpoints: endpoints, isActivateOnDemandEnabled: isActivateOnDemandEnabled) else {
+            let dnsError = PacketTunnelProviderError.dnsResolutionFailure(tunnelName: tunnelName, isActivateOnDemandEnabled: isActivateOnDemandEnabled)
+            errorNotifier.notify(dnsError)
+            startTunnelCompletionHandler(dnsError)
             return
-        } catch {
-            // There can be no other errors from DNSResolver.resolveSync()
-            fatalError()
         }
         assert(endpoints.count == resolvedEndpoints.count)
 
@@ -143,6 +144,36 @@ 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))")
+                }
+            }
+        }
+
+        return isDNSResolved ? resolvedEndpoints : nil
+    }
+
     private func connect(interfaceName: String, settings: String, fileDescriptor: Int32) -> Int32 {
         return withStringsAsGoStrings(interfaceName, settings) { return wgTurnOn($0.0, $0.1, fileDescriptor) }
     }