2018-10-27 09:32:32 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-12-04 11:15:29 +00:00
|
|
|
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
2018-10-27 09:32:32 +00:00
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import Network
|
2018-11-08 10:14:13 +00:00
|
|
|
import NetworkExtension
|
2020-12-02 15:10:44 +00:00
|
|
|
|
|
|
|
#if SWIFT_PACKAGE
|
2020-12-02 11:32:20 +00:00
|
|
|
import WireGuardKitC
|
2020-12-02 15:10:44 +00:00
|
|
|
#endif
|
2018-10-27 09:32:32 +00:00
|
|
|
|
2020-12-01 10:18:31 +00:00
|
|
|
/// A type alias for `Result` type that holds a tuple with source and resolved endpoint.
|
|
|
|
typealias EndpointResolutionResult = Result<(Endpoint, Endpoint), DNSResolutionError>
|
|
|
|
|
2018-11-08 10:14:13 +00:00
|
|
|
class PacketTunnelSettingsGenerator {
|
|
|
|
let tunnelConfiguration: TunnelConfiguration
|
|
|
|
let resolvedEndpoints: [Endpoint?]
|
2018-10-27 09:32:32 +00:00
|
|
|
|
2018-11-08 10:14:13 +00:00
|
|
|
init(tunnelConfiguration: TunnelConfiguration, resolvedEndpoints: [Endpoint?]) {
|
|
|
|
self.tunnelConfiguration = tunnelConfiguration
|
|
|
|
self.resolvedEndpoints = resolvedEndpoints
|
|
|
|
}
|
2018-10-27 09:32:32 +00:00
|
|
|
|
2020-12-01 10:18:31 +00:00
|
|
|
func endpointUapiConfiguration() -> (String, [EndpointResolutionResult?]) {
|
|
|
|
var resolutionResults = [EndpointResolutionResult?]()
|
2018-12-21 12:02:44 +00:00
|
|
|
var wgSettings = ""
|
2020-12-01 10:18:31 +00:00
|
|
|
|
|
|
|
assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
|
|
|
|
for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) {
|
2020-11-26 16:23:50 +00:00
|
|
|
wgSettings.append("public_key=\(peer.publicKey.hexKey)\n")
|
2020-12-03 14:10:29 +00:00
|
|
|
|
2020-12-01 10:18:31 +00:00
|
|
|
let result = resolvedEndpoint.map(Self.reresolveEndpoint)
|
|
|
|
if case .success((_, let resolvedEndpoint)) = result {
|
|
|
|
if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") }
|
|
|
|
wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n")
|
2018-12-11 22:12:04 +00:00
|
|
|
}
|
2020-12-01 10:18:31 +00:00
|
|
|
resolutionResults.append(result)
|
2018-12-11 22:12:04 +00:00
|
|
|
}
|
2020-12-01 10:18:31 +00:00
|
|
|
|
|
|
|
return (wgSettings, resolutionResults)
|
2018-12-11 22:12:04 +00:00
|
|
|
}
|
2018-12-12 17:40:57 +00:00
|
|
|
|
2020-12-01 10:18:31 +00:00
|
|
|
func uapiConfiguration() -> (String, [EndpointResolutionResult?]) {
|
|
|
|
var resolutionResults = [EndpointResolutionResult?]()
|
2018-10-27 09:32:32 +00:00
|
|
|
var wgSettings = ""
|
2020-11-26 16:23:50 +00:00
|
|
|
wgSettings.append("private_key=\(tunnelConfiguration.interface.privateKey.hexKey)\n")
|
2018-11-08 10:14:13 +00:00
|
|
|
if let listenPort = tunnelConfiguration.interface.listenPort {
|
2018-10-27 09:32:32 +00:00
|
|
|
wgSettings.append("listen_port=\(listenPort)\n")
|
|
|
|
}
|
2018-12-20 17:22:37 +00:00
|
|
|
if !tunnelConfiguration.peers.isEmpty {
|
2018-10-27 09:32:32 +00:00
|
|
|
wgSettings.append("replace_peers=true\n")
|
|
|
|
}
|
2018-11-08 10:14:13 +00:00
|
|
|
assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
|
2020-12-01 10:18:31 +00:00
|
|
|
for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) {
|
2020-11-26 16:23:50 +00:00
|
|
|
wgSettings.append("public_key=\(peer.publicKey.hexKey)\n")
|
|
|
|
if let preSharedKey = peer.preSharedKey?.hexKey {
|
2019-02-07 23:44:14 +00:00
|
|
|
wgSettings.append("preshared_key=\(preSharedKey)\n")
|
2018-10-27 09:32:32 +00:00
|
|
|
}
|
2020-12-01 10:18:31 +00:00
|
|
|
|
|
|
|
let result = resolvedEndpoint.map(Self.reresolveEndpoint)
|
|
|
|
if case .success((_, let resolvedEndpoint)) = result {
|
|
|
|
if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") }
|
|
|
|
wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n")
|
2018-10-27 09:32:32 +00:00
|
|
|
}
|
2020-12-01 10:18:31 +00:00
|
|
|
resolutionResults.append(result)
|
|
|
|
|
2018-10-27 09:32:32 +00:00
|
|
|
let persistentKeepAlive = peer.persistentKeepAlive ?? 0
|
|
|
|
wgSettings.append("persistent_keepalive_interval=\(persistentKeepAlive)\n")
|
2018-12-12 18:28:27 +00:00
|
|
|
if !peer.allowedIPs.isEmpty {
|
2018-10-27 09:32:32 +00:00
|
|
|
wgSettings.append("replace_allowed_ips=true\n")
|
2018-12-21 04:52:45 +00:00
|
|
|
peer.allowedIPs.forEach { wgSettings.append("allowed_ip=\($0.stringRepresentation)\n") }
|
2018-10-27 09:32:32 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-01 10:18:31 +00:00
|
|
|
return (wgSettings, resolutionResults)
|
2018-11-08 10:14:13 +00:00
|
|
|
}
|
2018-10-27 09:32:32 +00:00
|
|
|
|
2018-11-08 10:14:13 +00:00
|
|
|
func generateNetworkSettings() -> NEPacketTunnelNetworkSettings {
|
2018-11-05 05:23:26 +00:00
|
|
|
/* iOS requires a tunnel endpoint, whereas in WireGuard it's valid for
|
|
|
|
* a tunnel to have no endpoint, or for there to be many endpoints, in
|
|
|
|
* which case, displaying a single one in settings doesn't really
|
|
|
|
* make sense. So, we fill it in with this placeholder, which is not
|
|
|
|
* a valid IP address that will actually route over the Internet.
|
|
|
|
*/
|
2019-01-08 00:51:12 +00:00
|
|
|
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
|
2018-12-13 04:26:04 +00:00
|
|
|
|
Kit: PacketTunnelSettingsGenerator: do not require DNS queries if no DNS
Prior, we would set matchDomains=[""] even if the user didn't provide
any DNS servers. This was kind of incoherent, but I guess we had in mind
some kind of non-sensical leakproof scheme that never really worked
anyway. NetworkExtension didn't like this, so setTunnelNetworkSettings
would, rather than return an error, simply timeout and never call its
callback function. But everything worked fine, so we had code in the UI
to check to make sure everything was okay after 5 seconds or so of no
callback. Recent changes made the timeout fatal on the network extension
side, so rather than succeed, configs with no DNS server started
erroring out, causing user reports.
This commit attempts to handle the root cause of the timeout issue by
not twiddling with DNS settings if no DNS server was specified. For now,
however, it leaves the hard-timeout semantics in place.
Reported-by: Filipe Mendonça <cfilipem@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-16 23:10:28 +00:00
|
|
|
if !tunnelConfiguration.interface.dnsSearch.isEmpty || !tunnelConfiguration.interface.dns.isEmpty {
|
|
|
|
let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation }
|
|
|
|
let dnsSettings = NEDNSSettings(servers: dnsServerStrings)
|
|
|
|
dnsSettings.searchDomains = tunnelConfiguration.interface.dnsSearch
|
|
|
|
if !tunnelConfiguration.interface.dns.isEmpty {
|
|
|
|
dnsSettings.matchDomains = [""] // All DNS queries must first go through the tunnel's DNS
|
|
|
|
}
|
|
|
|
networkSettings.dnsSettings = dnsSettings
|
|
|
|
}
|
2018-12-13 04:26:04 +00:00
|
|
|
|
2019-01-15 20:21:50 +00:00
|
|
|
let mtu = tunnelConfiguration.interface.mtu ?? 0
|
2019-01-03 18:24:30 +00:00
|
|
|
|
|
|
|
/* 0 means automatic MTU. In theory, we should just do
|
|
|
|
* `networkSettings.tunnelOverheadBytes = 80` but in
|
|
|
|
* practice there are too many broken networks out there.
|
|
|
|
* Instead set it to 1280. Boohoo. Maybe someday we'll
|
|
|
|
* add a nob, maybe, or iOS will do probing for us.
|
|
|
|
*/
|
2018-12-12 18:28:27 +00:00
|
|
|
if mtu == 0 {
|
2019-01-15 20:21:50 +00:00
|
|
|
#if os(iOS)
|
|
|
|
networkSettings.mtu = NSNumber(value: 1280)
|
2019-01-21 22:36:27 +00:00
|
|
|
#elseif os(macOS)
|
2019-01-15 20:21:50 +00:00
|
|
|
networkSettings.tunnelOverheadBytes = 80
|
2019-01-21 22:36:27 +00:00
|
|
|
#else
|
|
|
|
#error("Unimplemented")
|
2019-01-15 20:21:50 +00:00
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
networkSettings.mtu = NSNumber(value: mtu)
|
2018-11-08 10:14:13 +00:00
|
|
|
}
|
2018-12-13 04:26:04 +00:00
|
|
|
|
2018-12-13 03:09:52 +00:00
|
|
|
let (ipv4Routes, ipv6Routes) = routes()
|
|
|
|
let (ipv4IncludedRoutes, ipv6IncludedRoutes) = includedRoutes()
|
2018-12-13 04:26:04 +00:00
|
|
|
|
2018-12-13 03:09:52 +00:00
|
|
|
let ipv4Settings = NEIPv4Settings(addresses: ipv4Routes.map { $0.destinationAddress }, subnetMasks: ipv4Routes.map { $0.destinationSubnetMask })
|
|
|
|
ipv4Settings.includedRoutes = ipv4IncludedRoutes
|
|
|
|
networkSettings.ipv4Settings = ipv4Settings
|
2018-12-13 04:26:04 +00:00
|
|
|
|
2018-12-13 03:09:52 +00:00
|
|
|
let ipv6Settings = NEIPv6Settings(addresses: ipv6Routes.map { $0.destinationAddress }, networkPrefixLengths: ipv6Routes.map { $0.destinationNetworkPrefixLength })
|
|
|
|
ipv6Settings.includedRoutes = ipv6IncludedRoutes
|
|
|
|
networkSettings.ipv6Settings = ipv6Settings
|
2018-10-27 09:32:32 +00:00
|
|
|
|
2018-12-13 03:09:52 +00:00
|
|
|
return networkSettings
|
|
|
|
}
|
2018-10-27 09:32:32 +00:00
|
|
|
|
2018-12-13 03:09:52 +00:00
|
|
|
private func ipv4SubnetMaskString(of addressRange: IPAddressRange) -> String {
|
|
|
|
let length: UInt8 = addressRange.networkPrefixLength
|
|
|
|
assert(length <= 32)
|
|
|
|
var octets: [UInt8] = [0, 0, 0, 0]
|
|
|
|
let subnetMask: UInt32 = length > 0 ? ~UInt32(0) << (32 - length) : UInt32(0)
|
|
|
|
octets[0] = UInt8(truncatingIfNeeded: subnetMask >> 24)
|
|
|
|
octets[1] = UInt8(truncatingIfNeeded: subnetMask >> 16)
|
|
|
|
octets[2] = UInt8(truncatingIfNeeded: subnetMask >> 8)
|
|
|
|
octets[3] = UInt8(truncatingIfNeeded: subnetMask)
|
|
|
|
return octets.map { String($0) }.joined(separator: ".")
|
|
|
|
}
|
2018-12-13 04:26:04 +00:00
|
|
|
|
2018-12-13 03:09:52 +00:00
|
|
|
private func routes() -> ([NEIPv4Route], [NEIPv6Route]) {
|
|
|
|
var ipv4Routes = [NEIPv4Route]()
|
|
|
|
var ipv6Routes = [NEIPv6Route]()
|
2018-11-08 10:14:13 +00:00
|
|
|
for addressRange in tunnelConfiguration.interface.addresses {
|
2018-12-12 18:28:27 +00:00
|
|
|
if addressRange.address is IPv4Address {
|
2018-12-13 03:09:52 +00:00
|
|
|
ipv4Routes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: ipv4SubnetMaskString(of: addressRange)))
|
2018-12-12 18:28:27 +00:00
|
|
|
} else if addressRange.address is IPv6Address {
|
2018-12-13 03:09:52 +00:00
|
|
|
/* Big fat ugly hack for broken iOS networking stack: the smallest prefix that will have
|
|
|
|
* any effect on iOS is a /120, so we clamp everything above to /120. This is potentially
|
|
|
|
* very bad, if various network parameters were actually relying on that subnet being
|
|
|
|
* intentionally small. TODO: talk about this with upstream iOS devs.
|
|
|
|
*/
|
|
|
|
ipv6Routes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: min(120, addressRange.networkPrefixLength))))
|
2018-10-27 09:32:32 +00:00
|
|
|
}
|
|
|
|
}
|
2018-12-13 03:09:52 +00:00
|
|
|
return (ipv4Routes, ipv6Routes)
|
|
|
|
}
|
2018-12-13 04:26:04 +00:00
|
|
|
|
2018-12-13 03:09:52 +00:00
|
|
|
private func includedRoutes() -> ([NEIPv4Route], [NEIPv6Route]) {
|
|
|
|
var ipv4IncludedRoutes = [NEIPv4Route]()
|
|
|
|
var ipv6IncludedRoutes = [NEIPv6Route]()
|
2018-11-08 10:14:13 +00:00
|
|
|
for peer in tunnelConfiguration.peers {
|
2018-10-27 09:32:32 +00:00
|
|
|
for addressRange in peer.allowedIPs {
|
2018-12-12 18:28:27 +00:00
|
|
|
if addressRange.address is IPv4Address {
|
2018-12-13 03:09:52 +00:00
|
|
|
ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: ipv4SubnetMaskString(of: addressRange)))
|
2018-12-12 18:28:27 +00:00
|
|
|
} else if addressRange.address is IPv6Address {
|
2018-12-13 03:09:52 +00:00
|
|
|
ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength)))
|
2018-10-27 09:32:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-12-13 03:09:52 +00:00
|
|
|
return (ipv4IncludedRoutes, ipv6IncludedRoutes)
|
|
|
|
}
|
2020-12-01 10:18:31 +00:00
|
|
|
|
|
|
|
private class func reresolveEndpoint(endpoint: Endpoint) -> EndpointResolutionResult {
|
|
|
|
return Result { (endpoint, try endpoint.withReresolvedIP()) }
|
|
|
|
.mapError { error -> DNSResolutionError in
|
|
|
|
// swiftlint:disable:next force_cast
|
|
|
|
return error as! DNSResolutionError
|
|
|
|
}
|
|
|
|
}
|
2018-10-27 09:32:32 +00:00
|
|
|
}
|