]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
NE: Communicate last error to app through a shared file
authorRoopesh Chander <roop@roopc.net>
Thu, 13 Dec 2018 20:54:53 +0000 (02:24 +0530)
committerRoopesh Chander <roop@roopc.net>
Thu, 13 Dec 2018 20:54:53 +0000 (02:24 +0530)
Signed-off-by: Roopesh Chander <roop@roopc.net>
WireGuard/Shared/FileManager+Extension.swift
WireGuard/WireGuard/Tunnel/TunnelsManager.swift
WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift

index 18931ba88b613b829b9d15b5d5eeb67d425eaaa4..55713fd2cc6f5a5573f8c6e9f7c637e4e94542c9 100644 (file)
@@ -17,6 +17,18 @@ extension FileManager {
         return sharedFolderURL.appendingPathComponent("tunnel-log.txt")
     }
 
+    static var networkExtensionLastErrorFileURL: URL? {
+        guard let appGroupId = Bundle.main.object(forInfoDictionaryKey: "com.wireguard.ios.app_group_id") as? String else {
+            os_log("Can't obtain app group id from bundle", log: OSLog.default, type: .error)
+            return nil
+        }
+        guard let sharedFolderURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
+            os_log("Can't obtain shared folder URL", log: OSLog.default, type: .error)
+            return nil
+        }
+        return sharedFolderURL.appendingPathComponent("last-error.txt")
+    }
+
     static var appLogFileURL: URL? {
         guard let documentDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
             os_log("Can't obtain app documents folder URL", log: OSLog.default, type: .error)
index 40d86629ea4df85b0d72c3b375ebf3c7a82a3770..a455ac2630bdda1b12682da8f76b2e7681beae42 100644 (file)
@@ -41,8 +41,14 @@ enum TunnelsManagerActivationAttemptError: WireGuardAppError {
 
 enum TunnelsManagerActivationError: WireGuardAppError {
     case activationFailed
+    case activationFailedWithExtensionError(title: String, message: String)
     var alertText: AlertText {
-        return ("Activation failure", "The tunnel could not be activated. Please ensure you are connected to the Internet.")
+        switch self {
+        case .activationFailed:
+            return ("Activation failure", "The tunnel could not be activated. Please ensure you are connected to the Internet.")
+        case .activationFailedWithExtensionError(let title, let message):
+            return (title, message)
+        }
     }
 }
 
@@ -297,7 +303,11 @@ class TunnelsManager {
                     self.activationDelegate?.tunnelActivationSucceeded(tunnel: tunnel)
                 } else if session.status == .disconnected {
                     tunnel.isAttemptingActivation = false
-                    self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailed)
+                    if let (title, message) = self.lastErrorTextFromNetworkExtension(for: tunnel) {
+                        self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailedWithExtensionError(title: title, message: message))
+                    } else {
+                        self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailed)
+                    }
                 }
             }
 
@@ -321,6 +331,20 @@ class TunnelsManager {
         }
     }
 
