diff options
| -rw-r--r-- | ios/PacketTunnel/AnyIPEndpoint+DNS64.swift | 6 | ||||
| -rw-r--r-- | ios/PacketTunnel/PacketTunnelProvider.swift | 6 | ||||
| -rw-r--r-- | ios/PacketTunnel/PacketTunnelSettingsGenerator.swift | 26 | ||||
| -rw-r--r-- | ios/PacketTunnel/WireguardCommand.swift | 21 | ||||
| -rw-r--r-- | ios/PacketTunnel/WireguardConfiguration.swift | 45 | ||||
| -rw-r--r-- | ios/PacketTunnel/WireguardDevice.swift | 142 |
6 files changed, 130 insertions, 116 deletions
diff --git a/ios/PacketTunnel/AnyIPEndpoint+DNS64.swift b/ios/PacketTunnel/AnyIPEndpoint+DNS64.swift index 758b731bd8..238db4b253 100644 --- a/ios/PacketTunnel/AnyIPEndpoint+DNS64.swift +++ b/ios/PacketTunnel/AnyIPEndpoint+DNS64.swift @@ -51,12 +51,6 @@ extension AnyIPEndpoint { freeaddrinfo(resultPointer) - if "\(resolvedAddress.ip)" == "\(self.ip)" { - os_log(.debug, "DNS64: mapped %{public}s to itself", "\(resolvedAddress.ip)") - } else { - os_log(.debug, "DNS64: mapped %{public}s to %{public}s", "\(self.ip)", "\(resolvedAddress.ip)") - } - return .success(resolvedAddress) } } diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift index c022f930cf..6ca459ef3b 100644 --- a/ios/PacketTunnel/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider.swift @@ -89,7 +89,11 @@ extension PacketTunnelConfiguration { return WireguardConfiguration( privateKey: tunnelConfig.interface.privateKey, - peers: wireguardPeers + peers: wireguardPeers, + allowedIPs: [ + IPAddressRange(address: IPv4Address.any, networkPrefixLength: 0), + IPAddressRange(address: IPv6Address.any, networkPrefixLength: 0) + ] ) } } diff --git a/ios/PacketTunnel/PacketTunnelSettingsGenerator.swift b/ios/PacketTunnel/PacketTunnelSettingsGenerator.swift index 63c0bad96f..82fca24762 100644 --- a/ios/PacketTunnel/PacketTunnelSettingsGenerator.swift +++ b/ios/PacketTunnel/PacketTunnelSettingsGenerator.swift @@ -29,7 +29,7 @@ struct PacketTunnelSettingsGenerator { private func dnsSettings() -> NEDNSSettings { let serverAddresses = [mullvadEndpoint.ipv4Gateway, mullvadEndpoint.ipv6Gateway] - .map { String(reflecting: $0) } + .map { "\($0)" } let dnsSettings = NEDNSSettings(servers: serverAddresses) @@ -51,14 +51,6 @@ struct PacketTunnelSettingsGenerator { NEIPv4Route.default() // 0.0.0.0/0 ] - let relayAddressRange = IPAddressRange(address: mullvadEndpoint.ipv4Relay.ip, networkPrefixLength: 32) - - ipv4Settings.excludedRoutes = [ - NEIPv4Route( - destinationAddress: "\(relayAddressRange.address)", - subnetMask: ipv4SubnetMaskString(of: relayAddressRange)) - ] - return ipv4Settings } @@ -66,21 +58,21 @@ struct PacketTunnelSettingsGenerator { let interfaceAddresses = tunnelConfiguration.interface.addresses let ipv6AddressRanges = interfaceAddresses.filter { $0.address is IPv6Address } + let addresses = ipv6AddressRanges.map { "\($0.address)" } + + // The smallest prefix that will have any effect on iOS is /120 + let networkPrefixLengths = ipv6AddressRanges + .map { NSNumber(value: min(120, $0.networkPrefixLength)) } + let ipv6Settings = NEIPv6Settings( - addresses: ipv6AddressRanges.map { "\($0.address)" }, - networkPrefixLengths: ipv6AddressRanges.map { NSNumber(value: $0.networkPrefixLength) } + addresses: addresses, + networkPrefixLengths: networkPrefixLengths ) ipv6Settings.includedRoutes = [ NEIPv6Route.default() // ::0 ] - if let ipv6Relay = mullvadEndpoint.ipv6Relay { - ipv6Settings.excludedRoutes = [ - NEIPv6Route(destinationAddress: "\(ipv6Relay.ip)", networkPrefixLength: 128) - ] - } - return ipv6Settings } diff --git a/ios/PacketTunnel/WireguardCommand.swift b/ios/PacketTunnel/WireguardCommand.swift index 86d0bb898f..1ece7883e5 100644 --- a/ios/PacketTunnel/WireguardCommand.swift +++ b/ios/PacketTunnel/WireguardCommand.swift @@ -17,30 +17,18 @@ struct WireguardPeer: Hashable { extension WireguardPeer { func withReresolvedEndpoint() -> Result<WireguardPeer, Error> { - self.endpoint.withReresolvedIP() + return self.endpoint.withReresolvedIP() .map { WireguardPeer(endpoint: $0, publicKey: self.publicKey) } } } -extension WireguardPeer { - - /// Returns a 0.0.0.0/0 for IPv4 and ::0/0 for IPv6 - var anyAllowedIP: IPAddressRange { - switch endpoint { - case .ipv4: - return IPAddressRange(address: IPv4Address.any, networkPrefixLength: 0) - case .ipv6: - return IPAddressRange(address: IPv6Address.any, networkPrefixLength: 0) - } - } -} - enum WireguardCommand { case privateKey(WireguardPrivateKey) case listenPort(UInt16) case replacePeers case peer(WireguardPeer) + case replaceAllowedIPs case allowedIP(IPAddressRange) } @@ -66,6 +54,9 @@ extension WireguardCommand { return ["public_key=\(keyString)", "endpoint=\(endpointString)"] .joined(separator: "\n") + case .replaceAllowedIPs: + return "replace_allowed_ips=true" + case .allowedIP(let ipAddressRange): return "allowed_ip=\(ipAddressRange)" } @@ -75,7 +66,7 @@ extension WireguardCommand { extension Array where Element == WireguardCommand { func toRawWireguardConfigString() -> String { - map { $0.toRawWireguardCommand() } + return map { $0.toRawWireguardCommand() } .joined(separator: "\n") } } diff --git a/ios/PacketTunnel/WireguardConfiguration.swift b/ios/PacketTunnel/WireguardConfiguration.swift index d4a31327bf..2652a3145e 100644 --- a/ios/PacketTunnel/WireguardConfiguration.swift +++ b/ios/PacketTunnel/WireguardConfiguration.swift @@ -6,14 +6,13 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import Combine import Foundation -import os /// A struct describing a basic WireGuard configuration struct WireguardConfiguration { var privateKey: WireguardPrivateKey var peers: [WireguardPeer] + var allowedIPs: [IPAddressRange] } extension WireguardConfiguration { @@ -28,7 +27,10 @@ extension WireguardConfiguration { peers.forEach { (peer) in commands.append(.peer(peer)) - commands.append(.allowedIP(peer.anyAllowedIP)) + } + + allowedIPs.forEach { (ipAddressRange) in + commands.append(.allowedIP(ipAddressRange)) } return commands @@ -44,43 +46,34 @@ extension WireguardConfiguration { let oldPeers = Set(self.peers) let newPeers = Set(newConfig.peers) + let oldPublicKeys = Set(oldPeers.map { $0.publicKey }) + let newPublicKeys = Set(newPeers.map { $0.publicKey }) + let shouldReplacePeers = oldPublicKeys != newPublicKeys if oldPeers != newPeers { - let oldPublicKeys = Set(oldPeers.map { $0.publicKey }) - let newPublicKeys = Set(newPeers.map { $0.publicKey }) - // Avoid using `replace_peers` when updating the existing peers. - if oldPublicKeys != newPublicKeys { + if shouldReplacePeers { commands.append(.replacePeers) } newPeers.forEach { (peer) in commands.append(.peer(peer)) - commands.append(.allowedIP(peer.anyAllowedIP)) } } - return commands - } + let oldAllowedIPs = Set(self.allowedIPs) + let newAllowedIPs = Set(newConfig.allowedIPs) - func withReresolvedPeers(maxRetryOnFailure: Int = 0) -> AnyPublisher<WireguardConfiguration, Error> { - self.peers - .publisher - .setFailureType(to: Error.self) - .flatMap { - $0.withReresolvedEndpoint() - .publisher - .retry(maxRetryOnFailure) + // It looks like the `allowed_ip` table is being flushed when `replace_peers=true` is passed + if oldAllowedIPs != newAllowedIPs || shouldReplacePeers { + commands.append(.replaceAllowedIPs) + newAllowedIPs.forEach { (allowedIP) in + commands.append(.allowedIP(allowedIP)) + } } - .collect() - .map({ (peers) in - WireguardConfiguration( - privateKey: self.privateKey, - peers: peers - ) - }) - .eraseToAnyPublisher() + + return commands } } diff --git a/ios/PacketTunnel/WireguardDevice.swift b/ios/PacketTunnel/WireguardDevice.swift index efb62b526c..9dc8fe4dbb 100644 --- a/ios/PacketTunnel/WireguardDevice.swift +++ b/ios/PacketTunnel/WireguardDevice.swift @@ -33,8 +33,8 @@ class WireguardDevice { /// A failure that indicates that Wireguard has already been started case alreadyStarted - /// A failure to resolve endpoints - case resolveEndpoints(Swift.Error) + /// A failure to resolve an endpoint + case resolveEndpoint(AnyIPEndpoint, Swift.Error) var localizedDescription: String { switch self { @@ -46,8 +46,8 @@ class WireguardDevice { return "Wireguard has not been started yet" case .alreadyStarted: return "Wireguard has already been started" - case .resolveEndpoints(let resolutionError): - return "Failed to resolve endpoints: \(resolutionError.localizedDescription)" + case .resolveEndpoint(let endpoint, let error): + return "Failed to resolve the endpoint: \(endpoint). Error: \(error.localizedDescription)" } } } @@ -75,9 +75,6 @@ class WireguardDevice { /// Network routes monitor private var networkMonitor: NWPathMonitor? - /// A subscriber used when resolving peer addresses - private var peerResolutionSubscriber: AnyCancellable? - /// A tunnel device descriptor private let tunFd: Int32 @@ -85,9 +82,12 @@ class WireguardDevice { /// with the specific Wireguard tunnel. private var wireguardHandle: Int32? - /// An instance of `WireguardConfiguration` + /// Active configuration private var configuration: WireguardConfiguration? + /// Active configuration with resolved endpoints + private var resolvedConfiguration: WireguardConfiguration? + /// Returns a Wireguard version class var version: String { String(cString: wgVersion()) @@ -135,31 +135,28 @@ class WireguardDevice { // MARK: - Public methods - func start(configuration: WireguardConfiguration) -> AnyPublisher<(), Error> { - return Deferred { - Future { (fulfill) in + func start(configuration: WireguardConfiguration) -> Future<(), Error> { + return Future { (fulfill) in + self.workQueue.async { fulfill(self._start(configuration: configuration)) } - }.subscribe(on: workQueue) - .eraseToAnyPublisher() + } } - func stop() -> AnyPublisher<(), Error> { - Deferred { - Future { (fulfill) in + func stop() -> Future<(), Error> { + return Future { (fulfill) in + self.workQueue.async { fulfill(self._stop()) } - }.subscribe(on: workQueue) - .eraseToAnyPublisher() + } } - func setConfig(configuration: WireguardConfiguration) -> AnyPublisher<(), Error> { - Deferred { - Future { (fulfill) in + func setConfig(configuration: WireguardConfiguration) -> Future<(), Error> { + return Future { (fulfill) in + self.workQueue.async { fulfill(self._setConfig(configuration: configuration)) } - }.subscribe(on: workQueue) - .eraseToAnyPublisher() + } } func getInterfaceName() -> String? { @@ -191,14 +188,18 @@ class WireguardDevice { return .failure(.alreadyStarted) } - let handle = configuration.baseline().toRawWireguardConfigString() + let resolvedConfiguration = Self.resolveConfiguration(configuration) + let handle = resolvedConfiguration + .baseline() + .toRawWireguardConfigString() .withCString { wgTurnOn($0, self.tunFd) } if handle < 0 { return .failure(.start(handle)) } else { - wireguardHandle = handle + self.wireguardHandle = handle self.configuration = configuration + self.resolvedConfiguration = resolvedConfiguration startNetworkMonitor() @@ -220,13 +221,17 @@ class WireguardDevice { } } - private func _setConfig(configuration: WireguardConfiguration) -> Result<(), Error> { - if let handle = wireguardHandle, let activeConfiguration = self.configuration { - let wireguardCommands = activeConfiguration.transition(to: configuration) + private func _setConfig(configuration newConfiguration: WireguardConfiguration) -> Result<(), Error> { + if let handle = wireguardHandle, + let oldResolvedConfigration = self.resolvedConfiguration + { + let newResolvedConfiguration = Self.resolveConfiguration(newConfiguration) + let wireguardCommands = oldResolvedConfigration.transition(to: newResolvedConfiguration) Self.setWireguardConfig(handle: handle, commands: wireguardCommands) - self.configuration = configuration + self.configuration = newConfiguration + self.resolvedConfiguration = newResolvedConfiguration return .success(()) } else { @@ -242,6 +247,55 @@ class WireguardDevice { .withCString { wgSetConfig(handle, $0) } } + private class func resolveConfiguration(_ configuration: WireguardConfiguration) + -> WireguardConfiguration + { + return WireguardConfiguration( + privateKey: configuration.privateKey, + peers: resolvePeers(configuration.peers), + allowedIPs: configuration.allowedIPs + ) + } + + private class func resolvePeers(_ peers: [WireguardPeer]) -> [WireguardPeer] { + var newPeers = [WireguardPeer]() + + for peer in peers { + switch self.resolvePeer(peer) { + case .success(let resolvedPeer): + newPeers.append(resolvedPeer) + case .failure(_): + // Fix me: Ignore resolution error and carry on with the last known peer + newPeers.append(peer) + } + } + + return newPeers + } + + private class func resolvePeer(_ peer: WireguardPeer) -> Result<WireguardPeer, Error> { + switch peer.withReresolvedEndpoint() { + case .success(let resolvedPeer): + if "\(peer.endpoint.ip)" == "\(resolvedPeer.endpoint.ip)" { + os_log(.debug, log: wireguardDeviceLog, + "DNS64: mapped %{public}s to itself", "\(resolvedPeer.endpoint.ip)") + } else { + os_log(.debug, log: wireguardDeviceLog, + "DNS64: mapped %{public}s to %{public}s", + "\(peer.endpoint.ip)", "\(resolvedPeer.endpoint.ip)") + } + + return .success(resolvedPeer) + + case .failure(let error): + os_log(.error, log: wireguardDeviceLog, + "Failed to re-resolve the peer: %{public}s. Error: %{public}s", + "\(peer.endpoint.ip)", error.localizedDescription) + + return .failure(.resolveEndpoint(peer.endpoint, error)) + } + } + // MARK: - Network monitoring private func startNetworkMonitor() { @@ -264,30 +318,16 @@ class WireguardDevice { String(describing: path.status), String(describing: path.availableInterfaces)) - // Re-resolve endpoints on network changes and update Wireguard configuration - if let activeConfiguration = self.configuration { - self.peerResolutionSubscriber = activeConfiguration - .withReresolvedPeers(maxRetryOnFailure: 1) - .mapError { WireguardDevice.Error.resolveEndpoints($0) } - .sink(receiveCompletion: { (completion) in - switch completion { - case .finished: - os_log(.debug, log: wireguardDeviceLog, "Re-resolved endpoints") + // Re-resolve endpoints on network changes + if let currentConfiguration = self.configuration, + let oldResolvedConfigration = self.resolvedConfiguration + { + let newResolvedConfiguration = Self.resolveConfiguration(currentConfiguration) + let commands = oldResolvedConfigration.transition(to: newResolvedConfiguration) - case .failure(let error): - os_log(.error, log: wireguardDeviceLog, - "Failed to re-resolve endpoints: %{public}s", - error.localizedDescription) - } - }, receiveValue: { (reresolvedConfiguration) in - let commands = activeConfiguration - .transition(to: reresolvedConfiguration) + Self.setWireguardConfig(handle: handle, commands: commands) - Self.setWireguardConfig( - handle: handle, - commands: commands - ) - }) + self.resolvedConfiguration = newResolvedConfiguration } // Tell Wireguard to re-open sockets and bind them to the new network interface |
