summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2025-04-09 16:25:42 +0200
committerBug Magnet <marco.nikic@mullvad.net>2025-04-09 16:25:42 +0200
commitb925b777c7cc3398abf707d8f9680d384a0d2d68 (patch)
tree220454b0163ac8a74af4d65d7f9bcf2ecf34045b
parent6b115f7af908eeaff8ea9f1afcc281480447b57e (diff)
parent08841ec61d12869e40ebc1869f3b93936d1e31ba (diff)
downloadmullvadvpn-b925b777c7cc3398abf707d8f9680d384a0d2d68.tar.xz
mullvadvpn-b925b777c7cc3398abf707d8f9680d384a0d2d68.zip
Merge branch 'ios-1125-actual-obfuscation-state'
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift17
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewComponentPreview.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift13
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift3
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift3
-rw-r--r--ios/PacketTunnelCore/Actor/ObservedState.swift9
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift10
-rw-r--r--ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift28
-rw-r--r--ios/PacketTunnelCore/Actor/State.swift4
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift4
-rw-r--r--ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift18
11 files changed, 79 insertions, 32 deletions
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
index 71b864bc85..5130d51764 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
@@ -6,6 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import MullvadSettings
+import PacketTunnelCore
import SwiftUI
// Opting to use NSLocalizedString instead of LocalizedStringKey here in order
@@ -69,12 +70,26 @@ struct MultihopFeature: ChipFeature {
struct ObfuscationFeature: ChipFeature {
let settings: LatestTunnelSettings
+ let state: ObservedState
+
+ var actualObfuscationMethod: WireGuardObfuscationState {
+ state.connectionState.map { $0.obfuscationMethod } ?? .off
+ }
var isEnabled: Bool {
- settings.wireGuardObfuscation.state.isEnabled
+ actualObfuscationMethod != .off
+ }
+
+ var isAutomatic: Bool {
+ settings.wireGuardObfuscation.state == .automatic
}
var name: String {
+ // This just currently says "Obfuscation".
+ // To add an automaticity indicator (a trailing " (automatic)"
+ // or a colour/border style or whatever), use the `isAutomatic` field.
+ // To say what type of obfuscation it is,
+ // we can look at `actualObfuscationMethod`
NSLocalizedString(
"FEATURE_INDICATORS_CHIP_OBFUSCATION",
tableName: "FeatureIndicatorsChip",
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewComponentPreview.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewComponentPreview.swift
index 1dd83fc6cf..1d80901337 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewComponentPreview.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewComponentPreview.swift
@@ -70,7 +70,7 @@ struct ConnectionViewComponentPreview<Content: View>: View {
FeatureIndicatorsViewModel(
tunnelSettings: tunnelSettings,
ipOverrides: [],
- tunnelState: connectedTunnelStatus.state
+ tunnelStatus: connectedTunnelStatus
),
viewModel,
$isExpanded
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift
index 39a18c987a..de1a9edab7 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicatorsViewModel.swift
@@ -7,17 +7,24 @@
//
import MullvadSettings
+import PacketTunnelCore
import SwiftUI
class FeatureIndicatorsViewModel: ChipViewModelProtocol {
@Published var tunnelSettings: LatestTunnelSettings
@Published var ipOverrides: [IPOverride]
@Published var tunnelState: TunnelState
+ @Published var observedState: ObservedState
- init(tunnelSettings: LatestTunnelSettings, ipOverrides: [IPOverride], tunnelState: TunnelState) {
+ init(
+ tunnelSettings: LatestTunnelSettings,
+ ipOverrides: [IPOverride],
+ tunnelStatus: TunnelStatus
+ ) {
self.tunnelSettings = tunnelSettings
self.ipOverrides = ipOverrides
- self.tunnelState = tunnelState
+ self.tunnelState = tunnelStatus.state
+ self.observedState = tunnelStatus.observedState
}
var chips: [ChipModel] {
@@ -30,7 +37,7 @@ class FeatureIndicatorsViewModel: ChipViewModelProtocol {
DaitaFeature(settings: tunnelSettings),
QuantumResistanceFeature(settings: tunnelSettings),
MultihopFeature(settings: tunnelSettings, state: tunnelState),
- ObfuscationFeature(settings: tunnelSettings),
+ ObfuscationFeature(settings: tunnelSettings, state: observedState),
DNSFeature(settings: tunnelSettings),
IPOverrideFeature(overrides: ipOverrides),
]
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
index bb6cebdc3c..fa81a6cbbd 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
@@ -74,7 +74,7 @@ class TunnelViewController: UIViewController, RootContainment {
indicatorsViewViewModel = FeatureIndicatorsViewModel(
tunnelSettings: interactor.tunnelSettings,
ipOverrides: interactor.ipOverrides,
- tunnelState: tunnelState
+ tunnelStatus: interactor.tunnelStatus
)
connectionView = ConnectionView(
@@ -100,6 +100,7 @@ class TunnelViewController: UIViewController, RootContainment {
self?.connectionViewViewModel.update(tunnelStatus: tunnelStatus)
self?.setTunnelState(tunnelStatus.state, animated: true)
self?.indicatorsViewViewModel.tunnelState = tunnelStatus.state
+ self?.indicatorsViewViewModel.observedState = tunnelStatus.observedState
self?.view.setNeedsLayout()
}
diff --git a/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift b/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
index 235dc1fec8..1cd87f8259 100644
--- a/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
@@ -36,7 +36,8 @@ final class PacketTunnelActorReducerTests: XCTestCase {
transportLayer: .udp,
remotePort: 12345,
isPostQuantum: false,
- isDaitaEnabled: false
+ isDaitaEnabled: false,
+ obfuscationMethod: .off
)
}
diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift
index 5aece77e56..83ea8f96a5 100644
--- a/ios/PacketTunnelCore/Actor/ObservedState.swift
+++ b/ios/PacketTunnelCore/Actor/ObservedState.swift
@@ -9,6 +9,7 @@
import Combine
import Foundation
import MullvadREST
+import MullvadSettings
import MullvadTypes
import Network
@preconcurrency import WireGuardKitTypes
@@ -36,6 +37,7 @@ public struct ObservedConnectionState: Equatable, Codable, Sendable {
public var lastKeyRotation: Date?
public let isPostQuantum: Bool
public let isDaitaEnabled: Bool
+ public let obfuscationMethod: WireGuardObfuscationState
public var isNetworkReachable: Bool {
networkReachability != .unreachable
@@ -50,7 +52,8 @@ public struct ObservedConnectionState: Equatable, Codable, Sendable {
remotePort: UInt16,
lastKeyRotation: Date? = nil,
isPostQuantum: Bool,
- isDaitaEnabled: Bool
+ isDaitaEnabled: Bool,
+ obfuscationMethod: WireGuardObfuscationState = .off
) {
self.selectedRelays = selectedRelays
self.relayConstraints = relayConstraints
@@ -61,6 +64,7 @@ public struct ObservedConnectionState: Equatable, Codable, Sendable {
self.lastKeyRotation = lastKeyRotation
self.isPostQuantum = isPostQuantum
self.isDaitaEnabled = isDaitaEnabled
+ self.obfuscationMethod = obfuscationMethod
}
}
@@ -111,7 +115,8 @@ extension State.ConnectionData {
remotePort: remotePort,
lastKeyRotation: lastKeyRotation,
isPostQuantum: isPostQuantum,
- isDaitaEnabled: isDaitaEnabled
+ isDaitaEnabled: isDaitaEnabled,
+ obfuscationMethod: obfuscationMethod
)
}
}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index e55cf1e8cb..bf4feac3c1 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -402,7 +402,8 @@ extension PacketTunnelActor {
transportLayer: .udp,
remotePort: connectedRelay.endpoint.ipv4Relay.port,
isPostQuantum: settings.quantumResistance.isEnabled,
- isDaitaEnabled: settings.daita.daitaState.isEnabled
+ isDaitaEnabled: settings.daita.daitaState.isEnabled,
+ obfuscationMethod: .off
)
case .disconnecting, .disconnected:
return nil
@@ -426,7 +427,7 @@ extension PacketTunnelActor {
guard let connectionState = try makeConnectionState(nextRelays: nextRelays, settings: settings, reason: reason)
else { return nil }
- let obfuscatedEndpoint = protocolObfuscator.obfuscate(
+ let obfuscated = protocolObfuscator.obfuscate(
connectionState.connectedEndpoint,
settings: settings.tunnelSettings,
retryAttempts: connectionState.selectedRelays.retryAttempt
@@ -441,11 +442,12 @@ extension PacketTunnelActor {
networkReachability: connectionState.networkReachability,
connectionAttemptCount: connectionState.connectionAttemptCount,
lastKeyRotation: connectionState.lastKeyRotation,
- connectedEndpoint: obfuscatedEndpoint,
+ connectedEndpoint: obfuscated.endpoint,
transportLayer: transportLayer,
remotePort: protocolObfuscator.remotePort,
isPostQuantum: settings.quantumResistance.isEnabled,
- isDaitaEnabled: settings.daita.daitaState.isEnabled
+ isDaitaEnabled: settings.daita.daitaState.isEnabled,
+ obfuscationMethod: obfuscated.method
)
}
diff --git a/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift b/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
index ca162f0919..2a0ed7bf06 100644
--- a/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
+++ b/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
@@ -12,8 +12,17 @@ import MullvadRustRuntime
import MullvadSettings
import MullvadTypes
+public struct ProtocolObfuscationResult {
+ let endpoint: MullvadEndpoint
+ let method: WireGuardObfuscationState
+}
+
public protocol ProtocolObfuscation {
- func obfuscate(_ endpoint: MullvadEndpoint, settings: LatestTunnelSettings, retryAttempts: UInt) -> MullvadEndpoint
+ func obfuscate(
+ _ endpoint: MullvadEndpoint,
+ settings: LatestTunnelSettings,
+ retryAttempts: UInt
+ ) -> ProtocolObfuscationResult
var transportLayer: TransportLayer? { get }
var remotePort: UInt16 { get }
}
@@ -38,7 +47,7 @@ public class ProtocolObfuscator<Obfuscator: TunnelObfuscation>: ProtocolObfuscat
_ endpoint: MullvadEndpoint,
settings: LatestTunnelSettings,
retryAttempts: UInt = 0
- ) -> MullvadEndpoint {
+ ) -> ProtocolObfuscationResult {
let obfuscationMethod = ObfuscationMethodSelector.obfuscationMethodBy(
connectionAttemptCount: retryAttempts,
tunnelSettings: settings
@@ -48,7 +57,7 @@ public class ProtocolObfuscator<Obfuscator: TunnelObfuscation>: ProtocolObfuscat
guard obfuscationMethod != .off else {
tunnelObfuscator = nil
- return endpoint
+ return .init(endpoint: endpoint, method: .off)
}
// At this point, the only possible obfuscation methods should be either `.udpOverTcp` or `.shadowsocks`
@@ -61,11 +70,14 @@ public class ProtocolObfuscator<Obfuscator: TunnelObfuscation>: ProtocolObfuscat
obfuscator.start()
tunnelObfuscator = obfuscator
- return MullvadEndpoint(
- ipv4Relay: IPv4Endpoint(ip: .loopback, port: obfuscator.localUdpPort),
- ipv4Gateway: endpoint.ipv4Gateway,
- ipv6Gateway: endpoint.ipv6Gateway,
- publicKey: endpoint.publicKey
+ return .init(
+ endpoint: MullvadEndpoint(
+ ipv4Relay: IPv4Endpoint(ip: .loopback, port: obfuscator.localUdpPort),
+ ipv4Gateway: endpoint.ipv4Gateway,
+ ipv6Gateway: endpoint.ipv6Gateway,
+ publicKey: endpoint.publicKey
+ ),
+ method: obfuscationMethod
)
}
}
diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift
index 83ecd53145..3aac312490 100644
--- a/ios/PacketTunnelCore/Actor/State.swift
+++ b/ios/PacketTunnelCore/Actor/State.swift
@@ -9,6 +9,7 @@
import Foundation
import MullvadREST
import MullvadRustRuntime
+import MullvadSettings
import MullvadTypes
@preconcurrency import WireGuardKitTypes
@@ -153,6 +154,9 @@ extension State {
/// True if Daita is enabled
public let isDaitaEnabled: Bool
+
+ /// The obfuscation method in force on the connection
+ public let obfuscationMethod: WireGuardObfuscationState
}
/// Data associated with error state.
diff --git a/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift b/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift
index 9acdf9f1d0..77087782a3 100644
--- a/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift
@@ -18,8 +18,8 @@ struct ProtocolObfuscationStub: ProtocolObfuscation {
_ endpoint: MullvadEndpoint,
settings: LatestTunnelSettings,
retryAttempts: UInt
- ) -> MullvadEndpoint {
- endpoint
+ ) -> ProtocolObfuscationResult {
+ .init(endpoint: endpoint, method: .off)
}
var transportLayer: TransportLayer? { .udp }
diff --git a/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift b/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift
index 83491d18f8..318a451a7b 100644
--- a/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift
+++ b/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift
@@ -33,39 +33,39 @@ final class ProtocolObfuscatorTests: XCTestCase {
func testObfuscateOffDoesNotChangeEndpoint() {
let settings = settings(.off, obfuscationPort: .automatic)
- let nonObfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings)
+ let nonObfuscated = obfuscator.obfuscate(endpoint, settings: settings)
- XCTAssertEqual(endpoint, nonObfuscatedEndpoint)
+ XCTAssertEqual(endpoint, nonObfuscated.endpoint)
}
func testObfuscateUdpOverTcp() throws {
let settings = settings(.udpOverTcp, obfuscationPort: .automatic)
- let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings)
+ let obfuscated = obfuscator.obfuscate(endpoint, settings: settings)
let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub)
- validate(obfuscatedEndpoint, against: obfuscationProtocol)
+ validate(obfuscated.endpoint, against: obfuscationProtocol)
}
func testObfuscateShadowsocks() throws {
let settings = settings(.shadowsocks, obfuscationPort: .automatic)
- let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings)
+ let obfuscated = obfuscator.obfuscate(endpoint, settings: settings)
let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub)
- validate(obfuscatedEndpoint, against: obfuscationProtocol)
+ validate(obfuscated.endpoint, against: obfuscationProtocol)
}
func testObfuscateAutomatic() throws {
let settings = settings(.automatic, obfuscationPort: .automatic)
try (UInt(0) ... 3).forEach { attempt in
- let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings, retryAttempts: attempt)
+ let obfuscated = obfuscator.obfuscate(endpoint, settings: settings, retryAttempts: attempt)
switch attempt {
case 0, 1:
- XCTAssertEqual(endpoint, obfuscatedEndpoint)
+ XCTAssertEqual(endpoint, obfuscated.endpoint)
case 2, 3:
let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub)
- validate(obfuscatedEndpoint, against: obfuscationProtocol)
+ validate(obfuscated.endpoint, against: obfuscationProtocol)
default:
XCTExpectFailure("Should not end up here, test setup is wrong")
}