+    func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? {
+        guard let lastErrorFileURL = FileManager.networkExtensionLastErrorFileURL else { return nil }
+        guard let lastErrorData = try? Data(contentsOf: lastErrorFileURL) else { return nil }
+        guard let lastErrorText = String(data: lastErrorData, encoding: .utf8) else { return nil }
+        let lastErrorStrings = lastErrorText.split(separator: "\n").map { String($0) }
+        guard lastErrorStrings.count == 3 else { return nil }
+        let attemptIdInDisk = lastErrorStrings[0]
+        if let attemptIdForTunnel = tunnel.activationAttemptId, attemptIdInDisk == attemptIdForTunnel {
+            return (title: lastErrorStrings[1], message: lastErrorStrings[2])
+        }
+
+        return nil
+    }
+
     deinit {
         if let statusObservationToken = self.statusObservationToken {
             NotificationCenter.default.removeObserver(statusObservationToken)
@@ -335,6 +359,7 @@ class TunnelContainer: NSObject {
     @objc dynamic var isActivateOnDemandEnabled: Bool
 
     var isAttemptingActivation = false
+    var activationAttemptId: String?
 
     fileprivate let tunnelProvider: NETunnelProviderManager
     private var lastTunnelConnectionStatus: NEVPNStatus?
@@ -398,7 +423,9 @@ class TunnelContainer: NSObject {
         do {
             wg_log(.debug, staticMessage: "startActivation: Starting tunnel")
             self.isAttemptingActivation = true
-            try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel()
+            let activationAttemptId = UUID().uuidString
+            self.activationAttemptId = activationAttemptId
+            try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel(options: ["activationAttemptId": activationAttemptId])
             wg_log(.debug, staticMessage: "startActivation: Success")
             activationDelegate?.tunnelActivationAttemptSucceeded(tunnel: self)
         } catch let error {
index 163535a3bcb76cb3537032dd5245e47c3ce8e733..c392233b042a57cfc0bf8e824b1c1875570038a7 100644 (file)
@@ -4,7 +4,17 @@
 import NetworkExtension
 
 class ErrorNotifier {
-    static func errorMessage(for error: PacketTunnelProviderError) -> (String, String)? {
+
+    let activationAttemptId: String?
+    weak var tunnelProvider: NEPacketTunnelProvider?
+
+    init(activationAttemptId: String?, tunnelProvider: NEPacketTunnelProvider) {
+        self.activationAttemptId = activationAttemptId
+        self.tunnelProvider = tunnelProvider
+        ErrorNotifier.removeLastErrorFile()
+    }
+
+    func errorMessage(for error: PacketTunnelProviderError) -> (String, String)? {
         switch error {
         case .savedProtocolConfigurationIsInvalid:
             return ("Activation failure", "Could not retrieve tunnel information from the saved configuration")
@@ -17,9 +27,24 @@ class ErrorNotifier {
         }
     }
 
-    static func notify(_ error: PacketTunnelProviderError, from tunnelProvider: NEPacketTunnelProvider) {
-        guard let (title, message) = ErrorNotifier.errorMessage(for: error) else { return }
-        // displayMessage() is deprecated, but there's no better alternative to show the error to the user
-        tunnelProvider.displayMessage("\(title): \(message)") { _ in }
+    func notify(_ error: PacketTunnelProviderError) {
+        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)
+            FileManager.default.createFile(atPath: lastErrorFilePath, contents: errorMessageData, attributes: nil)
+        } else {
+            // The tunnel was probably started from iOS Settings app
+            if let tunnelProvider = self.tunnelProvider {
+                // displayMessage() is deprecated, but there's no better alternative if invoked from iOS Settings
+                tunnelProvider.displayMessage("\(title): \(message)") { _ in }
+            }
+        }
+    }
+
+    static func removeLastErrorFile() {
+        if let lastErrorFileURL = FileManager.networkExtensionLastErrorFileURL {
+            _ = FileManager.deleteFile(at: lastErrorFileURL)
+        }
     }
 }
index f2fa26911e647af6bfa6d4d59302950bfa94eb2d..559c7c26de57c2ee2c461d75b0dcd2b1a111460f 100644 (file)
@@ -28,21 +28,23 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
         networkMonitor?.cancel()
     }
 
-    /// Begin the process of establishing the tunnel.
     override func startTunnel(options: [String: NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
 
+        let activationAttemptId = options?["activationAttemptId"] as? String
+        let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId, tunnelProvider: self)
+
         guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol,
             let tunnelConfiguration = tunnelProviderProtocol.tunnelConfiguration() else {
-                ErrorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid, from: self)
+                errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
                 startTunnelCompletionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
                 return
         }
 
-        startTunnel(with: tunnelConfiguration, completionHandler: startTunnelCompletionHandler)
+        startTunnel(with: tunnelConfiguration, errorNotifier: errorNotifier, completionHandler: startTunnelCompletionHandler)
     }
 
     //swiftlint:disable:next function_body_length
-    func startTunnel(with tunnelConfiguration: TunnelConfiguration, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
+    func startTunnel(with tunnelConfiguration: TunnelConfiguration, errorNotifier: ErrorNotifier, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
 
         configureLogger()
 
@@ -55,7 +57,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
         } 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), from: self)
+            errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure(hostnames: hostnames))
             startTunnelCompletionHandler(PacketTunnelProviderError.dnsResolutionFailure(hostnames: hostnames))
             return
         } catch {
@@ -73,7 +75,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
         let fileDescriptor = packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32 //swiftlint:disable:this force_cast
         if fileDescriptor < 0 {
             wg_log(.error, staticMessage: "Starting tunnel failed: Could not determine file descriptor")
-            ErrorNotifier.notify(PacketTunnelProviderError.couldNotStartWireGuard, from: self)
+            errorNotifier.notify(PacketTunnelProviderError.couldNotStartWireGuard)
             startTunnelCompletionHandler(PacketTunnelProviderError.couldNotStartWireGuard)
             return
         }
@@ -101,7 +103,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
 
         if handle < 0 {
             wg_log(.error, staticMessage: "Starting tunnel failed: Could not start WireGuard")
-            ErrorNotifier.notify(PacketTunnelProviderError.couldNotStartWireGuard, from: self)
+            errorNotifier.notify(PacketTunnelProviderError.couldNotStartWireGuard)
             startTunnelCompletionHandler(PacketTunnelProviderError.couldNotStartWireGuard)
             return
         }
@@ -115,7 +117,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
             if let error = error {
                 wg_log(.error, staticMessage: "Starting tunnel failed: Error setting network settings.")
                 wg_log(.error, message: "Error from setTunnelNetworkSettings: \(error.localizedDescription)")
-                ErrorNotifier.notify(PacketTunnelProviderError.coultNotSetNetworkSettings, from: self)
+                errorNotifier.notify(PacketTunnelProviderError.coultNotSetNetworkSettings)
                 startTunnelCompletionHandler(PacketTunnelProviderError.coultNotSetNetworkSettings)
             } else {
                 startTunnelCompletionHandler(nil /* No errors */)
@@ -128,6 +130,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
         networkMonitor?.cancel()
         networkMonitor = nil
 
+        ErrorNotifier.removeLastErrorFile()
+
         wg_log(.info, staticMessage: "Stopping tunnel")
         if let handle = wgHandle {
             wgTurnOff(handle)