diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-04-09 16:25:42 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-04-09 16:25:42 +0200 |
| commit | b925b777c7cc3398abf707d8f9680d384a0d2d68 (patch) | |
| tree | 220454b0163ac8a74af4d65d7f9bcf2ecf34045b | |
| parent | 6b115f7af908eeaff8ea9f1afcc281480447b57e (diff) | |
| parent | 08841ec61d12869e40ebc1869f3b93936d1e31ba (diff) | |
| download | mullvadvpn-b925b777c7cc3398abf707d8f9680d384a0d2d68.tar.xz mullvadvpn-b925b777c7cc3398abf707d8f9680d384a0d2d68.zip | |
Merge branch 'ios-1125-actual-obfuscation-state'
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") } |
