Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting for ian and update excludelocalnetwork flag from ios 246 #7655

11 changes: 8 additions & 3 deletions ios/MullvadSettings/TunnelSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

/// Alias to the latest version of the `TunnelSettings`.
public typealias LatestTunnelSettings = TunnelSettingsV6
public typealias LatestTunnelSettings = TunnelSettingsV7

/// Protocol all TunnelSettings must adhere to, for upgrade purposes.
public protocol TunnelSettings: Codable, Sendable {
Expand All @@ -36,6 +36,9 @@ public enum SchemaVersion: Int, Equatable, Sendable {
/// V5 format with DAITA settings, stored as `TunnelSettingsV6`.
case v6 = 6

/// V6 format with Local network sharing, stored as `TunnelSettingsV7`.
case v7 = 7

var settingsType: any TunnelSettings.Type {
switch self {
case .v1: return TunnelSettingsV1.self
Expand All @@ -44,6 +47,7 @@ public enum SchemaVersion: Int, Equatable, Sendable {
case .v4: return TunnelSettingsV4.self
case .v5: return TunnelSettingsV5.self
case .v6: return TunnelSettingsV6.self
case .v7: return TunnelSettingsV7.self
}
}

Expand All @@ -54,10 +58,11 @@ public enum SchemaVersion: Int, Equatable, Sendable {
case .v3: return .v4
case .v4: return .v5
case .v5: return .v6
case .v6: return .v6
case .v6: return .v7
case .v7: return .v7
}
}

/// Current schema version.
public static let current = SchemaVersion.v6
public static let current = SchemaVersion.v7
}
32 changes: 30 additions & 2 deletions ios/MullvadSettings/TunnelSettingsStrategy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,47 @@
import Foundation
public protocol TunnelSettingsStrategyProtocol: Sendable {
func shouldReconnectToNewRelay(oldSettings: LatestTunnelSettings, newSettings: LatestTunnelSettings) -> Bool
func getReconnectionStrategy(
oldSettings: LatestTunnelSettings,
newSettings: LatestTunnelSettings
) -> TunnelSettingsReconnectionStrategy
}

public struct TunnelSettingsStrategy: TunnelSettingsStrategyProtocol, Sendable {
public init() {}

public func shouldReconnectToNewRelay(
oldSettings: LatestTunnelSettings,
newSettings: LatestTunnelSettings
) -> Bool {
getReconnectionStrategy(
oldSettings: oldSettings,
newSettings: newSettings
) != .currentRelayReconnect
}

public func getReconnectionStrategy(
oldSettings: LatestTunnelSettings,
newSettings: LatestTunnelSettings
) -> TunnelSettingsReconnectionStrategy {
if oldSettings.localNetworkSharing != newSettings.localNetworkSharing ||
oldSettings.includeAllNetworks != newSettings.includeAllNetworks {
return .hardReconnect
}
switch (oldSettings, newSettings) {
case let (old, new) where old != new:
true
return .newRelayReconnect
default:
false
return .currentRelayReconnect
}
}
}

/// This enum representes reconnection strategies.
/// > Warning: `hardReconnect` will disconnect and reconnect which
/// > potentially leads to traffic leaking outside the tunnel.
public enum TunnelSettingsReconnectionStrategy {
case currentRelayReconnect
case newRelayReconnect
case hardReconnect
}
8 changes: 8 additions & 0 deletions ios/MullvadSettings/TunnelSettingsUpdate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Foundation
import MullvadTypes

public enum TunnelSettingsUpdate: Sendable {
case localNetworkSharing(Bool)
case includeAllNetworks(Bool)
case dnsSettings(DNSSettings)
case obfuscation(WireGuardObfuscationSettings)
case relayConstraints(RelayConstraints)
Expand All @@ -21,6 +23,10 @@ public enum TunnelSettingsUpdate: Sendable {
extension TunnelSettingsUpdate {
public func apply(to settings: inout LatestTunnelSettings) {
switch self {
case let .localNetworkSharing(enabled):
settings.localNetworkSharing = enabled
case let .includeAllNetworks(enabled):
settings.includeAllNetworks = enabled
case let .dnsSettings(newDNSSettings):
settings.dnsSettings = newDNSSettings
case let .obfuscation(newObfuscationSettings):
Expand All @@ -38,6 +44,8 @@ extension TunnelSettingsUpdate {

public var subjectName: String {
switch self {
case .localNetworkSharing: "Local network sharing"
case .includeAllNetworks: "Include all networks"
case .dnsSettings: "DNS settings"
case .obfuscation: "obfuscation settings"
case .relayConstraints: "relay constraints"
Expand Down
11 changes: 10 additions & 1 deletion ios/MullvadSettings/TunnelSettingsV6.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ public struct TunnelSettingsV6: Codable, Equatable, TunnelSettings, Sendable {
}

public func upgradeToNextVersion() -> any TunnelSettings {
self
TunnelSettingsV7(
relayConstraints: relayConstraints,
dnsSettings: dnsSettings,
wireGuardObfuscation: wireGuardObfuscation,
tunnelQuantumResistance: tunnelQuantumResistance,
tunnelMultihopState: tunnelMultihopState,
daita: daita,
localNetworkSharing: false,
includeAllNetworks: false
)
}
}
60 changes: 60 additions & 0 deletions ios/MullvadSettings/TunnelSettingsV7.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// TunnelSettingsV6 2.swift
// MullvadVPN
//
// Created by Steffen Ernst on 2025-02-04.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadTypes

public struct TunnelSettingsV7: Codable, Equatable, TunnelSettings, Sendable {
/// Relay constraints.
public var relayConstraints: RelayConstraints

/// DNS settings.
public var dnsSettings: DNSSettings

/// WireGuard obfuscation settings
public var wireGuardObfuscation: WireGuardObfuscationSettings

/// Whether Post Quantum exchanges are enabled.
public var tunnelQuantumResistance: TunnelQuantumResistance

/// Whether Multihop is enabled.
public var tunnelMultihopState: MultihopState

/// DAITA settings.
public var daita: DAITASettings

/// Local networks sharing.
public var localNetworkSharing: Bool

/// Forces the system to route most traffic through the tunnel
public var includeAllNetworks: Bool

public init(
relayConstraints: RelayConstraints = RelayConstraints(),
dnsSettings: DNSSettings = DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings(),
tunnelQuantumResistance: TunnelQuantumResistance = .automatic,
tunnelMultihopState: MultihopState = .off,
daita: DAITASettings = DAITASettings(),
localNetworkSharing: Bool = false,
includeAllNetworks: Bool = false
) {
self.relayConstraints = relayConstraints
self.dnsSettings = dnsSettings
self.wireGuardObfuscation = wireGuardObfuscation
self.tunnelQuantumResistance = tunnelQuantumResistance
self.tunnelMultihopState = tunnelMultihopState
self.daita = daita
self.localNetworkSharing = localNetworkSharing
self.includeAllNetworks = includeAllNetworks
}

public func upgradeToNextVersion() -> any TunnelSettings {
self
}
}
4 changes: 4 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,7 @@
F0FADDEC2BE90AB0000D0B02 /* LaunchArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */; };
F910A4312D4A1B41002FF3BB /* InAppPurchaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4302D4A1B3B002FF3BB /* InAppPurchaseCoordinator.swift */; };
F910A43A2D4A283D002FF3BB /* InAppPurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4392D4A2839002FF3BB /* InAppPurchaseViewController.swift */; };
F910A8572D523812002FF3BB /* TunnelSettingsV7.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */; };
F95C1C252D3E5E8E00EBE769 /* UIAlertController+InAppPurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F95C1C242D3E5E7A00EBE769 /* UIAlertController+InAppPurchase.swift */; };
F910A4012D3FF23A002FF3BB /* View+Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4002D3FF22E002FF3BB /* View+Modifier.swift */; };
F998EFF82D359C4600D88D01 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; };
Expand Down Expand Up @@ -2329,6 +2330,7 @@
F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorResult.swift; sourceTree = "<group>"; };
F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoRelaysSatisfyingConstraintsError.swift; sourceTree = "<group>"; };
F0FBD98E2C4A60CC00EE5323 /* KeyExchangingResultStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyExchangingResultStub.swift; sourceTree = "<group>"; };
F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV7.swift; sourceTree = "<group>"; };
F910A4002D3FF22E002FF3BB /* View+Modifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Modifier.swift"; sourceTree = "<group>"; };
F95C1C242D3E5E7A00EBE769 /* UIAlertController+InAppPurchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+InAppPurchase.swift"; sourceTree = "<group>"; };
F910A4302D4A1B3B002FF3BB /* InAppPurchaseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseCoordinator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3548,6 +3550,7 @@
A93181A02B727ED700E341D2 /* TunnelSettingsV4.swift */,
F0E61CA82BF2911D000C4A95 /* TunnelSettingsV5.swift */,
F0C13FE52C64FB3400BD087D /* TunnelSettingsV6.swift */,
F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */,
A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */,
);
path = MullvadSettings;
Expand Down Expand Up @@ -5799,6 +5802,7 @@
58B2FDDE2AA71D5C003EB5C6 /* Migration.swift in Sources */,
F05769BB2C6661EE00D9778B /* TunnelSettingsStrategy.swift in Sources */,
F0D7FF8F2B31DF5900E0FDE5 /* AccessMethodRepository.swift in Sources */,
F910A8572D523812002FF3BB /* TunnelSettingsV7.swift in Sources */,
58B2FDE12AA71D5C003EB5C6 /* TunnelSettingsV1.swift in Sources */,
449872E12B7BBC5400094DDC /* TunnelSettingsUpdate.swift in Sources */,
58B2FDE72AA71D5C003EB5C6 /* SettingsStore.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public enum AccessibilityIdentifier: Equatable {
case relayFilterChipCloseButton
case openPortSelectorMenuButton
case cancelPurchaseListButton
case acceptLocalNetworkSharingButton
// Cells
case deviceCell
case accessMethodProtocolSelectionCell
Expand Down Expand Up @@ -175,6 +176,8 @@ public enum AccessibilityIdentifier: Equatable {
case statusImageView

// DNS settings
case includeAllNetworks
case localNetworkSharing
case dnsSettings
case ipOverrides
case wireGuardCustomPort
Expand Down
25 changes: 6 additions & 19 deletions ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import Foundation
import MullvadLogging
import MullvadREST
import MullvadSettings
import NetworkExtension
import Operations
import PacketTunnelCore
Expand Down Expand Up @@ -48,7 +49,7 @@ class StartTunnelOperation: ResultOperation<Void>, @unchecked Sendable {

finish(result: .success(()))

case .disconnected, .pendingReconnect:
case .disconnected, .pendingReconnect, .waitingForConnectivity:
makeTunnelProviderAndStartTunnel { error in
self.finish(result: error.map { .failure($0) } ?? .success(()))
}
Expand Down Expand Up @@ -106,28 +107,14 @@ class StartTunnelOperation: ResultOperation<Void>, @unchecked Sendable {
) {
let persistentTunnels = interactor.getPersistentTunnels()
let tunnel = persistentTunnels.first ?? interactor.createNewTunnel()
let configuration = Self.makeTunnelConfiguration()
let configuration = TunnelConfiguration(
includeAllNetworks: interactor.settings.includeAllNetworks,
excludeLocalNetworks: interactor.settings.localNetworkSharing
)

tunnel.setConfiguration(configuration)
tunnel.saveToPreferences { error in
completionHandler(error.map { .failure($0) } ?? .success(tunnel))
}
}

private class func makeTunnelConfiguration() -> TunnelConfiguration {
let protocolConfig = NETunnelProviderProtocol()
protocolConfig.providerBundleIdentifier = ApplicationTarget.packetTunnel.bundleIdentifier
protocolConfig.serverAddress = ""

let alwaysOnRule = NEOnDemandRuleConnect()
alwaysOnRule.interfaceTypeMatch = .any

return TunnelConfiguration(
isEnabled: true,
localizedDescription: "WireGuard",
protocolConfiguration: protocolConfig,
onDemandRules: [alwaysOnRule],
isOnDemandEnabled: true
)
}
}
4 changes: 2 additions & 2 deletions ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Operations

class StopTunnelOperation: ResultOperation<Void>, @unchecked Sendable {
private let interactor: TunnelInteractor
var isOnDemandEnabled = false

init(
dispatchQueue: DispatchQueue,
Expand Down Expand Up @@ -50,8 +51,7 @@ class StopTunnelOperation: ResultOperation<Void>, @unchecked Sendable {
return
}

// Disable on-demand when stopping the tunnel to prevent it from coming back up
tunnel.isOnDemandEnabled = false
tunnel.isOnDemandEnabled = isOnDemandEnabled

tunnel.saveToPreferences { error in
self.dispatchQueue.async {
Expand Down
19 changes: 19 additions & 0 deletions ios/MullvadVPN/TunnelManager/TunnelConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ struct TunnelConfiguration {
var onDemandRules: [NEOnDemandRule]
var isOnDemandEnabled: Bool

init(includeAllNetworks: Bool, excludeLocalNetworks: Bool, isOnDemandEnabled: Bool = true) {
let protocolConfig = NETunnelProviderProtocol()
protocolConfig.providerBundleIdentifier = ApplicationTarget.packetTunnel.bundleIdentifier
protocolConfig.serverAddress = ""
#if DEBUG
protocolConfig.includeAllNetworks = includeAllNetworks
#endif
protocolConfig.excludeLocalNetworks = excludeLocalNetworks

let alwaysOnRule = NEOnDemandRuleConnect()
alwaysOnRule.interfaceTypeMatch = .any

isEnabled = true
localizedDescription = "WireGuard"
protocolConfiguration = protocolConfig
onDemandRules = [alwaysOnRule]
self.isOnDemandEnabled = isOnDemandEnabled
}

func apply(to manager: TunnelProviderManagerType) {
manager.isEnabled = isEnabled
manager.localizedDescription = localizedDescription
Expand Down
Loading
Loading