diff --git a/WireGuard/Shared/FileManager+Extension.swift b/WireGuard/Shared/FileManager+Extension.swift new file mode 100644 index 0000000..7b25ffb --- /dev/null +++ b/WireGuard/Shared/FileManager+Extension.swift @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import Foundation +import os.log + +extension FileManager { + static var networkExtensionLogFileURL: 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("lastActivatedTunnelLog.txt") + } +} diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index e47f9ba..c41bede 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 6BB8400421892C920003598F /* CopyableLabelTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */; }; 6F0068572191AFD200419BE9 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0068562191AFD200419BE9 /* ScrollableLabel.swift */; }; + 6F5A2B4621AFDED40081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; }; + 6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; }; 6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; }; 6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; }; @@ -88,6 +90,7 @@ /* Begin PBXFileReference section */ 6BB8400321892C920003598F /* CopyableLabelTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CopyableLabelTableViewCell.swift; sourceTree = ""; }; 6F0068562191AFD200419BE9 /* ScrollableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableLabel.swift; sourceTree = ""; }; + 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = ""; }; 6F5D0C1421832391000F85AD /* DNSResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = ""; }; 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WireGuardNetworkExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; @@ -179,6 +182,7 @@ children = ( 6F7774E6217201E0006A79B3 /* Model */, 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */, + 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */, ); path = Shared; sourceTree = ""; @@ -506,6 +510,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6F5A2B4621AFDED40081EDD8 /* FileManager+Extension.swift in Sources */, 6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */, 6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */, 6FFA5D8E2194370D0001E2F7 /* Configuration.swift in Sources */, @@ -538,6 +543,7 @@ 6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */, 6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */, 6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */, + 6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */, 6FDEF8082187442100D8FBF6 /* WgQuickConfigFileWriter.swift in Sources */, 6FE254FB219C10800028284D /* ZipImporter.swift in Sources */, 6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */, diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift index cae4524..4111282 100644 --- a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift +++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift @@ -2,6 +2,7 @@ // Copyright © 2018 WireGuard LLC. All Rights Reserved. import NetworkExtension +import Foundation import os.log enum PacketTunnelProviderError: Error { @@ -11,6 +12,8 @@ enum PacketTunnelProviderError: Error { case coultNotSetNetworkSettings } +private var logFileHandle: FileHandle? + /// A packet tunnel provider object. class PacketTunnelProvider: NEPacketTunnelProvider { @@ -35,7 +38,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } func startTunnel(with tunnelConfiguration: TunnelConfiguration, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) { - os_log("Starting tunnel", log: OSLog.default, type: .info) + + // Configure logging + configureLogger() + + wg_log(.info, "Starting tunnel") // Resolve endpoint domains @@ -44,8 +51,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { do { resolvedEndpoints = try DNSResolver.resolveSync(endpoints: endpoints) } catch DNSResolverError.dnsResolutionFailed(let hostnames) { - os_log("Starting tunnel failed: DNS resolution failure for %{public}d hostnames (%{public}s)", log: OSLog.default, - type: .error, hostnames.count, hostnames.joined(separator: ", ")) + wg_log(.error, "Starting tunnel failed: DNS resolution failure") + wg_log(.error, "Hostnames for which DNS resolution failed: \(hostnames.joined(separator: ", "))") ErrorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure(hostnames: hostnames), from: self) startTunnelCompletionHandler(PacketTunnelProviderError.dnsResolutionFailure(hostnames: hostnames)) return @@ -62,11 +69,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // Bring up wireguard-go backend - configureLogger() - let fd = packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32 if fd < 0 { - os_log("Starting tunnel failed: Could not determine file descriptor", log: OSLog.default, type: .error) + wg_log(.error, "Starting tunnel failed: Could not determine file descriptor") ErrorNotifier.notify(PacketTunnelProviderError.couldNotStartWireGuard, from: self) startTunnelCompletionHandler(PacketTunnelProviderError.couldNotStartWireGuard) return @@ -76,7 +81,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let handle = connect(interfaceName: tunnelConfiguration.interface.name, settings: wireguardSettings, fd: fd) if handle < 0 { - os_log("Starting tunnel failed: Could not start WireGuard", log: OSLog.default, type: .error) + wg_log(.error, "Starting tunnel failed: Could not start WireGuard") ErrorNotifier.notify(PacketTunnelProviderError.couldNotStartWireGuard, from: self) startTunnelCompletionHandler(PacketTunnelProviderError.couldNotStartWireGuard) return @@ -89,7 +94,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let networkSettings: NEPacketTunnelNetworkSettings = packetTunnelSettingsGenerator.generateNetworkSettings() setTunnelNetworkSettings(networkSettings) { (error) in if let error = error { - os_log("Starting tunnel failed: Error setting network settings: %s", log: OSLog.default, type: .error, error.localizedDescription) + wg_log(.error, "Starting tunnel failed: Error setting network settings.") + wg_log(.error, "Error from setTunnelNetworkSettings: \(error.localizedDescription)") ErrorNotifier.notify(PacketTunnelProviderError.coultNotSetNetworkSettings, from: self) startTunnelCompletionHandler(PacketTunnelProviderError.coultNotSetNetworkSettings) } else { @@ -100,14 +106,34 @@ class PacketTunnelProvider: NEPacketTunnelProvider { /// Begin the process of stopping the tunnel. override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - os_log("Stopping tunnel", log: OSLog.default, type: .info) + wg_log(.info, "Stopping tunnel") if let handle = wgHandle { wgTurnOff(handle) } + if let fileHandle = logFileHandle { + fileHandle.closeFile() + } completionHandler() } private func configureLogger() { + + // Setup writing the log to a file + if let networkExtensionLogFileURL = FileManager.networkExtensionLogFileURL { + let fileManager = FileManager.default + let filePath = networkExtensionLogFileURL.path + fileManager.createFile(atPath: filePath, contents: nil) // Create the file if it doesn't already exist + if let fileHandle = FileHandle(forWritingAtPath: filePath) { + logFileHandle = fileHandle + } else { + os_log("Can't open log file for writing. Log is not saved to file.", log: OSLog.default, type: .error) + logFileHandle = nil + } + } else { + os_log("Can't obtain log file URL. Log is not saved to file.", log: OSLog.default, type: .error) + } + + // Setup WireGuard logger wgSetLogger { (level, msgCStr) in let logType: OSLogType switch level { @@ -121,7 +147,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { logType = .default } let msg = (msgCStr != nil) ? String(cString: msgCStr!) : "" - os_log("%{public}s", log: OSLog.default, type: logType, msg) + wg_log(logType, msg) } } @@ -130,6 +156,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return wgTurnOn(nameGoStr, settingsGoStr, fd) } } + } private func withStringsAsGoStrings(_ str1: String, _ str2: String, closure: (gostring_t, gostring_t) -> R) -> R { @@ -141,3 +168,45 @@ private func withStringsAsGoStrings(_ str1: String, _ str2: String, closure: } } } + +private func wg_log(_ type: OSLogType, _ msg: StaticString) { + // Write to os log + os_log(msg, log: OSLog.default, type: type) + // Write to file log + let msgString: String = msg.withUTF8Buffer { (ptr: UnsafeBufferPointer) -> String in + return String(decoding: ptr, as: UTF8.self) + } + file_log(type: type, message: msgString) +} + +private func wg_log(_ type: OSLogType, _ msg: String) { + // Write to os log + os_log("%{public}s", log: OSLog.default, type: type, msg) + // Write to file log + file_log(type: type, message: msg) +} + +private func file_log(type: OSLogType, message: String) { + var msgLine = type.toMessagePrefix() + message + if (msgLine.last! != "\n") { + msgLine.append("\n") + } + let data = msgLine.data(using: .utf8) + if let data = data, let logFileHandle = logFileHandle { + logFileHandle.write(data) + logFileHandle.synchronizeFile() + } +} + +extension OSLogType { + func toMessagePrefix() -> String { + switch (self) { + case .debug: return "Debug: " + case .info: return "Info: " + case .error: return "Error: " + case .fault: return "Fault: " + default: + return "Unknown: " + } + } +}