summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-06-25 14:43:35 +0200
committerJon Petersson <jon.petersson@mullvad.net>2025-07-08 12:04:35 +0200
commit7ded39e1f58396baa401b93576dd4650e0836645 (patch)
treee275fbf4cd1a23ea38bef30e45f87c233e5c3c72 /ios
parent216653151569b218ca8f0d78a8585595147a16b9 (diff)
downloadmullvadvpn-7ded39e1f58396baa401b93576dd4650e0836645.tar.xz
mullvadvpn-7ded39e1f58396baa401b93576dd4650e0836645.zip
Update FFI to handle new QUIC params
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift3
-rw-r--r--ios/MullvadMockData/MullvadREST/SelectedRelaysStub+Stubs.swift3
-rw-r--r--ios/MullvadREST/Relay/ObfuscationMethodSelector.swift9
-rw-r--r--ios/MullvadREST/Relay/RelayPicking/RelayPicking.swift3
-rw-r--r--ios/MullvadREST/Relay/RelaySelectorProtocol.swift7
-rw-r--r--ios/MullvadRustRuntime/TunnelObfuscator.swift40
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h31
-rw-r--r--ios/MullvadSettings/WireGuardObfuscationSettings.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift3
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift3
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift11
-rw-r--r--ios/MullvadVPNTests/MullvadREST/Relay/ObfuscationMethodSelectorTests.swift30
-rw-r--r--ios/MullvadVPNUITests/Base/BaseUITestCase.swift6
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift38
-rw-r--r--ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift32
-rw-r--r--ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift3
-rw-r--r--ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift6
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift5
-rw-r--r--ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift6
-rw-r--r--ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift30
-rw-r--r--ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift7
21 files changed, 161 insertions, 119 deletions
diff --git a/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift b/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
index 64dbf3a943..d7333dc915 100644
--- a/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
+++ b/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
@@ -63,7 +63,8 @@ extension RelaySelectorStub {
cityCode: "got",
latitude: 0,
longitude: 0
- )
+ ),
+ features: nil
)
return SelectedRelays(
diff --git a/ios/MullvadMockData/MullvadREST/SelectedRelaysStub+Stubs.swift b/ios/MullvadMockData/MullvadREST/SelectedRelaysStub+Stubs.swift
index ce265aa90a..57576da35c 100644
--- a/ios/MullvadMockData/MullvadREST/SelectedRelaysStub+Stubs.swift
+++ b/ios/MullvadMockData/MullvadREST/SelectedRelaysStub+Stubs.swift
@@ -29,7 +29,8 @@ public struct SelectedRelaysStub {
cityCode: "got",
latitude: 42,
longitude: 42
- )
+ ),
+ features: nil
),
retryAttempt: 0
)
diff --git a/ios/MullvadREST/Relay/ObfuscationMethodSelector.swift b/ios/MullvadREST/Relay/ObfuscationMethodSelector.swift
index f73509affe..73944a8093 100644
--- a/ios/MullvadREST/Relay/ObfuscationMethodSelector.swift
+++ b/ios/MullvadREST/Relay/ObfuscationMethodSelector.swift
@@ -11,15 +11,18 @@ import MullvadSettings
public struct ObfuscationMethodSelector {
/// This retry logic used is explained at the following link:
/// https://github.com/mullvad/mullvadvpn-app/blob/main/docs/relay-selector.md#default-constraints-for-tunnel-endpoints
+ ///
+ /// - Note: This method should never return `.automatic`.
public static func obfuscationMethodBy(
connectionAttemptCount: UInt,
tunnelSettings: LatestTunnelSettings
) -> WireGuardObfuscationState {
- // TODO: Revisit this when QUIC obfuscation is added
if tunnelSettings.wireGuardObfuscation.state == .automatic {
- if connectionAttemptCount.isOrdered(nth: 2, forEverySetOf: 3) {
+ if connectionAttemptCount.isOrdered(nth: 2, forEverySetOf: 4) {
.shadowsocks
- } else if connectionAttemptCount.isOrdered(nth: 3, forEverySetOf: 3) {
+ } else if connectionAttemptCount.isOrdered(nth: 3, forEverySetOf: 4) {
+ .quic
+ } else if connectionAttemptCount.isOrdered(nth: 4, forEverySetOf: 4) {
.udpOverTcp
} else {
.off
diff --git a/ios/MullvadREST/Relay/RelayPicking/RelayPicking.swift b/ios/MullvadREST/Relay/RelayPicking/RelayPicking.swift
index 2edd67a7ea..18421de3f3 100644
--- a/ios/MullvadREST/Relay/RelayPicking/RelayPicking.swift
+++ b/ios/MullvadREST/Relay/RelayPicking/RelayPicking.swift
@@ -39,7 +39,8 @@ extension RelayPicking {
return SelectedRelay(
endpoint: match.endpoint,
hostname: match.relay.hostname,
- location: match.location
+ location: match.location,
+ features: match.relay.features
)
}
diff --git a/ios/MullvadREST/Relay/RelaySelectorProtocol.swift b/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
index 7b2f91c761..174ff9ccb1 100644
--- a/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
+++ b/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
@@ -6,7 +6,6 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
-import Foundation
import MullvadSettings
import MullvadTypes
@@ -34,11 +33,15 @@ public struct SelectedRelay: Equatable, Codable, Sendable {
/// Relay geo location.
public let location: Location
+ /// Relay features, such as `DAITA` or `QUIC`.
+ public let features: REST.ServerRelay.Features?
+
/// Designated initializer.
- public init(endpoint: MullvadEndpoint, hostname: String, location: Location) {
+ public init(endpoint: MullvadEndpoint, hostname: String, location: Location, features: REST.ServerRelay.Features?) {
self.endpoint = endpoint
self.hostname = hostname
self.location = location
+ self.features = features
}
}
diff --git a/ios/MullvadRustRuntime/TunnelObfuscator.swift b/ios/MullvadRustRuntime/TunnelObfuscator.swift
index 495930b222..6f74089265 100644
--- a/ios/MullvadRustRuntime/TunnelObfuscator.swift
+++ b/ios/MullvadRustRuntime/TunnelObfuscator.swift
@@ -14,7 +14,7 @@ import Network
public enum TunnelObfuscationProtocol {
case udpOverTcp
case shadowsocks
- case quic
+ case quic(hostname: String, token: String)
}
public protocol TunnelObfuscation {
@@ -74,22 +74,34 @@ public final class TunnelObfuscator: TunnelObfuscation {
stateLock.withLock {
guard !isStarted else { return }
- let obfuscationProtocol = switch obfuscationProtocol {
- case .udpOverTcp: TunnelObfuscatorProtocol(0)
- case .shadowsocks: TunnelObfuscatorProtocol(1)
- case .quic: TunnelObfuscatorProtocol(2)
- }
-
let result = withUnsafeMutablePointer(to: &proxyHandle) { proxyHandlePointer in
let addressData = remoteAddress.rawValue
- return start_tunnel_obfuscator_proxy(
- addressData.map { $0 },
- UInt(addressData.count),
- tcpPort,
- obfuscationProtocol,
- proxyHandlePointer
- )
+ return switch obfuscationProtocol {
+ case .udpOverTcp:
+ start_udp2tcp_obfuscator_proxy(
+ addressData.map { $0 },
+ UInt(addressData.count),
+ tcpPort,
+ proxyHandlePointer
+ )
+ case .shadowsocks:
+ start_shadowsocks_obfuscator_proxy(
+ addressData.map { $0 },
+ UInt(addressData.count),
+ tcpPort,
+ proxyHandlePointer
+ )
+ case let .quic(hostname, token):
+ start_quic_obfuscator_proxy(
+ addressData.map { $0 },
+ UInt(addressData.count),
+ tcpPort,
+ hostname,
+ token,
+ proxyHandlePointer
+ )
+ }
}
assert(result == 0)
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index d4c0bf334f..ad15b43d39 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -17,16 +17,6 @@ enum SwiftAccessMethodKind {
};
typedef uint8_t SwiftAccessMethodKind;
-/**
- * SAFETY: `TunnelObfuscatorProtocol` values must either be `0` or `1`
- */
-enum TunnelObfuscatorProtocol {
- UdpOverTcp = 0,
- Shadowsocks,
- Quic,
-};
-typedef uint8_t TunnelObfuscatorProtocol;
-
typedef struct ApiContext ApiContext;
/**
@@ -930,10 +920,21 @@ int32_t start_shadowsocks_proxy(const uint8_t *forward_address,
*/
int32_t stop_shadowsocks_proxy(struct ProxyHandle *proxy_config);
-int32_t start_tunnel_obfuscator_proxy(const uint8_t *peer_address,
- uintptr_t peer_address_len,
- uint16_t peer_port,
- TunnelObfuscatorProtocol obfuscation_protocol,
- struct ProxyHandle *proxy_handle);
+int32_t start_udp2tcp_obfuscator_proxy(const uint8_t *peer_address,
+ uintptr_t peer_address_len,
+ uint16_t peer_port,
+ struct ProxyHandle *proxy_handle);
+
+int32_t start_shadowsocks_obfuscator_proxy(const uint8_t *peer_address,
+ uintptr_t peer_address_len,
+ uint16_t peer_port,
+ struct ProxyHandle *proxy_handle);
+
+int32_t start_quic_obfuscator_proxy(const uint8_t *peer_address,
+ uintptr_t peer_address_len,
+ uint16_t peer_port,
+ const char *hostname,
+ const char *token,
+ struct ProxyHandle *proxy_handle);
int32_t stop_tunnel_obfuscator_proxy(struct ProxyHandle *proxy_handle);
diff --git a/ios/MullvadSettings/WireGuardObfuscationSettings.swift b/ios/MullvadSettings/WireGuardObfuscationSettings.swift
index 80a4a6d425..b32175030f 100644
--- a/ios/MullvadSettings/WireGuardObfuscationSettings.swift
+++ b/ios/MullvadSettings/WireGuardObfuscationSettings.swift
@@ -18,9 +18,7 @@ public enum WireGuardObfuscationState: Codable, Sendable {
case automatic
case udpOverTcp
case shadowsocks
- #if DEBUG
case quic
- #endif
case off
public init(from decoder: Decoder) throws {
@@ -45,10 +43,8 @@ public enum WireGuardObfuscationState: Codable, Sendable {
self = .udpOverTcp
case .shadowsocks:
self = .shadowsocks
- #if DEBUG
case .quic:
self = .quic
- #endif
case .off:
self = .off
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift
index 6c31610be4..744beededc 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift
@@ -115,7 +115,8 @@ struct ChipContainerView<ViewModel>: View where ViewModel: ChipViewModelProtocol
cityCode: "gbg",
latitude: 1234,
longitude: 1234
- )
+ ),
+ features: nil
),
retryAttempt: 0
),
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
index eca4097f31..91b0a5db98 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
@@ -224,7 +224,6 @@ final class VPNSettingsCellFactory: @preconcurrency CellFactoryProtocol {
self?.delegate?.showDetails(for: .wireguardOverShadowsocks)
}
- #if DEBUG
case .wireGuardObfuscationQuic:
guard let cell = cell as? SelectableSettingsCell else { return }
@@ -238,7 +237,7 @@ final class VPNSettingsCellFactory: @preconcurrency CellFactoryProtocol {
cell.setAccessibilityIdentifier(item.accessibilityIdentifier)
cell.detailTitleLabel.setAccessibilityIdentifier(.wireGuardObfuscationQuic)
cell.applySubCellStyling()
- #endif
+
case .wireGuardObfuscationOff:
guard let cell = cell as? SelectableSettingsCell else { return }
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
index 84df606905..356ae9bf5c 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
@@ -83,9 +83,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardObfuscationAutomatic
case wireGuardObfuscationUdpOverTcp
case wireGuardObfuscationShadowsocks
- #if DEBUG
case wireGuardObfuscationQuic
- #endif
case wireGuardObfuscationOff
case wireGuardObfuscationPort(_ port: WireGuardObfuscationUdpOverTcpPort)
case quantumResistanceAutomatic
@@ -151,10 +149,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
.wireGuardObfuscationUdpOverTcp
case .wireGuardObfuscationShadowsocks:
.wireGuardObfuscationShadowsocks
- #if DEBUG
case .wireGuardObfuscationQuic:
.wireGuardObfuscationQuic
- #endif
case .wireGuardObfuscationOff:
.wireGuardObfuscationOff
case .wireGuardObfuscationPort:
@@ -182,13 +178,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
.wireGuardPort
case .wireGuardCustomPort:
.wireGuardCustomPort
- #if DEBUG
case .wireGuardObfuscationAutomatic, .wireGuardObfuscationOff, .wireGuardObfuscationQuic:
.wireGuardObfuscation
- #else
- case .wireGuardObfuscationAutomatic, .wireGuardObfuscationOff:
- .wireGuardObfuscation
- #endif
case .wireGuardObfuscationUdpOverTcp, .wireGuardObfuscationShadowsocks:
.wireGuardObfuscationOption
case .wireGuardObfuscationPort:
@@ -228,9 +219,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case .off: .wireGuardObfuscationOff
case .on, .udpOverTcp: .wireGuardObfuscationUdpOverTcp
case .shadowsocks: .wireGuardObfuscationShadowsocks
- #if DEBUG
case .quic: .wireGuardObfuscationQuic
- #endif
}
let quantumResistanceItem: Item = switch viewModel.quantumResistance {
diff --git a/ios/MullvadVPNTests/MullvadREST/Relay/ObfuscationMethodSelectorTests.swift b/ios/MullvadVPNTests/MullvadREST/Relay/ObfuscationMethodSelectorTests.swift
index 3694076db0..3526606d72 100644
--- a/ios/MullvadVPNTests/MullvadREST/Relay/ObfuscationMethodSelectorTests.swift
+++ b/ios/MullvadVPNTests/MullvadREST/Relay/ObfuscationMethodSelectorTests.swift
@@ -29,7 +29,7 @@ class ObfuscationMethodSelectorTests: XCTestCase {
connectionAttemptCount: attempt,
tunnelSettings: tunnelSettings
)
- if attempt.isOrdered(nth: 1, forEverySetOf: 3) {
+ if attempt.isOrdered(nth: 1, forEverySetOf: 4) {
XCTAssertEqual(method, .off)
} else {
XCTAssertNotEqual(method, .off)
@@ -53,7 +53,7 @@ class ObfuscationMethodSelectorTests: XCTestCase {
connectionAttemptCount: attempt,
tunnelSettings: tunnelSettings
)
- if attempt.isOrdered(nth: 2, forEverySetOf: 3) {
+ if attempt.isOrdered(nth: 2, forEverySetOf: 4) {
XCTAssertEqual(method, .shadowsocks)
} else {
XCTAssertNotEqual(method, .shadowsocks)
@@ -61,6 +61,30 @@ class ObfuscationMethodSelectorTests: XCTestCase {
}
}
+ func testMethodSelectionQuic() throws {
+ (UInt(0) ... 10).forEach { attempt in
+ tunnelSettings.wireGuardObfuscation = WireGuardObfuscationSettings(state: .quic)
+
+ var method = ObfuscationMethodSelector.obfuscationMethodBy(
+ connectionAttemptCount: attempt,
+ tunnelSettings: tunnelSettings
+ )
+ XCTAssertEqual(method, .quic)
+
+ tunnelSettings.wireGuardObfuscation = WireGuardObfuscationSettings(state: .automatic)
+
+ method = ObfuscationMethodSelector.obfuscationMethodBy(
+ connectionAttemptCount: attempt,
+ tunnelSettings: tunnelSettings
+ )
+ if attempt.isOrdered(nth: 3, forEverySetOf: 4) {
+ XCTAssertEqual(method, .quic)
+ } else {
+ XCTAssertNotEqual(method, .quic)
+ }
+ }
+ }
+
func testMethodSelectionUdpOverTcp() throws {
(UInt(0) ... 10).forEach { attempt in
tunnelSettings.wireGuardObfuscation = WireGuardObfuscationSettings(state: .udpOverTcp)
@@ -77,7 +101,7 @@ class ObfuscationMethodSelectorTests: XCTestCase {
connectionAttemptCount: attempt,
tunnelSettings: tunnelSettings
)
- if attempt.isOrdered(nth: 3, forEverySetOf: 3) {
+ if attempt.isOrdered(nth: 4, forEverySetOf: 4) {
XCTAssertEqual(method, .udpOverTcp)
} else {
XCTAssertNotEqual(method, .udpOverTcp)
diff --git a/ios/MullvadVPNUITests/Base/BaseUITestCase.swift b/ios/MullvadVPNUITests/Base/BaseUITestCase.swift
index df03759409..ebae341a33 100644
--- a/ios/MullvadVPNUITests/Base/BaseUITestCase.swift
+++ b/ios/MullvadVPNUITests/Base/BaseUITestCase.swift
@@ -32,9 +32,9 @@ class BaseUITestCase: XCTestCase {
/// Default relay to use in tests
static let testsDefaultRelayName = "se-got-wg-001"
- static let testsDefaultQuicCountryName = "Relay Software Country"
- static let testsDefaultQuicCityName = "Relay Software city"
- static let testsDefaultQuicRelayName = "se-got-wg-881"
+ static let testsDefaultQuicCountryName = "Ireland"
+ static let testsDefaultQuicCityName = "Dublin"
+ static let testsDefaultQuicRelayName = "ie-dub-wg-001"
/// True when the current test case is capturing packets
private var currentTestCaseShouldCapturePackets = false
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index bf4feac3c1..7cd230e2fb 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -215,40 +215,6 @@ extension PacketTunnelActor {
}
/**
- Reconnect tunnel to new relays. Enters error state on failure.
-
- - Parameters:
- - nextRelay: next relays to connect to
- - reason: reason for reconnect
- */
- private func reconnect(to nextRelays: NextRelays, reason: ActorReconnectReason) async {
- do {
- switch state {
- // There is no connection monitoring going on when exchanging keys.
- // The procedure starts from scratch for each reconnection attempts.
- case .connecting, .connected, .reconnecting, .error, .negotiatingEphemeralPeer:
- switch reason {
- case .connectionLoss:
- // Tunnel monitor is already paused at this point. Avoid calling stop() to prevent the reset of
- // internal state
- break
- case .userInitiated:
- tunnelMonitor.stop()
- }
-
- try await tryStart(nextRelays: nextRelays, reason: reason)
-
- case .disconnected, .disconnecting, .initial:
- break
- }
- } catch {
- logger.error(error: error, message: "Failed to reconnect the tunnel.")
-
- await setErrorStateInternal(with: error)
- }
- }
-
- /**
Entry point for attempting to start the tunnel by performing the following steps:
- Read settings
@@ -430,7 +396,9 @@ extension PacketTunnelActor {
let obfuscated = protocolObfuscator.obfuscate(
connectionState.connectedEndpoint,
settings: settings.tunnelSettings,
- retryAttempts: connectionState.selectedRelays.retryAttempt
+ retryAttempts: connectionState.selectedRelays.retryAttempt,
+ relayFeatures: connectionState.selectedRelays.entry?.features ?? connectionState.selectedRelays.exit
+ .features
)
let transportLayer = protocolObfuscator.transportLayer.map { $0 } ?? .udp
diff --git a/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift b/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
index b6588d6de6..868effbe71 100644
--- a/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
+++ b/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
@@ -21,7 +21,8 @@ public protocol ProtocolObfuscation {
func obfuscate(
_ endpoint: MullvadEndpoint,
settings: LatestTunnelSettings,
- retryAttempts: UInt
+ retryAttempts: UInt,
+ relayFeatures: REST.ServerRelay.Features?
) -> ProtocolObfuscationResult
var transportLayer: TransportLayer? { get }
var remotePort: UInt16 { get }
@@ -46,7 +47,8 @@ public class ProtocolObfuscator<Obfuscator: TunnelObfuscation>: ProtocolObfuscat
public func obfuscate(
_ endpoint: MullvadEndpoint,
settings: LatestTunnelSettings,
- retryAttempts: UInt = 0
+ retryAttempts: UInt = 0,
+ relayFeatures: REST.ServerRelay.Features?
) -> ProtocolObfuscationResult {
let obfuscationMethod = ObfuscationMethodSelector.obfuscationMethodBy(
connectionAttemptCount: retryAttempts,
@@ -55,17 +57,29 @@ public class ProtocolObfuscator<Obfuscator: TunnelObfuscation>: ProtocolObfuscat
remotePort = endpoint.ipv4Relay.port
- guard obfuscationMethod != .off else {
+ #if DEBUG
+ let obfuscationProtocol: TunnelObfuscationProtocol? = switch obfuscationMethod {
+ case .udpOverTcp:
+ .udpOverTcp
+ case .shadowsocks:
+ .shadowsocks
+ case .quic:
+ if let relayFeatures = relayFeatures?.quic {
+ .quic(hostname: relayFeatures.domain, token: relayFeatures.token)
+ } else {
+ nil
+ }
+ default:
+ // This is fine, since ObfuscationMethodSelector.obfuscationMethodBy` above should never
+ // return .automatic.
+ nil
+ }
+
+ guard let obfuscationProtocol else {
tunnelObfuscator = nil
return .init(endpoint: endpoint, method: .off)
}
- #if DEBUG
- let obfuscationProtocol: TunnelObfuscationProtocol = switch obfuscationMethod {
- case .shadowsocks: .shadowsocks
- case .quic: .quic
- default: .udpOverTcp
- }
let obfuscator = Obfuscator(
remoteAddress: endpoint.ipv4Relay.ip,
tcpPort: remotePort,
diff --git a/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift b/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
index 16332271ed..527f6f49ca 100644
--- a/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
+++ b/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
@@ -138,7 +138,8 @@ final class AppMessageHandlerTests: XCTestCase {
exit: SelectedRelay(
endpoint: match.endpoint,
hostname: match.relay.hostname,
- location: match.location
+ location: match.location,
+ features: nil
),
retryAttempt: 0
)
diff --git a/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift
index d340974131..23b231f8d3 100644
--- a/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift
+++ b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift
@@ -51,12 +51,14 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase {
entryRelay = SelectedRelay(
endpoint: entryMatch.endpoint,
hostname: entryMatch.relay.hostname,
- location: entryMatch.location
+ location: entryMatch.location,
+ features: nil
)
exitRelay = SelectedRelay(
endpoint: exitMatch.endpoint,
hostname: exitMatch.relay.hostname,
- location: exitMatch.location
+ location: exitMatch.location,
+ features: nil
)
}
diff --git a/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift b/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift
index 77087782a3..4f65cb65e9 100644
--- a/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/ProtocolObfuscationStub.swift
@@ -6,7 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
-import Foundation
+@testable import MullvadREST
@testable import MullvadSettings
@testable import MullvadTypes
@testable import PacketTunnelCore
@@ -17,7 +17,8 @@ struct ProtocolObfuscationStub: ProtocolObfuscation {
func obfuscate(
_ endpoint: MullvadEndpoint,
settings: LatestTunnelSettings,
- retryAttempts: UInt
+ retryAttempts: UInt,
+ relayFeatures: REST.ServerRelay.Features?
) -> ProtocolObfuscationResult {
.init(endpoint: endpoint, method: .off)
}
diff --git a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift
index c77211b5fa..9d2f5289ca 100644
--- a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift
+++ b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift
@@ -50,12 +50,14 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase {
entryRelay = SelectedRelay(
endpoint: entryMatch.endpoint,
hostname: entryMatch.relay.hostname,
- location: entryMatch.location
+ location: entryMatch.location,
+ features: nil
)
exitRelay = SelectedRelay(
endpoint: exitMatch.endpoint,
hostname: exitMatch.relay.hostname,
- location: exitMatch.location
+ location: exitMatch.location,
+ features: nil
)
}
diff --git a/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift b/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift
index ba506a0519..1ef7dfe597 100644
--- a/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift
+++ b/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift
@@ -6,6 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
+@testable import MullvadREST
@testable import MullvadSettings
@testable import MullvadTypes
import Network
@@ -33,14 +34,14 @@ final class ProtocolObfuscatorTests: XCTestCase {
func testObfuscateOffDoesNotChangeEndpoint() {
let settings = settings(.off, obfuscationPort: .automatic)
- let nonObfuscated = obfuscator.obfuscate(endpoint, settings: settings)
+ let nonObfuscated = obfuscator.obfuscate(endpoint, settings: settings, relayFeatures: nil)
XCTAssertEqual(endpoint, nonObfuscated.endpoint)
}
func testObfuscateUdpOverTcp() throws {
let settings = settings(.udpOverTcp, obfuscationPort: .automatic)
- let obfuscated = obfuscator.obfuscate(endpoint, settings: settings)
+ let obfuscated = obfuscator.obfuscate(endpoint, settings: settings, relayFeatures: nil)
let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub)
validate(obfuscated.endpoint, against: obfuscationProtocol)
@@ -48,7 +49,19 @@ final class ProtocolObfuscatorTests: XCTestCase {
func testObfuscateShadowsocks() throws {
let settings = settings(.shadowsocks, obfuscationPort: .automatic)
- let obfuscated = obfuscator.obfuscate(endpoint, settings: settings)
+ let obfuscated = obfuscator.obfuscate(endpoint, settings: settings, relayFeatures: nil)
+ let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub)
+
+ validate(obfuscated.endpoint, against: obfuscationProtocol)
+ }
+
+ func testObfuscateQuic() throws {
+ let settings = settings(.quic, obfuscationPort: .automatic)
+ let obfuscated = obfuscator.obfuscate(
+ endpoint,
+ settings: settings,
+ relayFeatures: .init(daita: nil, quic: .init(addrIn: [], domain: "", token: ""))
+ )
let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub)
validate(obfuscated.endpoint, against: obfuscationProtocol)
@@ -57,13 +70,18 @@ final class ProtocolObfuscatorTests: XCTestCase {
func testObfuscateAutomatic() throws {
let settings = settings(.automatic, obfuscationPort: .automatic)
- try (UInt(0) ... 2).forEach { attempt in
- let obfuscated = obfuscator.obfuscate(endpoint, settings: settings, retryAttempts: attempt)
+ try (UInt(0) ... 3).forEach { attempt in
+ let obfuscated = obfuscator.obfuscate(
+ endpoint,
+ settings: settings,
+ retryAttempts: attempt,
+ relayFeatures: .init(daita: nil, quic: .init(addrIn: [], domain: "", token: ""))
+ )
switch attempt {
case 0:
XCTAssertEqual(endpoint, obfuscated.endpoint)
- case 1, 2:
+ case 1, 2, 3:
let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub)
validate(obfuscated.endpoint, against: obfuscationProtocol)
default:
diff --git a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift
index 7707ffabf7..314adbee33 100644
--- a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift
+++ b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift
@@ -35,7 +35,12 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase {
numberOfFailedAttempts: 0
)
- exitRelay = SelectedRelay(endpoint: match.endpoint, hostname: match.relay.hostname, location: match.location)
+ exitRelay = SelectedRelay(
+ endpoint: match.endpoint,
+ hostname: match.relay.hostname,
+ location: match.location,
+ features: nil
+ )
}
func testEphemeralPeerExchangeFailsWhenNegotiationCannotStart() async {