diff --git a/Sources/BrowserServicesKit/InternalUserDecider/UserDefaults+isInternalUser.swift b/Sources/BrowserServicesKit/InternalUserDecider/UserDefaults+isInternalUser.swift new file mode 100644 index 000000000..a40e0887c --- /dev/null +++ b/Sources/BrowserServicesKit/InternalUserDecider/UserDefaults+isInternalUser.swift @@ -0,0 +1,46 @@ +// +// UserDefaults+isInternalUser.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation + +extension UserDefaults: InternalUserStoring { + @objc + public dynamic var isInternalUser: Bool { + get { + bool(forKey: #keyPath(isInternalUser)) + } + + set { + guard newValue != bool(forKey: #keyPath(isInternalUser)) else { + return + } + + guard newValue else { + removeObject(forKey: #keyPath(isInternalUser)) + return + } + + set(newValue, forKey: #keyPath(isInternalUser)) + } + } + + var internalUserPublisher: AnyPublisher { + publisher(for: \.isInternalUser).eraseToAnyPublisher() + } +} diff --git a/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift b/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift index f21c9dc12..1d4ebbf46 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift @@ -120,6 +120,10 @@ public enum NetworkProtectionSubfeature: String, Equatable, PrivacySubfeature { /// Display user tips for Network Protection /// https://app.asana.com/0/72649045549333/1208231259093710/f case userTips + + /// Enforce routes for the VPN to fix TunnelVision + /// https://app.asana.com/0/72649045549333/1208617860225199/f + case enforceRoutes } public enum SyncSubfeature: String, PrivacySubfeature { diff --git a/Sources/NetworkProtection/Controllers/TunnelController.swift b/Sources/NetworkProtection/Controllers/TunnelController.swift index be56206de..abc4d50be 100644 --- a/Sources/NetworkProtection/Controllers/TunnelController.swift +++ b/Sources/NetworkProtection/Controllers/TunnelController.swift @@ -33,6 +33,10 @@ public protocol TunnelController { /// func stop() async + /// Sends a command to the adapter + /// + func command(_ command: VPNCommand) async throws + /// Whether the tunnel is connected /// var isConnected: Bool { get async } diff --git a/Sources/NetworkProtection/Models/AnyIPAddress.swift b/Sources/NetworkProtection/Models/AnyIPAddress.swift index 6677e0833..10e997813 100644 --- a/Sources/NetworkProtection/Models/AnyIPAddress.swift +++ b/Sources/NetworkProtection/Models/AnyIPAddress.swift @@ -19,7 +19,7 @@ import Foundation import Network -public enum AnyIPAddress: IPAddress, Hashable, CustomDebugStringConvertible, @unchecked Sendable { +public enum AnyIPAddress: Hashable, CustomDebugStringConvertible, @unchecked Sendable { /// A host specified as an IPv4 address case ipv4(IPv4Address) @@ -68,7 +68,7 @@ public enum AnyIPAddress: IPAddress, Hashable, CustomDebugStringConvertible, @un } } - private var ipAddress: IPAddress { + public var ipAddress: IPAddress { switch self { case .ipv4(let ip): return ip diff --git a/Sources/NetworkProtection/NetworkProtectionDeviceManager.swift b/Sources/NetworkProtection/NetworkProtectionDeviceManager.swift index 762ad1212..a599e150a 100644 --- a/Sources/NetworkProtection/NetworkProtectionDeviceManager.swift +++ b/Sources/NetworkProtection/NetworkProtectionDeviceManager.swift @@ -65,10 +65,8 @@ public protocol NetworkProtectionDeviceManagement { typealias GenerateTunnelConfigurationResult = (tunnelConfiguration: TunnelConfiguration, server: NetworkProtectionServer) func generateTunnelConfiguration(resolvedSelectionMethod: NetworkProtectionServerSelectionMethod, - includedRoutes: [IPAddressRange], - excludedRoutes: [IPAddressRange], + excludeLocalNetworks: Bool, dnsSettings: NetworkProtectionDNSSettings, - isKillSwitchEnabled: Bool, regenerateKey: Bool) async throws -> GenerateTunnelConfigurationResult } @@ -129,10 +127,8 @@ public actor NetworkProtectionDeviceManager: NetworkProtectionDeviceManagement { /// 3. If the key already existed, look up the stored set of backend servers and check if the preferred server is registered. If not, register it, and return the tunnel configuration + server info. /// public func generateTunnelConfiguration(resolvedSelectionMethod: NetworkProtectionServerSelectionMethod, - includedRoutes: [IPAddressRange], - excludedRoutes: [IPAddressRange], + excludeLocalNetworks: Bool, dnsSettings: NetworkProtectionDNSSettings, - isKillSwitchEnabled: Bool, regenerateKey: Bool) async throws -> GenerateTunnelConfigurationResult { var keyPair: KeyPair @@ -168,10 +164,8 @@ public actor NetworkProtectionDeviceManager: NetworkProtectionDeviceManagement { do { let configuration = try tunnelConfiguration(interfacePrivateKey: keyPair.privateKey, server: selectedServer, - includedRoutes: includedRoutes, - excludedRoutes: excludedRoutes, - dnsSettings: dnsSettings, - isKillSwitchEnabled: isKillSwitchEnabled) + excludeLocalNetworks: excludeLocalNetworks, + dnsSettings: dnsSettings) return (configuration, selectedServer) } catch let error as NetworkProtectionError { errorEvents?.fire(error) @@ -259,10 +253,8 @@ public actor NetworkProtectionDeviceManager: NetworkProtectionDeviceManagement { func tunnelConfiguration(interfacePrivateKey: PrivateKey, server: NetworkProtectionServer, - includedRoutes: [IPAddressRange], - excludedRoutes: [IPAddressRange], - dnsSettings: NetworkProtectionDNSSettings, - isKillSwitchEnabled: Bool) throws -> TunnelConfiguration { + excludeLocalNetworks: Bool, + dnsSettings: NetworkProtectionDNSSettings) throws -> TunnelConfiguration { guard let allowedIPs = server.allowedIPs else { throw NetworkProtectionError.noServerRegistrationInfo @@ -285,21 +277,30 @@ public actor NetworkProtectionDeviceManager: NetworkProtectionDeviceManagement { let dns: [DNSServer] switch dnsSettings { case .default: - dns = [DNSServer(address: server.serverInfo.internalIP)] + dns = [DNSServer(address: server.serverInfo.internalIP.ipAddress)] case .custom(let servers): dns = servers .compactMap { IPv4Address($0) } .map { DNSServer(address: $0) } } - let interface = interfaceConfiguration(privateKey: interfacePrivateKey, - addressRange: interfaceAddressRange, - includedRoutes: includedRoutes, - excludedRoutes: excludedRoutes, - dns: dns, - isKillSwitchEnabled: isKillSwitchEnabled) + let routingTableResolver = VPNRoutingTableResolver( + dnsServers: dns, + excludeLocalNetworks: excludeLocalNetworks) - return TunnelConfiguration(name: "DuckDuckGo VPN", interface: interface, peers: [peerConfiguration]) + Logger.networkProtection.log("Routing table information:\nL Included Routes: \(routingTableResolver.includedRoutes, privacy: .public)\nL Excluded Routes: \(routingTableResolver.excludedRoutes, privacy: .public)") + + let interface = InterfaceConfiguration(privateKey: interfacePrivateKey, + addresses: [interfaceAddressRange], + includedRoutes: routingTableResolver.includedRoutes, + excludedRoutes: routingTableResolver.excludedRoutes, + dns: dns) + + let tunnelConfiguration = TunnelConfiguration(name: "DuckDuckGo VPN", interface: interface, peers: [peerConfiguration]) + + Logger.networkProtection.log("Tunnel configuration routing information:\nL Included Routes: \(tunnelConfiguration.interface.includedRoutes, privacy: .public)\nL Excluded Routes: \(tunnelConfiguration.interface.excludedRoutes, privacy: .public)") + + return tunnelConfiguration } func peerConfiguration(serverPublicKey: PublicKey, serverEndpoint: Endpoint) -> PeerConfiguration { @@ -311,25 +312,6 @@ public actor NetworkProtectionDeviceManager: NetworkProtectionDeviceManagement { return peerConfiguration } - func interfaceConfiguration(privateKey: PrivateKey, - addressRange: IPAddressRange, - includedRoutes: [IPAddressRange], - excludedRoutes: [IPAddressRange], - dns: [DNSServer], - isKillSwitchEnabled: Bool) -> InterfaceConfiguration { - var includedRoutes = includedRoutes - // Tunnel doesn‘t work with ‘enforceRoutes‘ option when DNS IP/addressRange is in includedRoutes - if !isKillSwitchEnabled { - includedRoutes.append(contentsOf: dns.map { IPAddressRange(address: $0.address, networkPrefixLength: 32) }) - includedRoutes.append(addressRange) - } - return InterfaceConfiguration(privateKey: privateKey, - addresses: [addressRange], - includedRoutes: includedRoutes, - excludedRoutes: excludedRoutes, - dns: dns) - } - private func handle(clientError: NetworkProtectionClientError) { #if os(macOS) if case .invalidAuthToken = clientError { diff --git a/Sources/NetworkProtection/NetworkProtectionOptionKey.swift b/Sources/NetworkProtection/NetworkProtectionOptionKey.swift index 54a15a7f8..660b6368f 100644 --- a/Sources/NetworkProtection/NetworkProtectionOptionKey.swift +++ b/Sources/NetworkProtection/NetworkProtectionOptionKey.swift @@ -24,12 +24,13 @@ public enum NetworkProtectionOptionKey { public static let selectedServer = "selectedServer" public static let selectedLocation = "selectedLocation" public static let dnsSettings = "dnsSettings" + public static let excludeLocalNetworks = "excludeLocalNetworks" public static let authToken = "authToken" public static let isOnDemand = "is-on-demand" public static let activationAttemptId = "activationAttemptId" public static let tunnelFailureSimulation = "tunnelFailureSimulation" public static let tunnelFatalErrorCrashSimulation = "tunnelFatalErrorCrashSimulation" public static let tunnelMemoryCrashSimulation = "tunnelMemoryCrashSimulation" - public static let includedRoutes = "includedRoutes" public static let connectionTesterEnabled = "connectionTesterEnabled" + public static let settings = "settings" } diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index ba1718355..f1a8ba409 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -222,7 +222,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { // MARK: - Tunnel Settings - private let settings: VPNSettings + public let settings: VPNSettings // MARK: - User Defaults @@ -253,8 +253,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { public let lastSelectedServerInfoPublisher = CurrentValueSubject(nil) - private var includedRoutes: [IPAddressRange]? - // MARK: - User Notifications private let notificationsPresenter: NetworkProtectionNotificationsPresenter @@ -518,7 +516,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } } - private func load(options: StartupOptions) throws { + open func load(options: StartupOptions) throws { loadKeyValidity(from: options) loadSelectedEnvironment(from: options) loadSelectedServer(from: options) @@ -531,9 +529,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } open func loadVendorOptions(from provider: NETunnelProviderProtocol?) throws { - let vendorOptions = provider?.providerConfiguration - - loadRoutes(from: vendorOptions) + // no-op, but can be overridden by subclasses } private func loadKeyValidity(from options: StartupOptions) { @@ -624,10 +620,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } #endif - private func loadRoutes(from options: [String: Any]?) { - self.includedRoutes = (options?[NetworkProtectionOptionKey.includedRoutes] as? [String])?.compactMap(IPAddressRange.init(from:)) ?? [] - } - // MARK: - Observing Changes private func observeSettingChanges() { @@ -635,14 +627,14 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { .receive(on: DispatchQueue.main) .sink { [weak self] change in guard let self else { return } - let handleSettingsChange = handleSettingsChange - let subscriptionAccessErrorHandler = subscriptionAccessErrorHandler + + Logger.networkProtection.log("🔵 Settings changed: \(String(describing: change), privacy: .public)") Task { @MainActor in do { - try await handleSettingsChange(change) + try await self.handleSettingsChange(change) } catch { - await subscriptionAccessErrorHandler(error) + await self.subscriptionAccessErrorHandler(error) throw error } } @@ -687,7 +679,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { do { try load(options: startupOptions) - try loadVendorOptions(from: tunnelProviderProtocol) if (try? tokenStore.fetchToken()) == nil { throw TunnelError.startingTunnelWithoutAuthToken @@ -780,14 +771,13 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { private func startTunnel(onDemand: Bool) async throws { do { Logger.networkProtection.log("Generating tunnel config") - Logger.networkProtection.log("Excluded ranges are: \(String(describing: self.settings.excludedRanges), privacy: .public)") Logger.networkProtection.log("Server selection method: \(self.currentServerSelectionMethod.debugDescription, privacy: .public)") Logger.networkProtection.log("DNS server: \(String(describing: self.settings.dnsSettings), privacy: .public)") - let tunnelConfiguration = try await generateTunnelConfiguration(serverSelectionMethod: currentServerSelectionMethod, - includedRoutes: includedRoutes ?? [], - excludedRoutes: settings.excludedRanges, - dnsSettings: settings.dnsSettings, - regenerateKey: true) + let tunnelConfiguration = try await generateTunnelConfiguration( + serverSelectionMethod: currentServerSelectionMethod, + dnsSettings: settings.dnsSettings, + regenerateKey: true) + try await startTunnel(with: tunnelConfiguration, onDemand: onDemand) Logger.networkProtection.log("Done generating tunnel config") } catch { @@ -952,11 +942,10 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { switch updateMethod { case .selectServer(let serverSelectionMethod): - tunnelConfiguration = try await generateTunnelConfiguration(serverSelectionMethod: serverSelectionMethod, - includedRoutes: includedRoutes ?? [], - excludedRoutes: settings.excludedRanges, - dnsSettings: settings.dnsSettings, - regenerateKey: regenerateKey) + tunnelConfiguration = try await generateTunnelConfiguration( + serverSelectionMethod: serverSelectionMethod, + dnsSettings: settings.dnsSettings, + regenerateKey: regenerateKey) case .useConfiguration(let newTunnelConfiguration): tunnelConfiguration = newTunnelConfiguration @@ -1008,8 +997,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { @MainActor private func generateTunnelConfiguration(serverSelectionMethod: NetworkProtectionServerSelectionMethod, - includedRoutes: [IPAddressRange], - excludedRoutes: [IPAddressRange], dnsSettings: NetworkProtectionDNSSettings, regenerateKey: Bool) async throws -> TunnelConfiguration { @@ -1019,10 +1006,8 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { do { configurationResult = try await deviceManager.generateTunnelConfiguration( resolvedSelectionMethod: resolvedServerSelectionMethod, - includedRoutes: includedRoutes, - excludedRoutes: excludedRoutes, + excludeLocalNetworks: settings.excludeLocalNetworks, dnsSettings: dnsSettings, - isKillSwitchEnabled: isKillSwitchEnabled, regenerateKey: regenerateKey ) } catch { @@ -1037,7 +1022,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { self.lastSelectedServer = newSelectedServer Logger.networkProtection.log("⚪️ Generated tunnel configuration for server at location: \(newSelectedServer.serverInfo.serverLocation, privacy: .public) (preferred server is \(newSelectedServer.serverInfo.name, privacy: .public))") - Logger.networkProtection.log("Excluded routes: \(String(describing: excludedRoutes), privacy: .public)") return configurationResult.tunnelConfiguration } @@ -1093,8 +1077,9 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { case .setExcludedRoutes: // No longer supported, will remove, but keeping the enum to prevent ABI issues completionHandler?(nil) - case .setIncludedRoutes(let includedRoutes): - setIncludedRoutes(includedRoutes, completionHandler: completionHandler) + case .setIncludedRoutes: + // No longer supported, will remove, but keeping the enum to prevent ABI issues + completionHandler?(nil) case .simulateTunnelFailure: simulateTunnelFailure(completionHandler: completionHandler) case .simulateTunnelFatalError: @@ -1131,12 +1116,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { @MainActor private func handleSettingsChange(_ change: VPNSettings.Change) async throws { switch change { - case .setExcludeLocalNetworks: - if case .connected = connectionStatus { - try await updateTunnelConfiguration( - updateMethod: .selectServer(currentServerSelectionMethod), - reassert: false) - } case .setSelectedServer(let selectedServer): let serverSelectionMethod: NetworkProtectionServerSelectionMethod @@ -1167,21 +1146,20 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { updateMethod: .selectServer(serverSelectionMethod), reassert: true) } - case .setDNSSettings: - if case .connected = connectionStatus { - try? await updateTunnelConfiguration( - updateMethod: .selectServer(currentServerSelectionMethod), - reassert: true) - } case .setConnectOnLogin, - .setIncludeAllNetworks, + .setDNSSettings, .setEnforceRoutes, + .setExcludeLocalNetworks, + .setIncludeAllNetworks, .setNotifyStatusChanges, .setRegistrationKeyValidity, .setSelectedEnvironment, .setShowInMenuBar, .setDisableRekeying: - // Intentional no-op, as some setting changes don't require any further operation + // Intentional no-op + // Some of these don't require further action + // Some may require an adapter restart, but it's best if that's taken care of by + // the app that's coordinating the updates. break } } @@ -1236,19 +1214,21 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } } + private func handleRestartAdapter() async throws { + let tunnelConfiguration = try await generateTunnelConfiguration( + serverSelectionMethod: currentServerSelectionMethod, + dnsSettings: settings.dnsSettings, + regenerateKey: false) + + try await updateTunnelConfiguration(updateMethod: .useConfiguration(tunnelConfiguration), + reassert: false, + regenerateKey: false) + } + private func handleRestartAdapter(completionHandler: ((Data?) -> Void)? = nil) { Task { do { - let tunnelConfiguration = try await generateTunnelConfiguration(serverSelectionMethod: currentServerSelectionMethod, - includedRoutes: includedRoutes ?? [], - excludedRoutes: settings.excludedRanges, - dnsSettings: settings.dnsSettings, - regenerateKey: false) - - try await updateTunnelConfiguration(updateMethod: .useConfiguration(tunnelConfiguration), - reassert: false, - regenerateKey: false) - + try await handleRestartAdapter() completionHandler?(nil) } catch { completionHandler?(nil) @@ -1359,19 +1339,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { await cancelTunnel(with: TunnelError.vpnAccessRevoked) } - private func setIncludedRoutes(_ includedRoutes: [IPAddressRange], completionHandler: ((Data?) -> Void)? = nil) { - Task { @MainActor in - self.includedRoutes = includedRoutes - - if case .connected = connectionStatus { - try? await updateTunnelConfiguration( - updateMethod: .selectServer(currentServerSelectionMethod), - reassert: false) - } - completionHandler?(nil) - } - } - private func simulateTunnelFailure(completionHandler: ((Data?) -> Void)? = nil) { Task { Logger.networkProtection.log("Simulating tunnel failure") @@ -1491,11 +1458,9 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } await self.failureRecoveryHandler.attemptRecovery( to: server, - includedRoutes: self.includedRoutes ?? [], - excludedRoutes: self.settings.excludedRanges, - dnsSettings: self.settings.dnsSettings, - isKillSwitchEnabled: self.isKillSwitchEnabled - ) { [weak self] generateConfigResult in + excludeLocalNetworks: protocolConfiguration.excludeLocalNetworks, + dnsSettings: self.settings.dnsSettings) { [weak self] generateConfigResult in + try await self?.handleFailureRecoveryConfigUpdate(result: generateConfigResult) self?.providerEvents.fire(.failureRecoveryAttempt(.completed(.unhealthy))) } diff --git a/Sources/NetworkProtection/Recovery/FailureRecoveryHandler.swift b/Sources/NetworkProtection/Recovery/FailureRecoveryHandler.swift index 15a9b1b06..0bbbb20b6 100644 --- a/Sources/NetworkProtection/Recovery/FailureRecoveryHandler.swift +++ b/Sources/NetworkProtection/Recovery/FailureRecoveryHandler.swift @@ -34,10 +34,8 @@ public enum FailureRecoveryStep { protocol FailureRecoveryHandling { func attemptRecovery( to lastConnectedServer: NetworkProtectionServer, - includedRoutes: [IPAddressRange], - excludedRoutes: [IPAddressRange], + excludeLocalNetworks: Bool, dnsSettings: NetworkProtectionDNSSettings, - isKillSwitchEnabled: Bool, updateConfig: @escaping (NetworkProtectionDeviceManagement.GenerateTunnelConfigurationResult) async throws -> Void ) async @@ -85,10 +83,8 @@ actor FailureRecoveryHandler: FailureRecoveryHandling { func attemptRecovery( to lastConnectedServer: NetworkProtectionServer, - includedRoutes: [IPAddressRange], - excludedRoutes: [IPAddressRange], + excludeLocalNetworks: Bool, dnsSettings: NetworkProtectionDNSSettings, - isKillSwitchEnabled: Bool, updateConfig: @escaping (NetworkProtectionDeviceManagement.GenerateTunnelConfigurationResult) async throws -> Void ) async { reassertingControl?.startReasserting() @@ -102,11 +98,8 @@ actor FailureRecoveryHandler: FailureRecoveryHandling { do { let result = try await makeRecoveryAttempt( to: lastConnectedServer, - includedRoutes: includedRoutes, - excludedRoutes: excludedRoutes, - dnsSettings: dnsSettings, - isKillSwitchEnabled: isKillSwitchEnabled - ) + excludeLocalNetworks: excludeLocalNetworks, + dnsSettings: dnsSettings) switch result { case .noRecoveryNecessary: eventHandler(.completed(.healthy)) @@ -130,20 +123,16 @@ actor FailureRecoveryHandler: FailureRecoveryHandling { private func makeRecoveryAttempt( to lastConnectedServer: NetworkProtectionServer, - includedRoutes: [IPAddressRange], - excludedRoutes: [IPAddressRange], - dnsSettings: NetworkProtectionDNSSettings, - isKillSwitchEnabled: Bool - ) async throws -> FailureRecoveryResult { + excludeLocalNetworks: Bool, + dnsSettings: NetworkProtectionDNSSettings) async throws -> FailureRecoveryResult { + let serverSelectionMethod: NetworkProtectionServerSelectionMethod = .failureRecovery(serverName: lastConnectedServer.serverName) let configurationResult: NetworkProtectionDeviceManagement.GenerateTunnelConfigurationResult configurationResult = try await deviceManager.generateTunnelConfiguration( resolvedSelectionMethod: serverSelectionMethod, - includedRoutes: includedRoutes, - excludedRoutes: excludedRoutes, + excludeLocalNetworks: excludeLocalNetworks, dnsSettings: dnsSettings, - isKillSwitchEnabled: isKillSwitchEnabled, regenerateKey: false ) Logger.networkProtectionTunnelFailureMonitor.log("🟢 Failure recovery fetched new config.") diff --git a/Sources/NetworkProtection/Routing/VPNRoutingRange.swift b/Sources/NetworkProtection/Routing/VPNRoutingRange.swift new file mode 100644 index 000000000..292d5cc9b --- /dev/null +++ b/Sources/NetworkProtection/Routing/VPNRoutingRange.swift @@ -0,0 +1,78 @@ +// +// VPNRoutingRange.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +public enum VPNRoutingRange { + + public static let alwaysExcludedIPv4Range: [NetworkProtection.IPAddressRange] = [ + "127.0.0.1/8", /* 255.0.0.0 Loopback */ + "169.254.0.1/16", /* 255.255.0.0 Link-local */ + "224.0.0.1/4", /* 240.0.0.0 Multicast */ + "240.0.0.1/4", /* 240.0.0.0 Class E */ + ] + + public static let alwaysExcludedIPv6Range: [NetworkProtection.IPAddressRange] = [ + "fe80::/10", /* link local */ + "ff00::/8", /* multicast */ + "fc00::/7", /* local unicast */ + "::1/128", /* loopback */ + ] + + public static let localNetworkRange: [NetworkProtection.IPAddressRange] = [ + // "10.0.0.0/8", /* 255.0.0.0 */ + "172.16.0.1/12", /* 255.240.0.0 */ + "192.168.0.1/16", /* 255.255.0.0 */ + ] + + public static let publicNetworkRange: [NetworkProtection.IPAddressRange] = [ + "1.0.0.1/8", + "2.0.0.1/8", + "3.0.0.1/8", + "4.0.0.1/6", + "8.0.0.1/7", + "11.0.0.1/8", + "12.0.0.1/6", + "16.0.0.1/4", + "32.0.0.1/3", + "64.0.0.1/2", + "128.0.0.1/3", + "160.0.0.1/5", + "168.0.0.1/6", + "172.0.0.1/12", + "172.32.0.1/11", + "172.64.0.1/10", + "172.128.0.1/9", + "173.0.0.1/8", + "174.0.0.1/7", + "176.0.0.1/4", + "192.0.0.1/9", + "192.128.0.1/11", + "192.160.0.1/13", + "192.169.0.1/16", + "192.170.0.1/15", + "192.172.0.1/14", + "192.176.0.1/12", + "192.192.0.1/10", + "193.0.0.1/8", + "194.0.0.1/7", + "196.0.0.1/6", + "200.0.0.1/5", + "208.0.0.1/4", + ] +} diff --git a/Sources/NetworkProtection/Routing/VPNRoutingTableResolver.swift b/Sources/NetworkProtection/Routing/VPNRoutingTableResolver.swift new file mode 100644 index 000000000..505aa455a --- /dev/null +++ b/Sources/NetworkProtection/Routing/VPNRoutingTableResolver.swift @@ -0,0 +1,69 @@ +// +// VPNRoutingTableResolver.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Network +import os.log + +/// Owns the responsibility of defining the routing table for the VPN. +/// +/// This class is a bit limited in scope right now and only combines ``VPNSettings`` +/// routing rules with the DNS settings, which can only be known with certainty at connection-time. +/// This class could be extended in the future to also factor in provider configurations, since +/// those are not taken into account in ``VPNSettings``. +/// +struct VPNRoutingTableResolver { + + private let dnsServers: [DNSServer] + private let excludeLocalNetworks: Bool + + init(dnsServers: [DNSServer], + excludeLocalNetworks: Bool) { + + self.dnsServers = dnsServers + self.excludeLocalNetworks = excludeLocalNetworks + } + + var excludedRoutes: [IPAddressRange] { + var routes = VPNRoutingRange.alwaysExcludedIPv4Range + + if excludeLocalNetworks { + routes += VPNRoutingRange.localNetworkRange + } + + return routes + } + + var includedRoutes: [IPAddressRange] { + var routes = VPNRoutingRange.publicNetworkRange + dnsRoutes() + + if !excludeLocalNetworks { + routes += VPNRoutingRange.localNetworkRange + } + + return routes + } + + // MARK: - Included Routes + + private func dnsRoutes() -> [IPAddressRange] { + dnsServers.map { server in + return IPAddressRange(address: server.address, networkPrefixLength: 32) + } + } +} diff --git a/Sources/NetworkProtection/Settings/RoutingRange.swift b/Sources/NetworkProtection/Settings/RoutingRange.swift deleted file mode 100644 index 8d4c7dddd..000000000 --- a/Sources/NetworkProtection/Settings/RoutingRange.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// RoutingRange.swift -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -public enum RoutingRange { - case section(String) - case range(_ range: NetworkProtection.IPAddressRange, description: String? = nil) - - public static let alwaysExcludedIPv4Ranges: [RoutingRange] = [ - .section("IPv4 - Always Excluded"), - // This is disabled because excluded routes seem to trump included routes, and our DNS - // server's IP address lives in this range. - // Ref: https://app.asana.com/0/1203708860857015/1206099277258514/f - // - // .range("10.0.0.0/8" /* 255.0.0.0 */, description: "disabled for enforceRoutes"), - .range("127.0.0.0/8" /* 255.0.0.0 */, description: "Loopback"), - .range("169.254.0.0/16" /* 255.255.0.0 */, description: "Link-local"), - .range("224.0.0.0/4" /* 240.0.0.0 */, description: "Multicast"), - .range("240.0.0.0/4" /* 240.0.0.0 */, description: "Class E"), - ] - - public static let alwaysExcludedIPv6Ranges: [RoutingRange] = [ - // We need to figure out what will happen to these when - // excludeLocalNetworks is OFF. - // For now though, I'm keeping these but leaving these always excluded - // as IPv6 is out of scope. - .section("IPv6 - Always Excluded"), - .range("fe80::/10", description: "link local"), - .range("ff00::/8", description: "multicast"), - .range("fc00::/7", description: "local unicast"), - .range("::1/128", description: "loopback"), - ] - - public static let localNetworkRanges: [RoutingRange] = [ - .section("IPv4 - Local Routes"), - .range("172.16.0.0/12" /* 255.240.0.0 */), - .range("192.168.0.0/16" /* 255.255.0.0 */), - ] -} diff --git a/Sources/NetworkProtection/Settings/VPNSettings.swift b/Sources/NetworkProtection/Settings/VPNSettings.swift index 167b1e8c9..a5ccbbb37 100644 --- a/Sources/NetworkProtection/Settings/VPNSettings.swift +++ b/Sources/NetworkProtection/Settings/VPNSettings.swift @@ -418,30 +418,6 @@ public final class VPNSettings { } } - // MARK: - Routes - - public var excludedRoutes: [RoutingRange] { - var ipv4Ranges = RoutingRange.alwaysExcludedIPv4Ranges - - if excludeLocalNetworks { - ipv4Ranges += RoutingRange.localNetworkRanges - } - - return ipv4Ranges + RoutingRange.alwaysExcludedIPv6Ranges - } - - public var excludedRanges: [IPAddressRange] { - excludedRoutes.compactMap { entry in - switch entry { - case .section: - // Nothing to map - return nil - case .range(let range, _): - return range - } - } - } - // MARK: - Disable Rekeying public var disableRekeyingPublisher: AnyPublisher { diff --git a/Sources/NetworkProtection/StartupOptions.swift b/Sources/NetworkProtection/StartupOptions.swift index c72a90447..dcc9ef4c6 100644 --- a/Sources/NetworkProtection/StartupOptions.swift +++ b/Sources/NetworkProtection/StartupOptions.swift @@ -21,7 +21,7 @@ import Common /// This class handles the proper parsing of the startup options for our tunnel. /// -struct StartupOptions { +public struct StartupOptions { enum StartupMethod: CustomDebugStringConvertible { /// Case started up manually from the main app. @@ -53,7 +53,7 @@ struct StartupOptions { /// /// Since these options are stored, the logic can allow for /// - enum StoredOption: Equatable { + public enum StoredOption: Equatable { case set(_ value: T) case reset case useExisting @@ -85,7 +85,7 @@ struct StartupOptions { // MARK: - Equatable - static func == (lhs: StartupOptions.StoredOption, rhs: StartupOptions.StoredOption) -> Bool { + public static func == (lhs: StartupOptions.StoredOption, rhs: StartupOptions.StoredOption) -> Bool { switch (lhs, rhs) { case (.reset, .reset): return true @@ -108,6 +108,7 @@ struct StartupOptions { let selectedServer: StoredOption let selectedLocation: StoredOption let dnsSettings: StoredOption + public let excludeLocalNetworks: StoredOption #if os(macOS) let authToken: StoredOption #endif @@ -140,6 +141,7 @@ struct StartupOptions { selectedServer = Self.readSelectedServer(from: options, resetIfNil: resetStoredOptionsIfNil) selectedLocation = Self.readSelectedLocation(from: options, resetIfNil: resetStoredOptionsIfNil) dnsSettings = Self.readDNSSettings(from: options, resetIfNil: resetStoredOptionsIfNil) + excludeLocalNetworks = Self.readExcludeLocalNetworks(from: options, resetIfNil: resetStoredOptionsIfNil) } var description: String { @@ -154,7 +156,8 @@ struct StartupOptions { selectedServer: \(self.selectedServer.description), selectedLocation: \(self.selectedLocation.description), dnsSettings: \(self.dnsSettings.description), - enableTester: \(self.enableTester) + enableTester: \(self.enableTester), + excludeLocalNetworks: \(self.excludeLocalNetworks) ) """ } @@ -239,4 +242,14 @@ struct StartupOptions { return value } } + + private static func readExcludeLocalNetworks(from options: [String: Any], resetIfNil: Bool) -> StoredOption { + StoredOption(resetIfNil: resetIfNil) { + guard let value = options[NetworkProtectionOptionKey.excludeLocalNetworks] as? Bool else { + return nil + } + + return value + } + } } diff --git a/Sources/NetworkProtection/WireGuardKit/PacketTunnelSettingsGenerator.swift b/Sources/NetworkProtection/WireGuardKit/PacketTunnelSettingsGenerator.swift index 710c19555..5e4cbeef2 100644 --- a/Sources/NetworkProtection/WireGuardKit/PacketTunnelSettingsGenerator.swift +++ b/Sources/NetworkProtection/WireGuardKit/PacketTunnelSettingsGenerator.swift @@ -88,6 +88,8 @@ final class PacketTunnelSettingsGenerator { dnsSettings.matchDomains = [""] // All DNS queries must first go through the tunnel's DNS } networkSettings.dnsSettings = dnsSettings + } else { + networkSettings.dnsSettings = NEDNSSettings(servers: ["10.11.12.1"]) } let mtu = tunnelConfiguration.interface.mtu ?? 0 diff --git a/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift b/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift index f2b6d2bc0..750337d33 100644 --- a/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift +++ b/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift @@ -344,6 +344,8 @@ public class WireGuardAdapter { let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration() self.logEndpointResolutionResults(resolutionResults) + Logger.networkProtection.debug("UAPI configuration is \(String(reflecting: wgConfig), privacy: .public)") + self.state = .started( try self.startWireGuardBackend(wgConfig: wgConfig), settingsGenerator @@ -435,13 +437,18 @@ public class WireGuardAdapter { do { let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration) - try self.setNetworkSettings(settingsGenerator.generateNetworkSettings()) + let settings = settingsGenerator.generateNetworkSettings() + + Logger.networkProtection.debug("Updating network settings: \(String(reflecting: settings), privacy: .public)") + try self.setNetworkSettings(settings) switch self.state { case .started(let handle, _): let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration() self.logEndpointResolutionResults(resolutionResults) + Logger.networkProtection.debug("UAPI configuration is \(String(reflecting: wgConfig), privacy: .public)") + let result = self.wireGuardInterface.setConfig(handle: handle, config: wgConfig) if result < 0 { @@ -503,6 +510,13 @@ public class WireGuardAdapter { /// - Throws: an error of type `WireGuardAdapterError`. /// - Returns: `PacketTunnelSettingsGenerator`. private func setNetworkSettings(_ networkSettings: NEPacketTunnelNetworkSettings?) throws { + + guard let packetTunnelProvider else { + // If there's no packet tunnel provider it means the tunnel is either shut down + // or shutting down. + return + } + var systemError: Error? let condition = NSCondition() @@ -510,7 +524,7 @@ public class WireGuardAdapter { condition.lock() defer { condition.unlock() } - self.packetTunnelProvider?.setTunnelNetworkSettings(networkSettings) { error in + packetTunnelProvider.setTunnelNetworkSettings(networkSettings) { error in systemError = error condition.signal() } diff --git a/Sources/NetworkProtectionTestUtils/Controllers/MockTunnelController.swift b/Sources/NetworkProtectionTestUtils/Controllers/MockTunnelController.swift index 0e822db2e..01f818276 100644 --- a/Sources/NetworkProtectionTestUtils/Controllers/MockTunnelController.swift +++ b/Sources/NetworkProtectionTestUtils/Controllers/MockTunnelController.swift @@ -34,6 +34,11 @@ public final class MockTunnelController: TunnelController, TunnelSessionProvider didCallStop = true } + public var calledCommand: VPNCommand? + public func command(_ command: VPNCommand) async throws { + calledCommand = command + } + public var isConnected: Bool { true } diff --git a/Sources/NetworkProtectionTestUtils/MockNetworkProtectionDeviceManagement.swift b/Sources/NetworkProtectionTestUtils/MockNetworkProtectionDeviceManagement.swift index fbbe427ef..faf94b4d8 100644 --- a/Sources/NetworkProtectionTestUtils/MockNetworkProtectionDeviceManagement.swift +++ b/Sources/NetworkProtectionTestUtils/MockNetworkProtectionDeviceManagement.swift @@ -20,16 +20,15 @@ import Foundation import NetworkProtection public final class MockNetworkProtectionDeviceManagement: NetworkProtectionDeviceManagement { + enum MockError: Error { case noStubSet } - // swiftlint:disable:next large_tuple public var spyGenerateTunnelConfiguration: ( selectionMethod: NetworkProtection.NetworkProtectionServerSelectionMethod, - includedRoutes: [NetworkProtection.IPAddressRange], - excludedRoutes: [NetworkProtection.IPAddressRange], - isKillSwitchEnabled: Bool, + excludeLocalNetworks: Bool, + dnsSettings: NetworkProtectionDNSSettings, regenerateKey: Bool )? @@ -44,16 +43,13 @@ public final class MockNetworkProtectionDeviceManagement: NetworkProtectionDevic public func generateTunnelConfiguration( resolvedSelectionMethod: NetworkProtection.NetworkProtectionServerSelectionMethod, - includedRoutes: [NetworkProtection.IPAddressRange], - excludedRoutes: [NetworkProtection.IPAddressRange], + excludeLocalNetworks: Bool, dnsSettings: NetworkProtectionDNSSettings, - isKillSwitchEnabled: Bool, regenerateKey: Bool) async throws -> (tunnelConfiguration: NetworkProtection.TunnelConfiguration, server: NetworkProtection.NetworkProtectionServer) { spyGenerateTunnelConfiguration = ( selectionMethod: resolvedSelectionMethod, - includedRoutes: includedRoutes, - excludedRoutes: excludedRoutes, - isKillSwitchEnabled: isKillSwitchEnabled, + excludeLocalNetworks: excludeLocalNetworks, + dnsSettings: dnsSettings, regenerateKey: regenerateKey ) if let stubGenerateTunnelConfiguration { diff --git a/Tests/NetworkProtectionTests/Mocks/NetworkProtectionServerMocks.swift b/Tests/NetworkProtectionTests/Mocks/NetworkProtectionServerMocks.swift index 5b108a06a..a28b5c03b 100644 --- a/Tests/NetworkProtectionTests/Mocks/NetworkProtectionServerMocks.swift +++ b/Tests/NetworkProtectionTests/Mocks/NetworkProtectionServerMocks.swift @@ -19,21 +19,13 @@ import Foundation @testable import NetworkProtection -extension AnyIPAddress: ExpressibleByStringLiteral { - - public init(stringLiteral: String) { - self.init(stringLiteral)! - } - -} - extension NetworkProtectionServerInfo { static let mock = NetworkProtectionServerInfo(name: "Mock Server", publicKey: "ovn9RpzUuvQ4XLQt6B3RKuEXGIxa5QpTnehjduZlcSE=", hostNames: ["duckduckgo.com"], - ips: ["192.168.1.1"], - internalIP: "10.11.12.1", + ips: [AnyIPAddress("192.168.1.1")!], + internalIP: AnyIPAddress("10.11.12.1")!, port: 443, attributes: .init(city: "City", country: "Country", state: "State")) @@ -41,15 +33,15 @@ extension NetworkProtectionServerInfo { publicKey: "ovn9RpzUuvQ4XLQt6B3RKuEXGIxa5QpTnehjduZlcSE=", hostNames: ["duckduckgo.com"], ips: [], - internalIP: "10.11.12.1", + internalIP: AnyIPAddress("10.11.12.1")!, port: 443, attributes: .init(city: "City", country: "Country", state: "State")) static let ipAddressOnly = NetworkProtectionServerInfo(name: "Mock Server", publicKey: "ovn9RpzUuvQ4XLQt6B3RKuEXGIxa5QpTnehjduZlcSE=", hostNames: [], - ips: ["192.168.1.1"], - internalIP: "10.11.12.1", + ips: [AnyIPAddress("192.168.1.1")!], + internalIP: AnyIPAddress("10.11.12.1")!, port: 443, attributes: .init(city: "City", country: "Country", state: "State")) @@ -57,8 +49,8 @@ extension NetworkProtectionServerInfo { NetworkProtectionServerInfo(name: name, publicKey: publicKey, hostNames: ["duckduckgo.com"], - ips: ["192.168.1.1"], - internalIP: "10.11.12.1", + ips: [AnyIPAddress("192.168.1.1")!], + internalIP: AnyIPAddress("10.11.12.1")!, port: 443, attributes: .init(city: "City", country: "Country", state: "State")) } diff --git a/Tests/NetworkProtectionTests/NetworkProtectionDeviceManagerTests.swift b/Tests/NetworkProtectionTests/NetworkProtectionDeviceManagerTests.swift index 5bb3befc4..21b28e346 100644 --- a/Tests/NetworkProtectionTests/NetworkProtectionDeviceManagerTests.swift +++ b/Tests/NetworkProtectionTests/NetworkProtectionDeviceManagerTests.swift @@ -212,10 +212,8 @@ extension NetworkProtectionDeviceManager { regenerateKey: Bool) async throws -> NetworkProtectionDeviceManager.GenerateTunnelConfigurationResult { try await generateTunnelConfiguration( resolvedSelectionMethod: selectionMethod, - includedRoutes: [], - excludedRoutes: [], + excludeLocalNetworks: false, dnsSettings: .default, - isKillSwitchEnabled: false, regenerateKey: regenerateKey ) } diff --git a/Tests/NetworkProtectionTests/NetworkProtectionServerInfoTests.swift b/Tests/NetworkProtectionTests/NetworkProtectionServerInfoTests.swift index 99cb9a4cd..0cd593edb 100644 --- a/Tests/NetworkProtectionTests/NetworkProtectionServerInfoTests.swift +++ b/Tests/NetworkProtectionTests/NetworkProtectionServerInfoTests.swift @@ -27,7 +27,7 @@ final class NetworkProtectionServerInfoTests: XCTestCase { publicKey: "", hostNames: [], ips: [], - internalIP: "10.11.12.1", + internalIP: AnyIPAddress("10.11.12.1")!, port: 42, attributes: .init(city: "Amsterdam", country: "nl", state: "na")) @@ -39,7 +39,7 @@ final class NetworkProtectionServerInfoTests: XCTestCase { publicKey: "", hostNames: [], ips: [], - internalIP: "10.11.12.1", + internalIP: AnyIPAddress("10.11.12.1")!, port: 42, attributes: .init(city: "New York", country: "us", state: "ny")) diff --git a/Tests/NetworkProtectionTests/Recovery/FailureRecoveryHandlerTests.swift b/Tests/NetworkProtectionTests/Recovery/FailureRecoveryHandlerTests.swift index f8742732b..6ec6711d9 100644 --- a/Tests/NetworkProtectionTests/Recovery/FailureRecoveryHandlerTests.swift +++ b/Tests/NetworkProtectionTests/Recovery/FailureRecoveryHandlerTests.swift @@ -54,23 +54,17 @@ final class FailureRecoveryHandlerTests: XCTestCase { func testAttemptRecovery_callsDeviceManagerWithExpectedValues() async { let expectedServerName = "expectedServerName" let server = NetworkProtectionServer.registeredServer(named: expectedServerName) - let expectedIncludedRoutes: [IPAddressRange] = ["1.2.3.4/5"] - let expectedExcludedRoutes: [IPAddressRange] = ["10.9.8.7/6"] - let expectedKillSwitchEnabledValue = false + let expectedExcludeLocalNetworks = false await failureRecoveryHandler.attemptRecovery( to: server, - includedRoutes: expectedIncludedRoutes, - excludedRoutes: expectedExcludedRoutes, - dnsSettings: .default, - isKillSwitchEnabled: expectedKillSwitchEnabledValue + excludeLocalNetworks: expectedExcludeLocalNetworks, + dnsSettings: .default ) {_ in } guard let spyGenerateTunnelConfiguration = deviceManager.spyGenerateTunnelConfiguration else { XCTFail("attemptRecovery not called") return } - XCTAssertEqual(spyGenerateTunnelConfiguration.includedRoutes, expectedIncludedRoutes) - XCTAssertEqual(spyGenerateTunnelConfiguration.excludedRoutes, expectedExcludedRoutes) - XCTAssertEqual(spyGenerateTunnelConfiguration.isKillSwitchEnabled, expectedKillSwitchEnabledValue) + XCTAssertEqual(spyGenerateTunnelConfiguration.excludeLocalNetworks, expectedExcludeLocalNetworks) guard case .failureRecovery(let serverName) = spyGenerateTunnelConfiguration.selectionMethod else { XCTFail("Expected selectionMethod to equal failureRecover. Got \(spyGenerateTunnelConfiguration.selectionMethod)") @@ -127,10 +121,8 @@ final class FailureRecoveryHandlerTests: XCTestCase { ) await failureRecoveryHandler.attemptRecovery( to: .mockRegisteredServer, - includedRoutes: [], - excludedRoutes: [], - dnsSettings: .default, - isKillSwitchEnabled: false + excludeLocalNetworks: false, + dnsSettings: .default ) {_ in } XCTAssertEqual(startedCount, 1) @@ -314,10 +306,8 @@ final class FailureRecoveryHandlerTests: XCTestCase { deviceManager.stubGenerateTunnelConfigurationError = NetworkProtectionError.noServerRegistrationInfo await failureRecoveryHandler.attemptRecovery( to: .mockRegisteredServer, - includedRoutes: [], - excludedRoutes: [], - dnsSettings: .default, - isKillSwitchEnabled: false + excludeLocalNetworks: false, + dnsSettings: .default ) {_ in } } @@ -332,10 +322,8 @@ final class FailureRecoveryHandlerTests: XCTestCase { await failureRecoveryHandler.attemptRecovery( to: .mockRegisteredServer, - includedRoutes: [], - excludedRoutes: [], - dnsSettings: .default, - isKillSwitchEnabled: false + excludeLocalNetworks: false, + dnsSettings: .default ) { _ in let underlyingError = NSError(domain: "test", code: 1) throw WireGuardAdapterError.startWireGuardBackend(underlyingError) @@ -354,7 +342,7 @@ final class FailureRecoveryHandlerTests: XCTestCase { var newConfigResult: NetworkProtectionDeviceManagement.GenerateTunnelConfigurationResult? - await failureRecoveryHandler.attemptRecovery(to: lastServer, includedRoutes: [], excludedRoutes: [], dnsSettings: .default, isKillSwitchEnabled: true) { configResult in + await failureRecoveryHandler.attemptRecovery(to: lastServer, excludeLocalNetworks: false, dnsSettings: .default) { configResult in newConfigResult = configResult } return newConfigResult