// SPDX-License-Identifier: MIT // Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. import Foundation import NetworkExtension import os class PacketTunnelProvider: NEPacketTunnelProvider { private lazy var adapter: WireGuardAdapter = { return WireGuardAdapter(with: self, backend: WireGuardBackendGo()) { logLevel, message in wg_log(logLevel.osLogLevel, message: message) } }() override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { let activationAttemptId = options?["activationAttemptId"] as? String let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId) Logger.configureGlobal(tagged: "NET", withFilePath: FileManager.logFileURL?.path) wg_log(.info, message: "Starting tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app")) guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol, let tunnelConfiguration = tunnelProviderProtocol.asTunnelConfiguration() else { errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid) completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid) return } // Start the tunnel adapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in guard let adapterError = adapterError else { let interfaceName = self.adapter.interfaceName ?? "unknown" wg_log(.info, message: "Tunnel interface is \(interfaceName)") completionHandler(nil) return } switch adapterError { case .cannotLocateTunnelFileDescriptor: wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor") errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor) completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor) case .dnsResolution(let dnsErrors): let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address } .joined(separator: ", ") wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)") errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure) completionHandler(PacketTunnelProviderError.dnsResolutionFailure) case .setNetworkSettings(let error): wg_log(.error, message: "Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)") errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings) completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings) case .startWireGuardBackend(let errorCode): wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)") errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend) completionHandler(PacketTunnelProviderError.couldNotStartBackend) case .invalidState: // Must never happen fatalError() } } } override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { wg_log(.info, staticMessage: "Stopping tunnel") adapter.stop { error in ErrorNotifier.removeLastErrorFile() if let error = error { wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)") } completionHandler() #if os(macOS) // HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107). // Remove it when they finally fix this upstream and the fix has been rolled out to // sufficient quantities of users. exit(0) #endif } } override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { guard let completionHandler = completionHandler else { return } if messageData.count == 1 && messageData[0] == 0 { adapter.getRuntimeConfiguration { settings in var data: Data? if let settings = settings { data = settings.data(using: .utf8)! } completionHandler(data) } } else { completionHandler(nil) } } } extension PacketTunnelProvider: WireGuardAdapterDelegate { public func adapterShouldReassert(_ adapter: WireGuardAdapter, reasserting: Bool) { self.reasserting = reasserting } public func adapterShouldSetNetworkSettings(_ adapter: WireGuardAdapter, settings: Any?, completionHandler: ((Error?) -> Void)?) { guard let settings = settings as? NEPacketTunnelNetworkSettings else { return } setTunnelNetworkSettings(settings, completionHandler: completionHandler) } } extension WireGuardLogLevel { var osLogLevel: OSLogType { switch self { case .verbose: return .debug case .error: return .error } } }