From 93b1c4d83493e9fefab825ed9705d867b1de206f Mon Sep 17 00:00:00 2001 From: Joseph Mattello Date: Sat, 12 Nov 2022 23:21:02 -0500 Subject: [PATCH] WireGuard add extra source files Signed-off-by: Joseph Mattello --- AltStore.xcodeproj/project.pbxproj | 64 ++++- .../AppProxyProvider.swift | 141 ++++++++-- WireguardNetworkExtension/ErrorNotifier.swift | 25 ++ .../FileManager+Extension.swift | 50 ++++ WireguardNetworkExtension/Keychain.swift | 114 ++++++++ .../Logging/Logger.swift | 65 +++++ .../Logging/ringlogger.c | 173 ++++++++++++ .../Logging/ringlogger.h | 18 ++ .../Logging/test_ringlogger.c | 63 +++++ .../NETunnelProviderProtocol+Extension.swift | 108 ++++++++ .../Model/String+ArrayConversion.swift | 32 +++ .../TunnelConfiguration+WgQuickConfig.swift | 253 ++++++++++++++++++ .../NotificationToken.swift | 33 +++ ...ireguardNetworkExtension-Bridging-Header.h | 11 + 14 files changed, 1123 insertions(+), 27 deletions(-) create mode 100644 WireguardNetworkExtension/ErrorNotifier.swift create mode 100644 WireguardNetworkExtension/FileManager+Extension.swift create mode 100644 WireguardNetworkExtension/Keychain.swift create mode 100644 WireguardNetworkExtension/Logging/Logger.swift create mode 100644 WireguardNetworkExtension/Logging/ringlogger.c create mode 100644 WireguardNetworkExtension/Logging/ringlogger.h create mode 100644 WireguardNetworkExtension/Logging/test_ringlogger.c create mode 100644 WireguardNetworkExtension/Model/NETunnelProviderProtocol+Extension.swift create mode 100644 WireguardNetworkExtension/Model/String+ArrayConversion.swift create mode 100644 WireguardNetworkExtension/Model/TunnelConfiguration+WgQuickConfig.swift create mode 100644 WireguardNetworkExtension/NotificationToken.swift create mode 100644 WireguardNetworkExtension/WireguardNetworkExtension-Bridging-Header.h diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 45b53c53..f1a2bc93 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -28,6 +28,16 @@ B355DFC329209E2500E4C858 /* AppProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFC229209E2500E4C858 /* AppProxyProvider.swift */; }; B355DFC829209E2500E4C858 /* WireguardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = B355DFBE29209E2400E4C858 /* WireguardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; B355DFCF29209E4C00E4C858 /* WireGuardKit in Frameworks */ = {isa = PBXBuildFile; productRef = B355DFCE29209E4C00E4C858 /* WireGuardKit */; }; + B355DFEF2920A75E00E4C858 /* ringlogger.c in Sources */ = {isa = PBXBuildFile; fileRef = B355DFE42920A75D00E4C858 /* ringlogger.c */; }; + B355DFF02920A75E00E4C858 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFE52920A75D00E4C858 /* Logger.swift */; }; + B355DFF12920A75E00E4C858 /* test_ringlogger.c in Sources */ = {isa = PBXBuildFile; fileRef = B355DFE62920A75D00E4C858 /* test_ringlogger.c */; }; + B355DFF22920A75E00E4C858 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFE82920A75D00E4C858 /* Keychain.swift */; }; + B355DFF32920A75E00E4C858 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFE92920A75D00E4C858 /* FileManager+Extension.swift */; }; + B355DFF42920A75E00E4C858 /* TunnelConfiguration+WgQuickConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFEB2920A75D00E4C858 /* TunnelConfiguration+WgQuickConfig.swift */; }; + B355DFF52920A75E00E4C858 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFEC2920A75D00E4C858 /* NETunnelProviderProtocol+Extension.swift */; }; + B355DFF62920A75E00E4C858 /* String+ArrayConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFED2920A75D00E4C858 /* String+ArrayConversion.swift */; }; + B355DFF72920A75E00E4C858 /* NotificationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFEE2920A75E00E4C858 /* NotificationToken.swift */; }; + B355DFF92920A79E00E4C858 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFF82920A79E00E4C858 /* ErrorNotifier.swift */; }; B33FFBA8295F8E98002259E6 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B343F894295F7F9B002B1159 /* libfragmentzip.a */; }; B33FFBAA295F8F78002259E6 /* preboard.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBA9295F8F78002259E6 /* preboard.c */; }; B33FFBAC295F8F98002259E6 /* companion_proxy.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBAB295F8F98002259E6 /* companion_proxy.c */; }; @@ -544,6 +554,18 @@ B355DFC229209E2500E4C858 /* AppProxyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppProxyProvider.swift; sourceTree = ""; }; B355DFC429209E2500E4C858 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B355DFC529209E2500E4C858 /* WireguardNetworkExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireguardNetworkExtension.entitlements; sourceTree = ""; }; + B355DFE22920A6C200E4C858 /* WireguardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireguardNetworkExtension-Bridging-Header.h"; sourceTree = ""; }; + B355DFE42920A75D00E4C858 /* ringlogger.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ringlogger.c; sourceTree = ""; }; + B355DFE52920A75D00E4C858 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + B355DFE62920A75D00E4C858 /* test_ringlogger.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = test_ringlogger.c; sourceTree = ""; }; + B355DFE72920A75D00E4C858 /* ringlogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ringlogger.h; sourceTree = ""; }; + B355DFE82920A75D00E4C858 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; + B355DFE92920A75D00E4C858 /* FileManager+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = ""; }; + B355DFEB2920A75D00E4C858 /* TunnelConfiguration+WgQuickConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+WgQuickConfig.swift"; sourceTree = ""; }; + B355DFEC2920A75D00E4C858 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = ""; }; + B355DFED2920A75D00E4C858 /* String+ArrayConversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+ArrayConversion.swift"; sourceTree = ""; }; + B355DFEE2920A75E00E4C858 /* NotificationToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationToken.swift; sourceTree = ""; }; + B355DFF82920A79E00E4C858 /* ErrorNotifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = ""; }; B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = ""; }; B33FFBAB295F8F98002259E6 /* companion_proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = companion_proxy.c; path = src/companion_proxy.c; sourceTree = ""; }; B343F847295F6321002B1159 /* minimuxer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = minimuxer.xcodeproj; path = Dependencies/minimuxer.xcodeproj; sourceTree = SOURCE_ROOT; }; @@ -1024,13 +1046,41 @@ B355DFC129209E2500E4C858 /* WireguardNetworkExtension */ = { isa = PBXGroup; children = ( - B355DFC229209E2500E4C858 /* AppProxyProvider.swift */, - B355DFC429209E2500E4C858 /* Info.plist */, B355DFC529209E2500E4C858 /* WireguardNetworkExtension.entitlements */, + B355DFE22920A6C200E4C858 /* WireguardNetworkExtension-Bridging-Header.h */, + B355DFC429209E2500E4C858 /* Info.plist */, + B355DFC229209E2500E4C858 /* AppProxyProvider.swift */, + B355DFF82920A79E00E4C858 /* ErrorNotifier.swift */, + B355DFE92920A75D00E4C858 /* FileManager+Extension.swift */, + B355DFE82920A75D00E4C858 /* Keychain.swift */, + B355DFEE2920A75E00E4C858 /* NotificationToken.swift */, + B355DFE32920A75D00E4C858 /* Logging */, + B355DFEA2920A75D00E4C858 /* Model */, ); path = WireguardNetworkExtension; sourceTree = ""; }; + B355DFE32920A75D00E4C858 /* Logging */ = { + isa = PBXGroup; + children = ( + B355DFE42920A75D00E4C858 /* ringlogger.c */, + B355DFE52920A75D00E4C858 /* Logger.swift */, + B355DFE62920A75D00E4C858 /* test_ringlogger.c */, + B355DFE72920A75D00E4C858 /* ringlogger.h */, + ); + path = Logging; + sourceTree = ""; + }; + B355DFEA2920A75D00E4C858 /* Model */ = { + isa = PBXGroup; + children = ( + B355DFEB2920A75D00E4C858 /* TunnelConfiguration+WgQuickConfig.swift */, + B355DFEC2920A75D00E4C858 /* NETunnelProviderProtocol+Extension.swift */, + B355DFED2920A75D00E4C858 /* String+ArrayConversion.swift */, + ); + path = Model; + sourceTree = ""; + }; B33FFB8F295F8CF2002259E6 /* Recovered References */ = { isa = PBXGroup; children = ( @@ -2376,6 +2426,16 @@ buildActionMask = 2147483647; files = ( B355DFC329209E2500E4C858 /* AppProxyProvider.swift in Sources */, + B355DFF52920A75E00E4C858 /* NETunnelProviderProtocol+Extension.swift in Sources */, + B355DFF42920A75E00E4C858 /* TunnelConfiguration+WgQuickConfig.swift in Sources */, + B355DFF12920A75E00E4C858 /* test_ringlogger.c in Sources */, + B355DFF92920A79E00E4C858 /* ErrorNotifier.swift in Sources */, + B355DFF02920A75E00E4C858 /* Logger.swift in Sources */, + B355DFEF2920A75E00E4C858 /* ringlogger.c in Sources */, + B355DFF72920A75E00E4C858 /* NotificationToken.swift in Sources */, + B355DFF22920A75E00E4C858 /* Keychain.swift in Sources */, + B355DFF32920A75E00E4C858 /* FileManager+Extension.swift in Sources */, + B355DFF62920A75E00E4C858 /* String+ArrayConversion.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WireguardNetworkExtension/AppProxyProvider.swift b/WireguardNetworkExtension/AppProxyProvider.swift index cdbd39aa..95595ede 100644 --- a/WireguardNetworkExtension/AppProxyProvider.swift +++ b/WireguardNetworkExtension/AppProxyProvider.swift @@ -6,37 +6,128 @@ // Copyright © 2022 Riley Testut. All rights reserved. // +import Foundation import NetworkExtension +import os +import WireGuardKit -class AppProxyProvider: NEAppProxyProvider { +class PacketTunnelProvider: NEPacketTunnelProvider { - override func startProxy(options: [String : Any]? = nil, completionHandler: @escaping (Error?) -> Void) { - // Add code here to start the process of connecting the tunnel. - } - - override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - // Add code here to start the process of stopping the tunnel. - completionHandler() - } - - override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - // Add code here to handle the message. - if let handler = completionHandler { - handler(messageData) + private lazy var adapter: WireGuardAdapter = { + return WireGuardAdapter(with: self) { 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 sleep(completionHandler: @escaping() -> Void) { - // Add code here to get ready to sleep. - completionHandler() + + 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 wake() { - // Add code here to wake up. + + 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) + } } - - override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool { - // Add code here to handle the incoming flow. - return false + +// override func sleep(completionHandler: @escaping() -> Void) { +// // Add code here to get ready to sleep. +// completionHandler() +// } +// +// override func wake() { +// // Add code here to wake up. +// } +} + +extension WireGuardLogLevel { + var osLogLevel: OSLogType { + switch self { + case .verbose: + return .debug + case .error: + return .error + } } } diff --git a/WireguardNetworkExtension/ErrorNotifier.swift b/WireguardNetworkExtension/ErrorNotifier.swift new file mode 100644 index 00000000..a9794549 --- /dev/null +++ b/WireguardNetworkExtension/ErrorNotifier.swift @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import NetworkExtension + +class ErrorNotifier { + let activationAttemptId: String? + + init(activationAttemptId: String?) { + self.activationAttemptId = activationAttemptId + ErrorNotifier.removeLastErrorFile() + } + + func notify(_ error: PacketTunnelProviderError) { + guard let activationAttemptId = activationAttemptId, let lastErrorFilePath = FileManager.networkExtensionLastErrorFileURL?.path else { return } + let errorMessageData = "\(activationAttemptId)\n\(error)".data(using: .utf8) + FileManager.default.createFile(atPath: lastErrorFilePath, contents: errorMessageData, attributes: nil) + } + + static func removeLastErrorFile() { + if let lastErrorFileURL = FileManager.networkExtensionLastErrorFileURL { + _ = FileManager.deleteFile(at: lastErrorFileURL) + } + } +} diff --git a/WireguardNetworkExtension/FileManager+Extension.swift b/WireguardNetworkExtension/FileManager+Extension.swift new file mode 100644 index 00000000..a865b816 --- /dev/null +++ b/WireguardNetworkExtension/FileManager+Extension.swift @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import Foundation +import os.log + +extension FileManager { + static var appGroupId: String? { + #if os(iOS) + let appGroupIdInfoDictionaryKey = "com.wireguard.ios.app_group_id" + #elseif os(macOS) + let appGroupIdInfoDictionaryKey = "com.wireguard.macos.app_group_id" + #else + #error("Unimplemented") + #endif + return Bundle.main.object(forInfoDictionaryKey: appGroupIdInfoDictionaryKey) as? String + } + private static var sharedFolderURL: URL? { + guard let appGroupId = FileManager.appGroupId else { + os_log("Cannot obtain app group ID from bundle", log: OSLog.default, type: .error) + return nil + } + guard let sharedFolderURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else { + wg_log(.error, message: "Cannot obtain shared folder URL") + return nil + } + return sharedFolderURL + } + + static var logFileURL: URL? { + return sharedFolderURL?.appendingPathComponent("tunnel-log.bin") + } + + static var networkExtensionLastErrorFileURL: URL? { + return sharedFolderURL?.appendingPathComponent("last-error.txt") + } + + static var loginHelperTimestampURL: URL? { + return sharedFolderURL?.appendingPathComponent("login-helper-timestamp.bin") + } + + static func deleteFile(at url: URL) -> Bool { + do { + try FileManager.default.removeItem(at: url) + } catch { + return false + } + return true + } +} diff --git a/WireguardNetworkExtension/Keychain.swift b/WireguardNetworkExtension/Keychain.swift new file mode 100644 index 00000000..fb00062f --- /dev/null +++ b/WireguardNetworkExtension/Keychain.swift @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import Foundation +import Security + +class Keychain { + static func openReference(called ref: Data) -> String? { + var result: CFTypeRef? + let ret = SecItemCopyMatching([kSecValuePersistentRef: ref, + kSecReturnData: true] as CFDictionary, + &result) + if ret != errSecSuccess || result == nil { + wg_log(.error, message: "Unable to open config from keychain: \(ret)") + return nil + } + guard let data = result as? Data else { return nil } + return String(data: data, encoding: String.Encoding.utf8) + } + + static func makeReference(containing value: String, called name: String, previouslyReferencedBy oldRef: Data? = nil) -> Data? { + var ret: OSStatus + guard var bundleIdentifier = Bundle.main.bundleIdentifier else { + wg_log(.error, staticMessage: "Unable to determine bundle identifier") + return nil + } + if bundleIdentifier.hasSuffix(".network-extension") { + bundleIdentifier.removeLast(".network-extension".count) + } + let itemLabel = "WireGuard Tunnel: \(name)" + var items: [CFString: Any] = [kSecClass: kSecClassGenericPassword, + kSecAttrLabel: itemLabel, + kSecAttrAccount: name + ": " + UUID().uuidString, + kSecAttrDescription: "wg-quick(8) config", + kSecAttrService: bundleIdentifier, + kSecValueData: value.data(using: .utf8) as Any, + kSecReturnPersistentRef: true] + + #if os(iOS) + items[kSecAttrAccessGroup] = FileManager.appGroupId + items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlock + #elseif os(macOS) + items[kSecAttrSynchronizable] = false + items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + + guard let extensionPath = Bundle.main.builtInPlugInsURL?.appendingPathComponent("WireGuardNetworkExtension.appex", isDirectory: true).path else { + wg_log(.error, staticMessage: "Unable to determine app extension path") + return nil + } + var extensionApp: SecTrustedApplication? + var mainApp: SecTrustedApplication? + ret = SecTrustedApplicationCreateFromPath(extensionPath, &extensionApp) + if ret != kOSReturnSuccess || extensionApp == nil { + wg_log(.error, message: "Unable to create keychain extension trusted application object: \(ret)") + return nil + } + ret = SecTrustedApplicationCreateFromPath(nil, &mainApp) + if ret != errSecSuccess || mainApp == nil { + wg_log(.error, message: "Unable to create keychain local trusted application object: \(ret)") + return nil + } + var access: SecAccess? + ret = SecAccessCreate(itemLabel as CFString, [extensionApp!, mainApp!] as CFArray, &access) + if ret != errSecSuccess || access == nil { + wg_log(.error, message: "Unable to create keychain ACL object: \(ret)") + return nil + } + items[kSecAttrAccess] = access! + #else + #error("Unimplemented") + #endif + + var ref: CFTypeRef? + ret = SecItemAdd(items as CFDictionary, &ref) + if ret != errSecSuccess || ref == nil { + wg_log(.error, message: "Unable to add config to keychain: \(ret)") + return nil + } + if let oldRef = oldRef { + deleteReference(called: oldRef) + } + return ref as? Data + } + + static func deleteReference(called ref: Data) { + let ret = SecItemDelete([kSecValuePersistentRef: ref] as CFDictionary) + if ret != errSecSuccess { + wg_log(.error, message: "Unable to delete config from keychain: \(ret)") + } + } + + static func deleteReferences(except whitelist: Set) { + var result: CFTypeRef? + let ret = SecItemCopyMatching([kSecClass: kSecClassGenericPassword, + kSecAttrService: Bundle.main.bundleIdentifier as Any, + kSecMatchLimit: kSecMatchLimitAll, + kSecReturnPersistentRef: true] as CFDictionary, + &result) + if ret != errSecSuccess || result == nil { + return + } + guard let items = result as? [Data] else { return } + for item in items { + if !whitelist.contains(item) { + deleteReference(called: item) + } + } + } + + static func verifyReference(called ref: Data) -> Bool { + return SecItemCopyMatching([kSecValuePersistentRef: ref] as CFDictionary, + nil) != errSecItemNotFound + } +} diff --git a/WireguardNetworkExtension/Logging/Logger.swift b/WireguardNetworkExtension/Logging/Logger.swift new file mode 100644 index 00000000..089fd109 --- /dev/null +++ b/WireguardNetworkExtension/Logging/Logger.swift @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import Foundation +import os.log + +public class Logger { + enum LoggerError: Error { + case openFailure + } + + static var global: Logger? + + var log: OpaquePointer + var tag: String + + init(tagged tag: String, withFilePath filePath: String) throws { + guard let log = open_log(filePath) else { throw LoggerError.openFailure } + self.log = log + self.tag = tag + } + + deinit { + close_log(self.log) + } + + func log(message: String) { + write_msg_to_log(log, tag, message.trimmingCharacters(in: .newlines)) + } + + func writeLog(to targetFile: String) -> Bool { + return write_log_to_file(targetFile, self.log) == 0 + } + + static func configureGlobal(tagged tag: String, withFilePath filePath: String?) { + if Logger.global != nil { + return + } + guard let filePath = filePath else { + os_log("Unable to determine log destination path. Log will not be saved to file.", log: OSLog.default, type: .error) + return + } + guard let logger = try? Logger(tagged: tag, withFilePath: filePath) else { + os_log("Unable to open log file for writing. Log will not be saved to file.", log: OSLog.default, type: .error) + return + } + Logger.global = logger + var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version" + if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String { + appVersion += " (\(appBuild))" + } + + Logger.global?.log(message: "App version: \(appVersion)") + } +} + +func wg_log(_ type: OSLogType, staticMessage msg: StaticString) { + os_log(msg, log: OSLog.default, type: type) + Logger.global?.log(message: "\(msg)") +} + +func wg_log(_ type: OSLogType, message msg: String) { + os_log("%{public}s", log: OSLog.default, type: type, msg) + Logger.global?.log(message: msg) +} diff --git a/WireguardNetworkExtension/Logging/ringlogger.c b/WireguardNetworkExtension/Logging/ringlogger.c new file mode 100644 index 00000000..0c16cd62 --- /dev/null +++ b/WireguardNetworkExtension/Logging/ringlogger.c @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ringlogger.h" + +enum { + MAX_LOG_LINE_LENGTH = 512, + MAX_LINES = 2048, + MAGIC = 0xabadbeefU +}; + +struct log_line { + atomic_uint_fast64_t time_ns; + char line[MAX_LOG_LINE_LENGTH]; +}; + +struct log { + atomic_uint_fast32_t next_index; + struct log_line lines[MAX_LINES]; + uint32_t magic; +}; + +void write_msg_to_log(struct log *log, const char *tag, const char *msg) +{ + uint32_t index; + struct log_line *line; + struct timespec ts; + + // Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order. + clock_gettime(CLOCK_REALTIME, &ts); + + // Race: More than MAX_LINES writers and this will clash. + index = atomic_fetch_add(&log->next_index, 1); + line = &log->lines[index % MAX_LINES]; + + // Race: Before this line executes, we'll display old data after new data. + atomic_store(&line->time_ns, 0); + memset(line->line, 0, MAX_LOG_LINE_LENGTH); + + snprintf(line->line, MAX_LOG_LINE_LENGTH, "[%s] %s", tag, msg); + atomic_store(&line->time_ns, ts.tv_sec * 1000000000ULL + ts.tv_nsec); + + msync(&log->next_index, sizeof(log->next_index), MS_ASYNC); + msync(line, sizeof(*line), MS_ASYNC); +} + +int write_log_to_file(const char *file_name, const struct log *input_log) +{ + struct log *log; + uint32_t l, i; + FILE *file; + int ret; + + log = malloc(sizeof(*log)); + if (!log) + return -errno; + memcpy(log, input_log, sizeof(*log)); + + file = fopen(file_name, "w"); + if (!file) { + free(log); + return -errno; + } + + for (l = 0, i = log->next_index; l < MAX_LINES; ++l, ++i) { + const struct log_line *line = &log->lines[i % MAX_LINES]; + time_t seconds = line->time_ns / 1000000000ULL; + uint32_t useconds = (line->time_ns % 1000000000ULL) / 1000ULL; + struct tm tm; + + if (!line->time_ns) + continue; + + if (!localtime_r(&seconds, &tm)) + goto err; + + if (fprintf(file, "%04d-%02d-%02d %02d:%02d:%02d.%06d: %s\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, useconds, + line->line) < 0) + goto err; + + + } + errno = 0; + +err: + ret = -errno; + fclose(file); + free(log); + return ret; +} + +uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void *ctx, void(*cb)(const char *, uint64_t, void *)) +{ + struct log *log; + uint32_t l, i = cursor; + + log = malloc(sizeof(*log)); + if (!log) + return cursor; + memcpy(log, input_log, sizeof(*log)); + + if (i == -1) + i = log->next_index; + + for (l = 0; l < MAX_LINES; ++l, ++i) { + const struct log_line *line = &log->lines[i % MAX_LINES]; + + if (cursor != -1 && i % MAX_LINES == log->next_index % MAX_LINES) + break; + + if (!line->time_ns) { + if (cursor == -1) + continue; + else + break; + } + cb(line->line, line->time_ns, ctx); + cursor = (i + 1) % MAX_LINES; + } + free(log); + return cursor; +} + +struct log *open_log(const char *file_name) +{ + int fd; + struct log *log; + + fd = open(file_name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (fd < 0) + return NULL; + if (ftruncate(fd, sizeof(*log))) + goto err; + log = mmap(NULL, sizeof(*log), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (log == MAP_FAILED) + goto err; + close(fd); + + if (log->magic != MAGIC) { + memset(log, 0, sizeof(*log)); + log->magic = MAGIC; + msync(log, sizeof(*log), MS_ASYNC); + } + + return log; + +err: + close(fd); + return NULL; +} + +void close_log(struct log *log) +{ + munmap(log, sizeof(*log)); +} diff --git a/WireguardNetworkExtension/Logging/ringlogger.h b/WireguardNetworkExtension/Logging/ringlogger.h new file mode 100644 index 00000000..4211e9f1 --- /dev/null +++ b/WireguardNetworkExtension/Logging/ringlogger.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#ifndef RINGLOGGER_H +#define RINGLOGGER_H + +#include + +struct log; +void write_msg_to_log(struct log *log, const char *tag, const char *msg); +int write_log_to_file(const char *file_name, const struct log *input_log); +uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void *ctx, void(*)(const char *, uint64_t, void *)); +struct log *open_log(const char *file_name); +void close_log(struct log *log); + +#endif diff --git a/WireguardNetworkExtension/Logging/test_ringlogger.c b/WireguardNetworkExtension/Logging/test_ringlogger.c new file mode 100644 index 00000000..ae3f4a93 --- /dev/null +++ b/WireguardNetworkExtension/Logging/test_ringlogger.c @@ -0,0 +1,63 @@ +#include "ringlogger.h" +#include +#include +#include +#include +#include +#include + +static void forkwrite(void) +{ + struct log *log = open_log("/tmp/test_log"); + char c[512]; + int i, base; + bool in_fork = !fork(); + + base = 10000 * in_fork; + for (i = 0; i < 1024; ++i) { + snprintf(c, 512, "bla bla bla %d", base + i); + write_msg_to_log(log, "HMM", c); + } + + + if (in_fork) + _exit(0); + wait(NULL); + + write_log_to_file("/dev/stdout", log); + close_log(log); +} + +static void writetext(const char *text) +{ + struct log *log = open_log("/tmp/test_log"); + write_msg_to_log(log, "TXT", text); + close_log(log); +} + +static void show_line(const char *line, uint64_t time_ns) +{ + printf("%" PRIu64 ": %s\n", time_ns, line); +} + +static void follow(void) +{ + uint32_t cursor = -1; + struct log *log = open_log("/tmp/test_log"); + + for (;;) { + cursor = view_lines_from_cursor(log, cursor, show_line); + usleep(1000 * 300); + } +} + +int main(int argc, char *argv[]) +{ + if (!strcmp(argv[1], "fork")) + forkwrite(); + else if (!strcmp(argv[1], "write")) + writetext(argv[2]); + else if (!strcmp(argv[1], "follow")) + follow(); + return 0; +} diff --git a/WireguardNetworkExtension/Model/NETunnelProviderProtocol+Extension.swift b/WireguardNetworkExtension/Model/NETunnelProviderProtocol+Extension.swift new file mode 100644 index 00000000..ecb6e1f2 --- /dev/null +++ b/WireguardNetworkExtension/Model/NETunnelProviderProtocol+Extension.swift @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import NetworkExtension + +enum PacketTunnelProviderError: String, Error { + case savedProtocolConfigurationIsInvalid + case dnsResolutionFailure + case couldNotStartBackend + case couldNotDetermineFileDescriptor + case couldNotSetNetworkSettings +} + +extension NETunnelProviderProtocol { + convenience init?(tunnelConfiguration: TunnelConfiguration, previouslyFrom old: NEVPNProtocol? = nil) { + self.init() + + guard let name = tunnelConfiguration.name else { return nil } + guard let appId = Bundle.main.bundleIdentifier else { return nil } + providerBundleIdentifier = "\(appId).network-extension" + passwordReference = Keychain.makeReference(containing: tunnelConfiguration.asWgQuickConfig(), called: name, previouslyReferencedBy: old?.passwordReference) + if passwordReference == nil { + return nil + } + #if os(macOS) + providerConfiguration = ["UID": getuid()] + #endif + + let endpoints = tunnelConfiguration.peers.compactMap { $0.endpoint } + if endpoints.count == 1 { + serverAddress = endpoints[0].stringRepresentation + } else if endpoints.isEmpty { + serverAddress = "Unspecified" + } else { + serverAddress = "Multiple endpoints" + } + } + + func asTunnelConfiguration(called name: String? = nil) -> TunnelConfiguration? { + if let passwordReference = passwordReference, + let config = Keychain.openReference(called: passwordReference) { + return try? TunnelConfiguration(fromWgQuickConfig: config, called: name) + } + if let oldConfig = providerConfiguration?["WgQuickConfig"] as? String { + return try? TunnelConfiguration(fromWgQuickConfig: oldConfig, called: name) + } + return nil + } + + func destroyConfigurationReference() { + guard let ref = passwordReference else { return } + Keychain.deleteReference(called: ref) + } + + func verifyConfigurationReference() -> Bool { + guard let ref = passwordReference else { return false } + return Keychain.verifyReference(called: ref) + } + + @discardableResult + func migrateConfigurationIfNeeded(called name: String) -> Bool { + /* This is how we did things before we switched to putting items + * in the keychain. But it's still useful to keep the migration + * around so that .mobileconfig files are easier. + */ + if let oldConfig = providerConfiguration?["WgQuickConfig"] as? String { + #if os(macOS) + providerConfiguration = ["UID": getuid()] + #elseif os(iOS) + providerConfiguration = nil + #else + #error("Unimplemented") + #endif + guard passwordReference == nil else { return true } + wg_log(.info, message: "Migrating tunnel configuration '\(name)'") + passwordReference = Keychain.makeReference(containing: oldConfig, called: name) + return true + } + #if os(macOS) + if passwordReference != nil && providerConfiguration?["UID"] == nil && verifyConfigurationReference() { + providerConfiguration = ["UID": getuid()] + return true + } + #elseif os(iOS) + if #available(iOS 15, *) { + /* Update the stored reference from the old iOS 14 one to the canonical iOS 15 one. + * The iOS 14 ones are 96 bits, while the iOS 15 ones are 160 bits. We do this so + * that we can have fast set exclusion in deleteReferences safely. */ + if passwordReference != nil && passwordReference!.count == 12 { + var result: CFTypeRef? + let ret = SecItemCopyMatching([kSecValuePersistentRef: passwordReference!, + kSecReturnPersistentRef: true] as CFDictionary, + &result) + if ret != errSecSuccess || result == nil { + return false + } + guard let newReference = result as? Data else { return false } + if !newReference.elementsEqual(passwordReference!) { + wg_log(.info, message: "Migrating iOS 14-style keychain reference to iOS 15-style keychain reference for '\(name)'") + passwordReference = newReference + return true + } + } + } + #endif + return false + } +} diff --git a/WireguardNetworkExtension/Model/String+ArrayConversion.swift b/WireguardNetworkExtension/Model/String+ArrayConversion.swift new file mode 100644 index 00000000..adb959f1 --- /dev/null +++ b/WireguardNetworkExtension/Model/String+ArrayConversion.swift @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import Foundation + +extension String { + + func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] { + return split(separator: separator) + .map { + if let charSet = trimmingCharacters { + return $0.trimmingCharacters(in: charSet) + } else { + return String($0) + } + } + } + +} + +extension Optional where Wrapped == String { + + func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] { + switch self { + case .none: + return [] + case .some(let wrapped): + return wrapped.splitToArray(separator: separator, trimmingCharacters: trimmingCharacters) + } + } + +} diff --git a/WireguardNetworkExtension/Model/TunnelConfiguration+WgQuickConfig.swift b/WireguardNetworkExtension/Model/TunnelConfiguration+WgQuickConfig.swift new file mode 100644 index 00000000..ea8e6ea5 --- /dev/null +++ b/WireguardNetworkExtension/Model/TunnelConfiguration+WgQuickConfig.swift @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import Foundation +import WireGuardKit + +extension TunnelConfiguration { + + enum ParserState { + case inInterfaceSection + case inPeerSection + case notInASection + } + + enum ParseError: Error { + case invalidLine(String.SubSequence) + case noInterface + case multipleInterfaces + case interfaceHasNoPrivateKey + case interfaceHasInvalidPrivateKey(String) + case interfaceHasInvalidListenPort(String) + case interfaceHasInvalidAddress(String) + case interfaceHasInvalidDNS(String) + case interfaceHasInvalidMTU(String) + case interfaceHasUnrecognizedKey(String) + case peerHasNoPublicKey + case peerHasInvalidPublicKey(String) + case peerHasInvalidPreSharedKey(String) + case peerHasInvalidAllowedIP(String) + case peerHasInvalidEndpoint(String) + case peerHasInvalidPersistentKeepAlive(String) + case peerHasInvalidTransferBytes(String) + case peerHasInvalidLastHandshakeTime(String) + case peerHasUnrecognizedKey(String) + case multiplePeersWithSamePublicKey + case multipleEntriesForKey(String) + } + + convenience init(fromWgQuickConfig wgQuickConfig: String, called name: String? = nil) throws { + var interfaceConfiguration: InterfaceConfiguration? + var peerConfigurations = [PeerConfiguration]() + + let lines = wgQuickConfig.split { $0.isNewline } + + var parserState = ParserState.notInASection + var attributes = [String: String]() + + for (lineIndex, line) in lines.enumerated() { + var trimmedLine: String + if let commentRange = line.range(of: "#") { + trimmedLine = String(line[.. = ["privatekey", "listenport", "address", "dns", "mtu"] + let peerSectionKeys: Set = ["publickey", "presharedkey", "allowedips", "endpoint", "persistentkeepalive"] + if parserState == .inInterfaceSection { + guard interfaceSectionKeys.contains(key) else { + throw ParseError.interfaceHasUnrecognizedKey(keyWithCase) + } + } else if parserState == .inPeerSection { + guard peerSectionKeys.contains(key) else { + throw ParseError.peerHasUnrecognizedKey(keyWithCase) + } + } + } else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" { + throw ParseError.invalidLine(line) + } + } + + let isLastLine = lineIndex == lines.count - 1 + + if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" { + // Previous section has ended; process the attributes collected so far + if parserState == .inInterfaceSection { + let interface = try TunnelConfiguration.collate(interfaceAttributes: attributes) + guard interfaceConfiguration == nil else { throw ParseError.multipleInterfaces } + interfaceConfiguration = interface + } else if parserState == .inPeerSection { + let peer = try TunnelConfiguration.collate(peerAttributes: attributes) + peerConfigurations.append(peer) + } + } + + if lowercasedLine == "[interface]" { + parserState = .inInterfaceSection + attributes.removeAll() + } else if lowercasedLine == "[peer]" { + parserState = .inPeerSection + attributes.removeAll() + } + } + + let peerPublicKeysArray = peerConfigurations.map { $0.publicKey } + let peerPublicKeysSet = Set(peerPublicKeysArray) + if peerPublicKeysArray.count != peerPublicKeysSet.count { + throw ParseError.multiplePeersWithSamePublicKey + } + + if let interfaceConfiguration = interfaceConfiguration { + self.init(name: name, interface: interfaceConfiguration, peers: peerConfigurations) + } else { + throw ParseError.noInterface + } + } + + func asWgQuickConfig() -> String { + var output = "[Interface]\n" + output.append("PrivateKey = \(interface.privateKey.base64Key)\n") + if let listenPort = interface.listenPort { + output.append("ListenPort = \(listenPort)\n") + } + if !interface.addresses.isEmpty { + let addressString = interface.addresses.map { $0.stringRepresentation }.joined(separator: ", ") + output.append("Address = \(addressString)\n") + } + if !interface.dns.isEmpty || !interface.dnsSearch.isEmpty { + var dnsLine = interface.dns.map { $0.stringRepresentation } + dnsLine.append(contentsOf: interface.dnsSearch) + let dnsString = dnsLine.joined(separator: ", ") + output.append("DNS = \(dnsString)\n") + } + if let mtu = interface.mtu { + output.append("MTU = \(mtu)\n") + } + + for peer in peers { + output.append("\n[Peer]\n") + output.append("PublicKey = \(peer.publicKey.base64Key)\n") + if let preSharedKey = peer.preSharedKey?.base64Key { + output.append("PresharedKey = \(preSharedKey)\n") + } + if !peer.allowedIPs.isEmpty { + let allowedIPsString = peer.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ") + output.append("AllowedIPs = \(allowedIPsString)\n") + } + if let endpoint = peer.endpoint { + output.append("Endpoint = \(endpoint.stringRepresentation)\n") + } + if let persistentKeepAlive = peer.persistentKeepAlive { + output.append("PersistentKeepalive = \(persistentKeepAlive)\n") + } + } + + return output + } + + private static func collate(interfaceAttributes attributes: [String: String]) throws -> InterfaceConfiguration { + guard let privateKeyString = attributes["privatekey"] else { + throw ParseError.interfaceHasNoPrivateKey + } + guard let privateKey = PrivateKey(base64Key: privateKeyString) else { + throw ParseError.interfaceHasInvalidPrivateKey(privateKeyString) + } + var interface = InterfaceConfiguration(privateKey: privateKey) + if let listenPortString = attributes["listenport"] { + guard let listenPort = UInt16(listenPortString) else { + throw ParseError.interfaceHasInvalidListenPort(listenPortString) + } + interface.listenPort = listenPort + } + if let addressesString = attributes["address"] { + var addresses = [IPAddressRange]() + for addressString in addressesString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { + guard let address = IPAddressRange(from: addressString) else { + throw ParseError.interfaceHasInvalidAddress(addressString) + } + addresses.append(address) + } + interface.addresses = addresses + } + if let dnsString = attributes["dns"] { + var dnsServers = [DNSServer]() + var dnsSearch = [String]() + for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { + if let dnsServer = DNSServer(from: dnsServerString) { + dnsServers.append(dnsServer) + } else { + dnsSearch.append(dnsServerString) + } + } + interface.dns = dnsServers + interface.dnsSearch = dnsSearch + } + if let mtuString = attributes["mtu"] { + guard let mtu = UInt16(mtuString) else { + throw ParseError.interfaceHasInvalidMTU(mtuString) + } + interface.mtu = mtu + } + return interface + } + + private static func collate(peerAttributes attributes: [String: String]) throws -> PeerConfiguration { + guard let publicKeyString = attributes["publickey"] else { + throw ParseError.peerHasNoPublicKey + } + guard let publicKey = PublicKey(base64Key: publicKeyString) else { + throw ParseError.peerHasInvalidPublicKey(publicKeyString) + } + var peer = PeerConfiguration(publicKey: publicKey) + if let preSharedKeyString = attributes["presharedkey"] { + guard let preSharedKey = PreSharedKey(base64Key: preSharedKeyString) else { + throw ParseError.peerHasInvalidPreSharedKey(preSharedKeyString) + } + peer.preSharedKey = preSharedKey + } + if let allowedIPsString = attributes["allowedips"] { + var allowedIPs = [IPAddressRange]() + for allowedIPString in allowedIPsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { + guard let allowedIP = IPAddressRange(from: allowedIPString) else { + throw ParseError.peerHasInvalidAllowedIP(allowedIPString) + } + allowedIPs.append(allowedIP) + } + peer.allowedIPs = allowedIPs + } + if let endpointString = attributes["endpoint"] { + guard let endpoint = Endpoint(from: endpointString) else { + throw ParseError.peerHasInvalidEndpoint(endpointString) + } + peer.endpoint = endpoint + } + if let persistentKeepAliveString = attributes["persistentkeepalive"] { + guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { + throw ParseError.peerHasInvalidPersistentKeepAlive(persistentKeepAliveString) + } + peer.persistentKeepAlive = persistentKeepAlive + } + return peer + } + +} diff --git a/WireguardNetworkExtension/NotificationToken.swift b/WireguardNetworkExtension/NotificationToken.swift new file mode 100644 index 00000000..9eb94ef5 --- /dev/null +++ b/WireguardNetworkExtension/NotificationToken.swift @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import Foundation + +/// This source file contains bits of code from: +/// https://oleb.net/blog/2018/01/notificationcenter-removeobserver/ + +/// Wraps the observer token received from +/// `NotificationCenter.addObserver(forName:object:queue:using:)` +/// and unregisters it in deinit. +final class NotificationToken { + let notificationCenter: NotificationCenter + let token: Any + + init(notificationCenter: NotificationCenter = .default, token: Any) { + self.notificationCenter = notificationCenter + self.token = token + } + + deinit { + notificationCenter.removeObserver(token) + } +} + +extension NotificationCenter { + /// Convenience wrapper for addObserver(forName:object:queue:using:) + /// that returns our custom `NotificationToken`. + func observe(name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NotificationToken { + let token = addObserver(forName: name, object: obj, queue: queue, using: block) + return NotificationToken(notificationCenter: self, token: token) + } +} diff --git a/WireguardNetworkExtension/WireguardNetworkExtension-Bridging-Header.h b/WireguardNetworkExtension/WireguardNetworkExtension-Bridging-Header.h new file mode 100644 index 00000000..0455a564 --- /dev/null +++ b/WireguardNetworkExtension/WireguardNetworkExtension-Bridging-Header.h @@ -0,0 +1,11 @@ +// +// WireguardNetworkExtension-Bridging-Header.h.h +// AltStore +// +// Created by Joseph Mattiello on 11/12/22. +// Copyright © 2022 Riley Testut. All rights reserved. +// + +#include +#include +//#include "ringlogger.h"