mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-20 04:03:26 +01:00
WireGuard add extra source files
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
This commit is contained in:
@@ -28,6 +28,16 @@
|
|||||||
B355DFC329209E2500E4C858 /* AppProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B355DFC229209E2500E4C858 /* AppProxyProvider.swift */; };
|
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, ); }; };
|
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 */; };
|
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 */; };
|
B33FFBA8295F8E98002259E6 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B343F894295F7F9B002B1159 /* libfragmentzip.a */; };
|
||||||
B33FFBAA295F8F78002259E6 /* preboard.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBA9295F8F78002259E6 /* preboard.c */; };
|
B33FFBAA295F8F78002259E6 /* preboard.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBA9295F8F78002259E6 /* preboard.c */; };
|
||||||
B33FFBAC295F8F98002259E6 /* companion_proxy.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBAB295F8F98002259E6 /* companion_proxy.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 = "<group>"; };
|
B355DFC229209E2500E4C858 /* AppProxyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppProxyProvider.swift; sourceTree = "<group>"; };
|
||||||
B355DFC429209E2500E4C858 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
B355DFC429209E2500E4C858 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
B355DFC529209E2500E4C858 /* WireguardNetworkExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireguardNetworkExtension.entitlements; sourceTree = "<group>"; };
|
B355DFC529209E2500E4C858 /* WireguardNetworkExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireguardNetworkExtension.entitlements; sourceTree = "<group>"; };
|
||||||
|
B355DFE22920A6C200E4C858 /* WireguardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireguardNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
B355DFE42920A75D00E4C858 /* ringlogger.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ringlogger.c; sourceTree = "<group>"; };
|
||||||
|
B355DFE52920A75D00E4C858 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||||
|
B355DFE62920A75D00E4C858 /* test_ringlogger.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = test_ringlogger.c; sourceTree = "<group>"; };
|
||||||
|
B355DFE72920A75D00E4C858 /* ringlogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ringlogger.h; sourceTree = "<group>"; };
|
||||||
|
B355DFE82920A75D00E4C858 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||||
|
B355DFE92920A75D00E4C858 /* FileManager+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = "<group>"; };
|
||||||
|
B355DFEB2920A75D00E4C858 /* TunnelConfiguration+WgQuickConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+WgQuickConfig.swift"; sourceTree = "<group>"; };
|
||||||
|
B355DFEC2920A75D00E4C858 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; };
|
||||||
|
B355DFED2920A75D00E4C858 /* String+ArrayConversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+ArrayConversion.swift"; sourceTree = "<group>"; };
|
||||||
|
B355DFEE2920A75E00E4C858 /* NotificationToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationToken.swift; sourceTree = "<group>"; };
|
||||||
|
B355DFF82920A79E00E4C858 /* ErrorNotifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; };
|
||||||
B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = "<group>"; };
|
B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = "<group>"; };
|
||||||
B33FFBAB295F8F98002259E6 /* companion_proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = companion_proxy.c; path = src/companion_proxy.c; sourceTree = "<group>"; };
|
B33FFBAB295F8F98002259E6 /* companion_proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = companion_proxy.c; path = src/companion_proxy.c; sourceTree = "<group>"; };
|
||||||
B343F847295F6321002B1159 /* minimuxer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = minimuxer.xcodeproj; path = Dependencies/minimuxer.xcodeproj; sourceTree = SOURCE_ROOT; };
|
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 */ = {
|
B355DFC129209E2500E4C858 /* WireguardNetworkExtension */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B355DFC229209E2500E4C858 /* AppProxyProvider.swift */,
|
|
||||||
B355DFC429209E2500E4C858 /* Info.plist */,
|
|
||||||
B355DFC529209E2500E4C858 /* WireguardNetworkExtension.entitlements */,
|
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;
|
path = WireguardNetworkExtension;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
B355DFE32920A75D00E4C858 /* Logging */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
B355DFE42920A75D00E4C858 /* ringlogger.c */,
|
||||||
|
B355DFE52920A75D00E4C858 /* Logger.swift */,
|
||||||
|
B355DFE62920A75D00E4C858 /* test_ringlogger.c */,
|
||||||
|
B355DFE72920A75D00E4C858 /* ringlogger.h */,
|
||||||
|
);
|
||||||
|
path = Logging;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
B355DFEA2920A75D00E4C858 /* Model */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
B355DFEB2920A75D00E4C858 /* TunnelConfiguration+WgQuickConfig.swift */,
|
||||||
|
B355DFEC2920A75D00E4C858 /* NETunnelProviderProtocol+Extension.swift */,
|
||||||
|
B355DFED2920A75D00E4C858 /* String+ArrayConversion.swift */,
|
||||||
|
);
|
||||||
|
path = Model;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
B33FFB8F295F8CF2002259E6 /* Recovered References */ = {
|
B33FFB8F295F8CF2002259E6 /* Recovered References */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2376,6 +2426,16 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
B355DFC329209E2500E4C858 /* AppProxyProvider.swift in Sources */,
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,37 +6,128 @@
|
|||||||
// Copyright © 2022 Riley Testut. All rights reserved.
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
|
import os
|
||||||
|
import WireGuardKit
|
||||||
|
|
||||||
class AppProxyProvider: NEAppProxyProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
override func startProxy(options: [String : Any]? = nil, completionHandler: @escaping (Error?) -> Void) {
|
private lazy var adapter: WireGuardAdapter = {
|
||||||
// Add code here to start the process of connecting the tunnel.
|
return WireGuardAdapter(with: self) { logLevel, message in
|
||||||
}
|
wg_log(logLevel.osLogLevel, message: message)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||||
// Add code here to start the process of stopping the tunnel.
|
let activationAttemptId = options?["activationAttemptId"] as? String
|
||||||
completionHandler()
|
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
||||||
}
|
|
||||||
|
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
Logger.configureGlobal(tagged: "NET", withFilePath: FileManager.logFileURL?.path)
|
||||||
// Add code here to handle the message.
|
|
||||||
if let handler = completionHandler {
|
wg_log(.info, message: "Starting tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||||
handler(messageData)
|
|
||||||
|
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) {
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
// Add code here to get ready to sleep.
|
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||||
completionHandler()
|
|
||||||
|
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() {
|
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
// Add code here to wake up.
|
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 {
|
// override func sleep(completionHandler: @escaping() -> Void) {
|
||||||
// Add code here to handle the incoming flow.
|
// // Add code here to get ready to sleep.
|
||||||
return false
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
WireguardNetworkExtension/ErrorNotifier.swift
Normal file
25
WireguardNetworkExtension/ErrorNotifier.swift
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
WireguardNetworkExtension/FileManager+Extension.swift
Normal file
50
WireguardNetworkExtension/FileManager+Extension.swift
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
114
WireguardNetworkExtension/Keychain.swift
Normal file
114
WireguardNetworkExtension/Keychain.swift
Normal file
@@ -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<Data>) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
65
WireguardNetworkExtension/Logging/Logger.swift
Normal file
65
WireguardNetworkExtension/Logging/Logger.swift
Normal file
@@ -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)
|
||||||
|
}
|
||||||
173
WireguardNetworkExtension/Logging/ringlogger.c
Normal file
173
WireguardNetworkExtension/Logging/ringlogger.c
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#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));
|
||||||
|
}
|
||||||
18
WireguardNetworkExtension/Logging/ringlogger.h
Normal file
18
WireguardNetworkExtension/Logging/ringlogger.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RINGLOGGER_H
|
||||||
|
#define RINGLOGGER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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
|
||||||
63
WireguardNetworkExtension/Logging/test_ringlogger.c
Normal file
63
WireguardNetworkExtension/Logging/test_ringlogger.c
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#include "ringlogger.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
32
WireguardNetworkExtension/Model/String+ArrayConversion.swift
Normal file
32
WireguardNetworkExtension/Model/String+ArrayConversion.swift
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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[..<commentRange.lowerBound])
|
||||||
|
} else {
|
||||||
|
trimmedLine = String(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let lowercasedLine = trimmedLine.lowercased()
|
||||||
|
|
||||||
|
if !trimmedLine.isEmpty {
|
||||||
|
if let equalsIndex = trimmedLine.firstIndex(of: "=") {
|
||||||
|
// Line contains an attribute
|
||||||
|
let keyWithCase = trimmedLine[..<equalsIndex].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let key = keyWithCase.lowercased()
|
||||||
|
let value = trimmedLine[trimmedLine.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let keysWithMultipleEntriesAllowed: Set<String> = ["address", "allowedips", "dns"]
|
||||||
|
if let presentValue = attributes[key] {
|
||||||
|
if keysWithMultipleEntriesAllowed.contains(key) {
|
||||||
|
attributes[key] = presentValue + "," + value
|
||||||
|
} else {
|
||||||
|
throw ParseError.multipleEntriesForKey(keyWithCase)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attributes[key] = value
|
||||||
|
}
|
||||||
|
let interfaceSectionKeys: Set<String> = ["privatekey", "listenport", "address", "dns", "mtu"]
|
||||||
|
let peerSectionKeys: Set<String> = ["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<PublicKey>(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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
WireguardNetworkExtension/NotificationToken.swift
Normal file
33
WireguardNetworkExtension/NotificationToken.swift
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <WireGuardKitC/WireGuardKitC.h>
|
||||||
|
#include <WireGuardKitGo/wireguard.h>
|
||||||
|
//#include "ringlogger.h"
|
||||||
Reference in New Issue
Block a user