197 lines
8.9 KiB
Swift
197 lines
8.9 KiB
Swift
import Foundation
|
|
import NetworkExtension
|
|
|
|
extension PacketTunnelProvider {
|
|
func startWireguard(activationAttemptId: String?,
|
|
errorNotifier: ErrorNotifier,
|
|
completionHandler: @escaping (Error?) -> Void) {
|
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
|
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
|
wg_log(.error, message: "Can't start, config missing")
|
|
completionHandler(nil)
|
|
return
|
|
}
|
|
|
|
do {
|
|
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
|
let wgConfigStr = wgConfig.str
|
|
wg_log(.info, title: "config: ", message: wgConfig.redux)
|
|
|
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
|
|
|
if tunnelConfiguration.peers.first!.allowedIPs
|
|
.map({ $0.stringRepresentation })
|
|
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
|
if wgConfig.splitTunnelType == 1 {
|
|
for index in tunnelConfiguration.peers.indices {
|
|
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
|
var allowedIPs = [IPAddressRange]()
|
|
|
|
for allowedIPString in wgConfig.splitTunnelSites {
|
|
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
|
allowedIPs.append(allowedIP)
|
|
}
|
|
}
|
|
|
|
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
|
}
|
|
} else if wgConfig.splitTunnelType == 2 {
|
|
for index in tunnelConfiguration.peers.indices {
|
|
var excludeIPs = [IPAddressRange]()
|
|
|
|
for excludeIPString in wgConfig.splitTunnelSites {
|
|
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
|
excludeIPs.append(excludeIP)
|
|
}
|
|
}
|
|
|
|
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
|
}
|
|
}
|
|
}
|
|
|
|
wg_log(.info, message: "Starting tunnel from the " +
|
|
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
|
|
|
// Start the tunnel
|
|
wgAdapter = WireGuardAdapter(with: self) { logLevel, message in
|
|
wg_log(logLevel.osLogLevel, message: message)
|
|
}
|
|
|
|
wgAdapter?.start(tunnelConfiguration: tunnelConfiguration) { [weak self] adapterError in
|
|
guard let adapterError else {
|
|
let interfaceName = self?.wgAdapter?.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:
|
|
fatalError()
|
|
}
|
|
}
|
|
} catch {
|
|
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
|
completionHandler(nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
|
guard let completionHandler = completionHandler else { return }
|
|
wgAdapter?.getRuntimeConfiguration { settings in
|
|
let components = settings!.components(separatedBy: "\n")
|
|
|
|
var settingsDictionary: [String: String] = [:]
|
|
for component in components {
|
|
let pair = component.components(separatedBy: "=")
|
|
if pair.count == 2 {
|
|
settingsDictionary[pair[0]] = pair[1]
|
|
}
|
|
}
|
|
|
|
let lastHandshakeString = settingsDictionary["last_handshake_time_sec"]
|
|
let lastHandshake: Int64
|
|
|
|
if let lastHandshakeValue = lastHandshakeString, let handshakeValue = Int64(lastHandshakeValue) {
|
|
lastHandshake = handshakeValue
|
|
} else {
|
|
lastHandshake = -2 // Return an error if there is no value for `last_handshake_time_sec`
|
|
}
|
|
|
|
let response: [String: Any] = [
|
|
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
|
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0",
|
|
"last_handshake_time_sec": lastHandshake
|
|
]
|
|
|
|
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
|
}
|
|
}
|
|
|
|
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
|
guard let completionHandler = completionHandler else { return }
|
|
if messageData.count == 1 && messageData[0] == 0 {
|
|
wgAdapter?.getRuntimeConfiguration { settings in
|
|
var data: Data?
|
|
if let settings {
|
|
data = settings.data(using: .utf8)!
|
|
}
|
|
completionHandler(data)
|
|
}
|
|
} else if messageData.count >= 1 {
|
|
// Updates the tunnel configuration and responds with the active configuration
|
|
wg_log(.info, message: "Switching tunnel configuration")
|
|
guard let configString = String(data: messageData, encoding: .utf8)
|
|
else {
|
|
completionHandler(nil)
|
|
return
|
|
}
|
|
|
|
do {
|
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
|
wgAdapter?.update(tunnelConfiguration: tunnelConfiguration) { [weak self] error in
|
|
if let error {
|
|
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
|
completionHandler(nil)
|
|
return
|
|
}
|
|
|
|
self?.wgAdapter?.getRuntimeConfiguration { settings in
|
|
var data: Data?
|
|
if let settings {
|
|
data = settings.data(using: .utf8)!
|
|
}
|
|
completionHandler(data)
|
|
}
|
|
}
|
|
} catch {
|
|
completionHandler(nil)
|
|
}
|
|
} else {
|
|
completionHandler(nil)
|
|
}
|
|
}
|
|
|
|
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
|
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
|
|
|
wgAdapter?.stop { error in
|
|
ErrorNotifier.removeLastErrorFile()
|
|
|
|
if let 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
|
|
}
|
|
}
|
|
}
|