Skip to content

Commit

Permalink
Remediate TunnelVision, TunnelCrack and fix "Exclude Local Networks" (#…
Browse files Browse the repository at this point in the history
…1039)

Task/Issue URL:
https://app.asana.com/0/1206580121312550/1208686409805161/f
Tech Design URL:
https://app.asana.com/0/481882893211075/1208643192597095/f

iOS PR: duckduckgo/iOS#3460
macOS PR: duckduckgo/macos-browser#3422
What kind of version bump will this require?: Major

## Description

Remediate TunnelVision, TunnelCrack and fix "Exclude Local Networks".
  • Loading branch information
diegoreymendez authored Nov 11, 2024
1 parent 6be7815 commit 614ea57
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 278 deletions.
Original file line number Diff line number Diff line change
@@ -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<Bool, Never> {
publisher(for: \.isInternalUser).eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions Sources/NetworkProtection/Controllers/TunnelController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
4 changes: 2 additions & 2 deletions Sources/NetworkProtection/Models/AnyIPAddress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
64 changes: 23 additions & 41 deletions Sources/NetworkProtection/NetworkProtectionDeviceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

}
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion Sources/NetworkProtection/NetworkProtectionOptionKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Loading

0 comments on commit 614ea57

Please sign in to comment.