diff options
32 files changed, 345 insertions, 237 deletions
diff --git a/docs/relay-selector.md b/docs/relay-selector.md index 2096a21865..3f74ffd6ae 100644 --- a/docs/relay-selector.md +++ b/docs/relay-selector.md @@ -54,9 +54,9 @@ constraints the following default ones will take effect #### Tunnel protocol is Wireguard - The first attempt will connect to a Wireguard relay on a random port -- The second attempt will connect to a Wireguard relay on port 443 -- The third attempt will connect to a Wireguard relay over IPv6 (if IPv6 is configured on the host) on a random port -- The fourth attempt will connect to a Wireguard relay on a random port using Shadowsocks for obfuscation +- The second attempt will connect to a Wireguard relay over IPv6 (if IPv6 is configured on the host) on a random port +- The third attempt will connect to a Wireguard relay on a random port using Shadowsocks for obfuscation +- The fourth attempt will connect to a Wireguard relay using QUIC for obfuscation (if QUIC is implemented) - The fifth attempt will connect to a Wireguard relay on a random port using [UDP2TCP obfuscation](https://github.com/mullvad/udp-over-tcp) - The sixth attempt will connect to a Wireguard relay over IPv6 on a random port using UDP2TCP obfuscation (if IPv6 is configured on the host) @@ -73,8 +73,8 @@ Note: This is not applicable to Android nor iOS. The iOS platform does not support OpenVPN, or connecting to a relay over IPv6. As such, the above algorithm is simplified to the following version: - The first attempt will connect to a Wireguard relay on a random port - - The second attempt will connect to a Wireguard relay on port 443 - - The third attempt will connect to a Wireguard relay on a random port using Shadowsocks for obfuscation + - The second attempt will connect to a Wireguard relay on a random port using Shadowsocks for obfuscation + - The third attempt will connect to a Wireguard relay using QUIC for obfuscation - The fourth attempt will connect to a Wireguard relay on a random port using [UDP2TCP obfuscation](https://github.com/mullvad/udp-over-tcp) ### Random Ports for UDP2TCP and Shadowsocks 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 { diff --git a/mullvad-ios/src/api_client/access_method_settings.rs b/mullvad-ios/src/api_client/access_method_settings.rs index 35a90d1f0c..2870047e24 100644 --- a/mullvad-ios/src/api_client/access_method_settings.rs +++ b/mullvad-ios/src/api_client/access_method_settings.rs @@ -11,7 +11,7 @@ use mullvad_types::access_method::{ }; use talpid_types::net::proxy::{self, Shadowsocks, Socks5Remote}; -use super::helpers::convert_c_string; +use crate::get_string; /// Converts parameters into a `Box<AccessMethodSetting>` raw representation that /// can be passed across the FFI boundary @@ -54,9 +54,9 @@ fn convert_builtin_access_method_setting_inner( proxy_configuration: *const c_void, ) -> Option<AccessMethodSetting> { // SAFETY: See `convert_builtin_access_method_setting` - let id = Id::from_string(unsafe { convert_c_string(unique_identifier) })?; + let id = unsafe { Id::from_string(get_string(unique_identifier))? }; // SAFETY: See `convert_builtin_access_method_setting` - let name = unsafe { convert_c_string(name) }; + let name = unsafe { get_string(name) }; match method_kind { SwiftAccessMethodKind::KindDirect => Some(AccessMethodSetting::with_id( id, diff --git a/mullvad-ios/src/api_client/address_cache_provider.rs b/mullvad-ios/src/api_client/address_cache_provider.rs index f813683445..9d5f656c62 100644 --- a/mullvad-ios/src/api_client/address_cache_provider.rs +++ b/mullvad-ios/src/api_client/address_cache_provider.rs @@ -1,7 +1,8 @@ -use super::helpers::convert_c_string; use libc::c_char; use std::{ffi::c_void, net::SocketAddr}; +use super::get_string; + extern "C" { /// Return the latest available endpoint, or a default one if none are cached /// @@ -58,7 +59,7 @@ impl SwiftAddressCacheProviderContext { // SAFETY: The pointer contained in the late deallocator returned by `swift_get_cached_endpoint` // is guaranteed to point to a valid UTF-8 String // It is also guaranteed to be a valid representation of either an IPv4 or IPv6 address - let cached_address = unsafe { convert_c_string(deallocator.ptr) } + let cached_address = unsafe { get_string(deallocator.ptr) } .parse() .expect("Invalid socket address in cache"); diff --git a/mullvad-ios/src/api_client/helpers.rs b/mullvad-ios/src/api_client/helpers.rs index f331e56a9b..556b679d71 100644 --- a/mullvad-ios/src/api_client/helpers.rs +++ b/mullvad-ios/src/api_client/helpers.rs @@ -1,10 +1,12 @@ use std::{ - ffi::{c_char, c_void, CStr}, + ffi::{c_char, c_void}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, }; use talpid_types::net::proxy::{Shadowsocks, Socks5Remote, SocksAuth}; +use super::get_string; + /// Constructs a new IP address from a pointer containing bytes representing an IP address. /// /// SAFETY: `addr` pointer must be non-null, aligned, and point to at least addr_len bytes @@ -30,16 +32,6 @@ pub(crate) unsafe fn parse_ip_addr(addr: *const u8, addr_len: usize) -> Option<I } } -/// Converts a pointer to a C style string into an owned Rust `String` -/// -/// # SAFETY -/// `c_str` must point to a valid, null terminated C string. -pub unsafe fn convert_c_string(c_str: *const c_char) -> String { - // SAFETY: c_str points to a valid region of memory and contains a null terminator. - let str = unsafe { CStr::from_ptr(c_str) }; - String::from_utf8_lossy(str.to_bytes()).into_owned() -} - /// Converts parameters into a boxed `Shadowsocks` configuration that is safe /// to send across the FFI boundary /// @@ -60,8 +52,8 @@ pub unsafe extern "C" fn new_shadowsocks_access_method_setting( return std::ptr::null(); }; - let password = convert_c_string(c_password); - let cipher = convert_c_string(c_cipher); + let password = get_string(c_password); + let cipher = get_string(c_cipher); let shadowsocks_configuration = Shadowsocks { endpoint, @@ -97,8 +89,8 @@ pub unsafe extern "C" fn new_socks5_access_method_setting( if c_username.is_null() || c_password.is_null() { None } else { - let username = convert_c_string(c_username); - let password = convert_c_string(c_password); + let username = get_string(c_username); + let password = get_string(c_password); SocksAuth::new(username, password).ok() } }; diff --git a/mullvad-ios/src/api_client/mod.rs b/mullvad-ios/src/api_client/mod.rs index 4e875fe4c6..1c45fb3397 100644 --- a/mullvad-ios/src/api_client/mod.rs +++ b/mullvad-ios/src/api_client/mod.rs @@ -1,9 +1,9 @@ -use std::{ffi::c_char, ffi::CStr, future::Future, sync::Arc}; +use std::{ffi::c_char, future::Future, sync::Arc}; +use crate::get_string; use access_method_resolver::SwiftAccessMethodResolver; use access_method_settings::SwiftAccessMethodSettingsWrapper; use address_cache_provider::SwiftAddressCacheWrapper; -use helpers::convert_c_string; use mullvad_api::{ access_mode::{AccessModeSelector, AccessModeSelectorHandle}, rest::{self, MullvadRestHandle}, @@ -108,8 +108,8 @@ pub unsafe extern "C" fn mullvad_api_use_access_method( access_method_id: *const c_char, ) { let api_context = api_context.rust_context(); - // SAFETY: See Safety notes for `convert_c_string` - let id = unsafe { convert_c_string(access_method_id) }; + // SAFETY: See Safety notes for `get_string` + let id = get_string(access_method_id); let Some(id) = Id::from_string(id) else { return; @@ -214,14 +214,9 @@ pub extern "C" fn mullvad_api_init_inner( settings_provider: SwiftAccessMethodSettingsWrapper, address_cache: SwiftAddressCacheWrapper, ) -> SwiftApiContext { - // Safety: See notes for `convert_c_string` - let (host, address, domain) = unsafe { - ( - convert_c_string(host), - convert_c_string(address), - convert_c_string(domain), - ) - }; + // Safety: See notes for `get_string` + let (host, address, domain) = + unsafe { (get_string(host), get_string(address), get_string(domain)) }; // The iOS client provides a different default endpoint based on its configuration // Debug and Release builds use the standard endpoints @@ -314,17 +309,3 @@ where retry_future(future_factory, should_retry, retry_strategy.delays()).await } - -/// Try to convert a C string to an owned [String]. if `ptr` is null, an empty [String] is -/// returned. -/// -/// # Safety -/// - `ptr` must uphold all safety invariants as required by [CStr::from_ptr]. -fn get_string(ptr: *const c_char) -> String { - if ptr.is_null() { - return String::new(); - } - // Safety: See function doc comment. - let cstr = unsafe { CStr::from_ptr(ptr) }; - cstr.to_str().map(ToOwned::to_owned).unwrap_or_default() -} diff --git a/mullvad-ios/src/api_client/storekit.rs b/mullvad-ios/src/api_client/storekit.rs index 61967eec9b..994e6628d7 100644 --- a/mullvad-ios/src/api_client/storekit.rs +++ b/mullvad-ios/src/api_client/storekit.rs @@ -9,8 +9,7 @@ use mullvad_types::account::AccountNumber; use super::{ cancellation::{RequestCancelHandle, SwiftCancelHandle}, completion::{CompletionCookie, SwiftCompletionHandler}, - do_request, - helpers::convert_c_string, + do_request, get_string, response::SwiftMullvadApiResponse, retry_strategy::{RetryStrategy, SwiftRetryStrategy}, SwiftApiContext, @@ -58,7 +57,7 @@ pub unsafe extern "C" fn mullvad_ios_legacy_storekit_payment( let completion = completion_handler.clone(); // SAFETY: See param documentation for `account_number`. - let account_number = unsafe { AccountNumber::from(convert_c_string(account_number)) }; + let account_number = AccountNumber::from(get_string(account_number)); // SAFETY: See param documentation for `body`. let body = unsafe { std::slice::from_raw_parts(body, body_size) }.to_vec(); @@ -133,7 +132,7 @@ pub unsafe extern "C" fn mullvad_ios_init_storekit_payment( let completion = completion_handler.clone(); // SAFETY: See param documentation for `account_number`. - let account_number = unsafe { AccountNumber::from(convert_c_string(account_number)) }; + let account_number = AccountNumber::from(get_string(account_number)); let task = tokio_handle.spawn(async move { match mullvad_ios_init_storekit_payment_inner( @@ -208,7 +207,7 @@ pub unsafe extern "C" fn mullvad_ios_check_storekit_payment( let completion = completion_handler.clone(); // SAFETY: See param documentation for `account_number`. - let account_number = unsafe { AccountNumber::from(convert_c_string(account_number)) }; + let account_number = AccountNumber::from(get_string(account_number)); // SAFETY: See param documentation for `body`. let body = unsafe { std::slice::from_raw_parts(body, body_size) }.to_vec(); diff --git a/mullvad-ios/src/lib.rs b/mullvad-ios/src/lib.rs index fa23672e29..b98960f158 100644 --- a/mullvad-ios/src/lib.rs +++ b/mullvad-ios/src/lib.rs @@ -1,4 +1,9 @@ #![cfg(target_os = "ios")] +use libc::c_char; +use std::ffi::CStr; +use std::sync::OnceLock; +use tokio::runtime::{Builder, Handle, Runtime}; + mod api_client; mod encrypted_dns_proxy; mod ephemeral_peer_proxy; @@ -14,23 +19,30 @@ pub struct ProxyHandle { #[unsafe(no_mangle)] pub static CONFIG_SERVICE_PORT: u16 = talpid_tunnel_config_client::CONFIG_SERVICE_PORT; -mod ios { - use std::sync::OnceLock; - use tokio::runtime::{Builder, Handle, Runtime}; - - static RUNTIME: OnceLock<Result<Runtime, String>> = OnceLock::new(); +static RUNTIME: OnceLock<Result<Runtime, String>> = OnceLock::new(); - pub fn mullvad_ios_runtime() -> Result<Handle, String> { - match RUNTIME.get_or_init(|| { - Builder::new_multi_thread() - .enable_all() - .build() - .map_err(|error| ToString::to_string(&error)) - }) { - Ok(runtime) => Ok(runtime.handle().clone()), - Err(error) => Err(error.clone()), - } +fn mullvad_ios_runtime() -> Result<Handle, String> { + match RUNTIME.get_or_init(|| { + Builder::new_multi_thread() + .enable_all() + .build() + .map_err(|error| ToString::to_string(&error)) + }) { + Ok(runtime) => Ok(runtime.handle().clone()), + Err(error) => Err(error.clone()), } } -use ios::*; +/// Try to convert a C string to an owned [String]. if `ptr` is null, an empty [String] is +/// returned. +/// +/// # Safety +/// - `ptr` must uphold all safety invariants as required by [CStr::from_ptr]. +unsafe fn get_string(ptr: *const c_char) -> String { + if ptr.is_null() { + return String::new(); + } + // Safety: See function doc comment. + let cstr = unsafe { CStr::from_ptr(ptr) }; + cstr.to_str().map(ToOwned::to_owned).unwrap_or_default() +} diff --git a/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs b/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs index 732d889e89..849d4336cc 100644 --- a/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs +++ b/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs @@ -1,52 +1,131 @@ +use libc::c_char; + use super::{TunnelObfuscatorHandle, TunnelObfuscatorRuntime}; use crate::ProxyHandle; use std::{net::SocketAddr, sync::Once}; -use crate::api_client::helpers::parse_ip_addr; +use crate::{api_client::helpers::parse_ip_addr, get_string}; static INIT_LOGGING: Once = Once::new(); -/// SAFETY: `TunnelObfuscatorProtocol` values must either be `0` or `1` -#[repr(u8)] -pub enum TunnelObfuscatorProtocol { - UdpOverTcp = 0, - Shadowsocks, - Quic, +macro_rules! throw_int_error { + ($result:expr) => { + match $result { + Ok(value) => value, + Err(value) => return value, + } + }; +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn start_udp2tcp_obfuscator_proxy( + peer_address: *const u8, + peer_address_len: usize, + peer_port: u16, + proxy_handle: *mut ProxyHandle, +) -> i32 { + init_logging(); + + let peer_sock_addr = throw_int_error!(get_socket_address( + peer_address, + peer_address_len, + peer_port + )); + let result = TunnelObfuscatorRuntime::new_udp2tcp(peer_sock_addr).run(); + + start(proxy_handle, result) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn start_shadowsocks_obfuscator_proxy( + peer_address: *const u8, + peer_address_len: usize, + peer_port: u16, + proxy_handle: *mut ProxyHandle, +) -> i32 { + init_logging(); + + let peer_sock_addr = throw_int_error!(get_socket_address( + peer_address, + peer_address_len, + peer_port + )); + let result = TunnelObfuscatorRuntime::new_shadowsocks(peer_sock_addr).run(); + + start(proxy_handle, result) } #[unsafe(no_mangle)] -pub unsafe extern "C" fn start_tunnel_obfuscator_proxy( +pub unsafe extern "C" fn start_quic_obfuscator_proxy( peer_address: *const u8, peer_address_len: usize, peer_port: u16, - obfuscation_protocol: TunnelObfuscatorProtocol, + hostname: *const c_char, + token: *const c_char, proxy_handle: *mut ProxyHandle, ) -> i32 { + init_logging(); + + let peer_sock_addr = throw_int_error!(get_socket_address( + peer_address, + peer_address_len, + peer_port + )); + let hostname = get_string(hostname); + let token = get_string(token); + let result = TunnelObfuscatorRuntime::new_quic(peer_sock_addr, hostname, token).run(); + + start(proxy_handle, result) +} + +fn init_logging() { INIT_LOGGING.call_once(|| { let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.TunnelObfuscatorProxy") .level_filter(log::LevelFilter::Info) .init(); }); +} - let peer_sock_addr: SocketAddr = - if let Some(ip_address) = parse_ip_addr(peer_address, peer_address_len) { +/// Constructs a new IP address from a pointer containing bytes representing an IP address. +/// +/// SAFETY: `addr` pointer must be non-null, aligned, and point to at least addr_len bytes +unsafe fn get_socket_address( + peer_address: *const u8, + peer_address_len: usize, + peer_port: u16, +) -> Result<SocketAddr, i32> { + let peer_sock_addr = + // SAFETY: See notes for `parse_ip_addr`. + if let Some(ip_address) = unsafe { parse_ip_addr(peer_address, peer_address_len) } { SocketAddr::new(ip_address, peer_port) } else { - return -1; + return Err(-1); }; + Ok(peer_sock_addr) +} - let result = TunnelObfuscatorRuntime::new(peer_sock_addr, obfuscation_protocol).run(); - +/// # Safety +/// +/// Behavior is undefined if any of the following conditions are violated: +/// +/// * `proxy_handle` must be [valid] for writes. +/// * `proxy_handle` must be properly aligned. Use [`write_unaligned`] if this is not the +/// case. +unsafe fn start( + proxy_handle: *mut ProxyHandle, + result: Result<(SocketAddr, TunnelObfuscatorHandle), std::io::Error>, +) -> i32 { match result { Ok((local_endpoint, obfuscator_handle)) => { let boxed_handle = Box::new(obfuscator_handle); - std::ptr::write( - proxy_handle, - ProxyHandle { - context: Box::into_raw(boxed_handle) as *mut _, - port: local_endpoint.port(), - }, - ); + let source_handle = ProxyHandle { + context: Box::into_raw(boxed_handle) as *mut _, + port: local_endpoint.port(), + }; + + // SAFETY: Caller guarantees that this pointer is valid. + unsafe { std::ptr::write(proxy_handle, source_handle) }; + 0 } Err(err) => { diff --git a/mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs b/mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs index bb208db57e..5faeab7162 100644 --- a/mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs +++ b/mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs @@ -1,4 +1,3 @@ -use ffi::TunnelObfuscatorProtocol; use std::{ io, net::{Ipv4Addr, SocketAddr}, @@ -17,27 +16,26 @@ pub struct TunnelObfuscatorRuntime { } impl TunnelObfuscatorRuntime { - pub fn new(peer: SocketAddr, obfuscation_protocol: TunnelObfuscatorProtocol) -> Self { - let settings: ObfuscationSettings = match obfuscation_protocol { - TunnelObfuscatorProtocol::UdpOverTcp => { - ObfuscationSettings::Udp2Tcp(udp2tcp::Settings { peer }) - } - TunnelObfuscatorProtocol::Shadowsocks => { - ObfuscationSettings::Shadowsocks(shadowsocks::Settings { - shadowsocks_endpoint: peer, - wireguard_endpoint: SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)), - }) - } - TunnelObfuscatorProtocol::Quic => { - ObfuscationSettings::Quic(quic::Settings { - quic_endpoint: peer, - wireguard_endpoint: SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)), - // TODO: fetch the real hostname from the relay list - hostname: "www.mullvad.net".to_string(), - }) - } - }; + pub fn new_udp2tcp(peer: SocketAddr) -> Self { + let settings = ObfuscationSettings::Udp2Tcp(udp2tcp::Settings { peer }); + Self { settings } + } + pub fn new_shadowsocks(peer: SocketAddr) -> Self { + let settings = ObfuscationSettings::Shadowsocks(shadowsocks::Settings { + shadowsocks_endpoint: peer, + wireguard_endpoint: SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)), + }); + Self { settings } + } + + pub fn new_quic(peer: SocketAddr, hostname: String, token: String) -> Self { + let settings = ObfuscationSettings::Quic(quic::Settings { + quic_endpoint: peer, + wireguard_endpoint: SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)), + hostname, + token, + }); Self { settings } } diff --git a/talpid-wireguard/src/obfuscation.rs b/talpid-wireguard/src/obfuscation.rs index b214e89ae0..6677d630cb 100644 --- a/talpid-wireguard/src/obfuscation.rs +++ b/talpid-wireguard/src/obfuscation.rs @@ -16,6 +16,9 @@ use tunnel_obfuscation::{ create_obfuscator, quic, shadowsocks, udp2tcp, Settings as ObfuscationSettings, }; +/// Test authentication header to set for the CONNECT request. +const AUTH_HEADER: &str = "test"; + /// Begin running obfuscation machine, if configured. This function will patch `config`'s endpoint /// to point to an endpoint on localhost pub async fn apply_obfuscation_config( @@ -101,6 +104,7 @@ fn settings_from_config( quic_endpoint: *endpoint, wireguard_endpoint: SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)), hostname: hostname.to_owned(), + token: AUTH_HEADER.to_owned(), #[cfg(target_os = "linux")] fwmark, }) diff --git a/tunnel-obfuscation/src/quic.rs b/tunnel-obfuscation/src/quic.rs index 1230e53ad1..e0b16ecb66 100644 --- a/tunnel-obfuscation/src/quic.rs +++ b/tunnel-obfuscation/src/quic.rs @@ -12,9 +12,6 @@ use crate::Obfuscator; type Result<T> = std::result::Result<T, Error>; -/// Authentication header to set for the CONNECT request -const AUTH_HEADER: &str = "Bearer test"; - #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Failed to bind UDP socket")] @@ -36,6 +33,8 @@ pub struct Settings { pub wireguard_endpoint: SocketAddr, /// Hostname to use for QUIC pub hostname: String, + /// Auth token to use for QUIC. Must NOT be prefixed with "Bearer". + pub token: String, /// fwmark to apply to use for the QUIC connection #[cfg(target_os = "linux")] pub fwmark: Option<u32>, @@ -48,6 +47,7 @@ impl Quic { .map_err(Error::BindError)?; let local_endpoint = local_socket.local_addr().unwrap(); + let token = settings.token.clone(); let config_builder = ClientConfig::builder() .client_socket(local_socket) @@ -55,7 +55,7 @@ impl Quic { .server_addr(settings.quic_endpoint) .server_host(settings.hostname.clone()) .target_addr(settings.wireguard_endpoint) - .auth_header(Some(AUTH_HEADER.to_owned())); + .auth_header(Some(format!("Bearer {token}").to_owned())); #[cfg(target_os = "linux")] let config_builder = config_builder.fwmark(settings.fwmark); |
