diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index a5dee54a5183..83562ad95f35 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -892,7 +892,7 @@ F072D3D22C071AD100906F64 /* ShadowsocksLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */; }; F073FCB32C6617D70062EA1D /* TunnelStore+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F073FCB22C6617D70062EA1D /* TunnelStore+Stubs.swift */; }; F07751552C50F149006E6A12 /* EphemeralPeerExchangeActorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C4C9BF2C495E7500A79006 /* EphemeralPeerExchangeActorStub.swift */; }; - F07751572C50F149006E6A12 /* PostQuantumKeyExchangingPipelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F053F4B92C4A94D300FBD937 /* PostQuantumKeyExchangingPipelineTests.swift */; }; + F07751572C50F149006E6A12 /* EphemeralPeerExchangingPipelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F053F4B92C4A94D300FBD937 /* EphemeralPeerExchangingPipelineTests.swift */; }; F07751582C50F149006E6A12 /* MultiHopEphemeralPeerExchangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C4C9BD2C49477B00A79006 /* MultiHopEphemeralPeerExchangerTests.swift */; }; F07751592C50F149006E6A12 /* SingleHopEphemeralPeerExchangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A163882C47B46300592300 /* SingleHopEphemeralPeerExchangerTests.swift */; }; F07B53572C53B5270024F547 /* LocalNetworkIPs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07B53562C53B5270024F547 /* LocalNetworkIPs.swift */; }; @@ -2087,7 +2087,7 @@ F050AE5D2B739A73003F4EDB /* LocationDataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataSourceProtocol.swift; sourceTree = ""; }; F050AE5F2B73A41E003F4EDB /* AllLocationDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllLocationDataSource.swift; sourceTree = ""; }; F050AE612B74DBAC003F4EDB /* CustomListsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsDataSource.swift; sourceTree = ""; }; - F053F4B92C4A94D300FBD937 /* PostQuantumKeyExchangingPipelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangingPipelineTests.swift; sourceTree = ""; }; + F053F4B92C4A94D300FBD937 /* EphemeralPeerExchangingPipelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerExchangingPipelineTests.swift; sourceTree = ""; }; F05769B82C6656E400D9778B /* TunnelSettingsPropagator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsPropagator.swift; sourceTree = ""; }; F05769BA2C6661EE00D9778B /* TunnelSettingsStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsStrategy.swift; sourceTree = ""; }; F05919742C45194B00C301F3 /* EphemeralPeerKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerKey.swift; sourceTree = ""; }; @@ -3362,14 +3362,12 @@ isa = PBXGroup; children = ( 7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */, + F053F4B92C4A94D300FBD937 /* EphemeralPeerExchangingPipelineTests.swift */, 586C14572AC463BB00245C01 /* EventChannelTests.swift */, - F0FBD98E2C4A60CC00EE5323 /* KeyExchangingResultStub.swift */, 58EC067D2A8D2B0700BEB973 /* Mocks */, F0C4C9BD2C49477B00A79006 /* MultiHopEphemeralPeerExchangerTests.swift */, 58FE25D32AA729B5003D1918 /* PacketTunnelActorTests.swift */, 58C7A46F2A8649ED0060C66F /* PingerTests.swift */, - F0C4C9BF2C495E7500A79006 /* EphemeralPeerExchangeActorStub.swift */, - F053F4B92C4A94D300FBD937 /* PostQuantumKeyExchangingPipelineTests.swift */, A97D25B12B0CB02D00946B2D /* ProtocolObfuscatorTests.swift */, F0A163882C47B46300592300 /* SingleHopEphemeralPeerExchangerTests.swift */, 5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */, @@ -3659,6 +3657,8 @@ children = ( 58F7753C2AB8473200425B47 /* BlockedStateErrorMapperStub.swift */, 581F23AC2A8CF92100788AB6 /* DefaultPathObserverFake.swift */, + F0C4C9BF2C495E7500A79006 /* EphemeralPeerExchangeActorStub.swift */, + F0FBD98E2C4A60CC00EE5323 /* KeyExchangingResultStub.swift */, 58EC067B2A8D2A0B00BEB973 /* NetworkCounters.swift */, 5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */, 7AD0AA1B2AD6A63F00119E10 /* PacketTunnelActorStub.swift */, @@ -5531,7 +5531,7 @@ F0ACE3372BE517F1006D5333 /* ServerRelaysResponse+Stubs.swift in Sources */, 58F7753D2AB8473200425B47 /* BlockedStateErrorMapperStub.swift in Sources */, 58FE25D42AA729B5003D1918 /* PacketTunnelActorTests.swift in Sources */, - F07751572C50F149006E6A12 /* PostQuantumKeyExchangingPipelineTests.swift in Sources */, + F07751572C50F149006E6A12 /* EphemeralPeerExchangingPipelineTests.swift in Sources */, 7A3FD1B52AD4465A0042BEA6 /* AppMessageHandlerTests.swift in Sources */, 58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */, A97D25B22B0CB02D00946B2D /* ProtocolObfuscatorTests.swift in Sources */, diff --git a/ios/PacketTunnelCoreTests/PostQuantumKeyExchangingPipelineTests.swift b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift similarity index 57% rename from ios/PacketTunnelCoreTests/PostQuantumKeyExchangingPipelineTests.swift rename to ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift index 50551db1d545..e5a679ec58c8 100644 --- a/ios/PacketTunnelCoreTests/PostQuantumKeyExchangingPipelineTests.swift +++ b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift @@ -1,5 +1,5 @@ // -// PostQuantumKeyExchangingPipelineTests.swift +// EphemeralPeerExchangingPipelineTests.swift // MullvadPostQuantumTests // // Created by Mojgan on 2024-07-19. @@ -13,7 +13,7 @@ @testable import WireGuardKitTypes import XCTest -final class PostQuantumKeyExchangingPipelineTests: XCTestCase { +final class EphemeralPeerExchangingPipelineTests: XCTestCase { var entryRelay: SelectedRelay! var exitRelay: SelectedRelay! var relayConstraints: RelayConstraints! @@ -60,7 +60,7 @@ final class PostQuantumKeyExchangingPipelineTests: XCTestCase { ) } - func testSingleHopKeyExchange() throws { + func testSingleHopPostQuantumKeyExchange() throws { let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") reconfigurationExpectation.expectedFulfillmentCount = 2 @@ -81,17 +81,37 @@ final class PostQuantumKeyExchangingPipelineTests: XCTestCase { postQuantumKeyExchangingPipeline.receivePostQuantumKey(preSharedKey, ephemeralKey: privateKey) }) - let connectionState = ObservedConnectionState( - selectedRelays: SelectedRelays(entry: nil, exit: exitRelay, retryAttempt: 0), - relayConstraints: relayConstraints, - networkReachability: NetworkReachability.reachable, - connectionAttemptCount: 0, - transportLayer: .udp, - remotePort: 1234, - isPostQuantum: true, - isDaitaEnabled: false + let connectionState = stubConnectionState(enableMultiHop: false, enablePostQuantum: true, enableDaita: false) + postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) + + wait( + for: [reconfigurationExpectation, negotiationSuccessful], + timeout: .UnitTest.invertedTimeout ) + } + + func testSingleHopDaitaPeerExchange() throws { + let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") + reconfigurationExpectation.expectedFulfillmentCount = 2 + + let negotiationSuccessful = expectation(description: "Negotiation succeeded.") + negotiationSuccessful.expectedFulfillmentCount = 1 + let keyExchangeActor = EphemeralPeerExchangeActorStub() + let preSharedKey = try XCTUnwrap(PreSharedKey(hexKey: PrivateKey().hexKey)) + keyExchangeActor.result = .success((preSharedKey, PrivateKey())) + + let postQuantumKeyExchangingPipeline = EphemeralPeerExchangingPipeline(keyExchangeActor) { _ in + reconfigurationExpectation.fulfill() + } onFinish: { + negotiationSuccessful.fulfill() + } + + keyExchangeActor.delegate = KeyExchangingResultStub(onReceiveEphemeralPeerPrivateKey: { privateKey in + postQuantumKeyExchangingPipeline.receiveEphemeralPeerPrivateKey(privateKey) + }) + + let connectionState = stubConnectionState(enableMultiHop: false, enablePostQuantum: false, enableDaita: true) postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) wait( @@ -100,7 +120,7 @@ final class PostQuantumKeyExchangingPipelineTests: XCTestCase { ) } - func testMultiHopKeyExchange() throws { + func testMultiHopPostQuantumKeyExchange() throws { let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") reconfigurationExpectation.expectedFulfillmentCount = 3 @@ -121,17 +141,37 @@ final class PostQuantumKeyExchangingPipelineTests: XCTestCase { postQuantumKeyExchangingPipeline.receivePostQuantumKey(preSharedKey, ephemeralKey: privateKey) }) - let connectionState = ObservedConnectionState( - selectedRelays: SelectedRelays(entry: entryRelay, exit: exitRelay, retryAttempt: 0), - relayConstraints: relayConstraints, - networkReachability: NetworkReachability.reachable, - connectionAttemptCount: 0, - transportLayer: .udp, - remotePort: 1234, - isPostQuantum: true, - isDaitaEnabled: false + let connectionState = stubConnectionState(enableMultiHop: true, enablePostQuantum: true, enableDaita: false) + postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) + + wait( + for: [reconfigurationExpectation, negotiationSuccessful], + timeout: .UnitTest.invertedTimeout ) + } + + func testMultiHopDaitaExchange() throws { + let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") + reconfigurationExpectation.expectedFulfillmentCount = 3 + + let negotiationSuccessful = expectation(description: "Negotiation succeeded.") + negotiationSuccessful.expectedFulfillmentCount = 1 + + let keyExchangeActor = EphemeralPeerExchangeActorStub() + let preSharedKey = try XCTUnwrap(PreSharedKey(hexKey: PrivateKey().hexKey)) + keyExchangeActor.result = .success((preSharedKey, PrivateKey())) + let postQuantumKeyExchangingPipeline = EphemeralPeerExchangingPipeline(keyExchangeActor) { _ in + reconfigurationExpectation.fulfill() + } onFinish: { + negotiationSuccessful.fulfill() + } + + keyExchangeActor.delegate = KeyExchangingResultStub(onReceiveEphemeralPeerPrivateKey: { privateKey in + postQuantumKeyExchangingPipeline.receiveEphemeralPeerPrivateKey(privateKey) + }) + + let connectionState = stubConnectionState(enableMultiHop: true, enablePostQuantum: false, enableDaita: true) postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) wait( @@ -139,4 +179,21 @@ final class PostQuantumKeyExchangingPipelineTests: XCTestCase { timeout: .UnitTest.invertedTimeout ) } + + func stubConnectionState( + enableMultiHop: Bool, + enablePostQuantum: Bool, + enableDaita: Bool + ) -> ObservedConnectionState { + ObservedConnectionState( + selectedRelays: SelectedRelays(entry: enableMultiHop ? entryRelay : nil, exit: exitRelay, retryAttempt: 0), + relayConstraints: relayConstraints, + networkReachability: NetworkReachability.reachable, + connectionAttemptCount: 0, + transportLayer: .udp, + remotePort: 1234, + isPostQuantum: enablePostQuantum, + isDaitaEnabled: enableDaita + ) + } } diff --git a/ios/PacketTunnelCoreTests/EphemeralPeerExchangeActorStub.swift b/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift similarity index 100% rename from ios/PacketTunnelCoreTests/EphemeralPeerExchangeActorStub.swift rename to ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift diff --git a/ios/PacketTunnelCoreTests/KeyExchangingResultStub.swift b/ios/PacketTunnelCoreTests/Mocks/KeyExchangingResultStub.swift similarity index 100% rename from ios/PacketTunnelCoreTests/KeyExchangingResultStub.swift rename to ios/PacketTunnelCoreTests/Mocks/KeyExchangingResultStub.swift diff --git a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift index a53e0e87bcca..95436edb4c9c 100644 --- a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift +++ b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift @@ -134,7 +134,6 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { ) } - // TODO: Write more tests func testEphemeralPeerExchangeSuccessWhenDaitaNegotiationStarts() throws { let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.") unexpectedNegotiationFailure.isInverted = true diff --git a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift index 92a1e01f2334..e94ca0889e06 100644 --- a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift +++ b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift @@ -38,7 +38,7 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { exitRelay = SelectedRelay(endpoint: match.endpoint, hostname: match.relay.hostname, location: match.location) } - func testKeyExchangeFailsWhenNegotiationCannotStart() { + func testEphemeralPeerExchangeFailsWhenNegotiationCannotStart() { let expectedNegotiationFailure = expectation(description: "Negotiation failed.") let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") @@ -74,7 +74,7 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { ) } - func testKeyExchangeSuccessWhenNegotiationStart() throws { + func testEphemeralPeerExchangeSuccessWhenPostQuantumNegotiationStarts() throws { let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.") unexpectedNegotiationFailure.isInverted = true @@ -110,4 +110,41 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { timeout: .UnitTest.invertedTimeout ) } + + func testEphemeralPeerExchangeSuccessWhenDaitaNegotiationStarts() throws { + let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.") + unexpectedNegotiationFailure.isInverted = true + + let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") + reconfigurationExpectation.expectedFulfillmentCount = 2 + + let negotiationSuccessful = expectation(description: "Negotiation succeeded.") + negotiationSuccessful.expectedFulfillmentCount = 1 + + let peerExchangeActor = EphemeralPeerExchangeActorStub() + let preSharedKey = try XCTUnwrap(PreSharedKey(hexKey: PrivateKey().hexKey)) + peerExchangeActor.result = .success((preSharedKey, PrivateKey())) + + let multiHopPeerExchanger = SingleHopEphemeralPeerExchanger( + exit: exitRelay, + devicePrivateKey: PrivateKey(), + keyExchanger: peerExchangeActor, + enablePostQuantum: false, + enableDaita: true + ) { _ in + reconfigurationExpectation.fulfill() + } onFinish: { + negotiationSuccessful.fulfill() + } + + peerExchangeActor.delegate = KeyExchangingResultStub(onReceiveEphemeralPeerPrivateKey: { ephemeralKey in + multiHopPeerExchanger.receiveEphemeralPeerPrivateKey(ephemeralKey) + }) + multiHopPeerExchanger.start() + + wait( + for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], + timeout: .UnitTest.invertedTimeout + ) + } } diff --git a/mullvad-ios/src/post_quantum_proxy/ios_runtime.rs b/mullvad-ios/src/post_quantum_proxy/ios_runtime.rs index c9e56edaeca2..1b4380c67061 100644 --- a/mullvad-ios/src/post_quantum_proxy/ios_runtime.rs +++ b/mullvad-ios/src/post_quantum_proxy/ios_runtime.rs @@ -22,7 +22,6 @@ pub unsafe fn run_post_quantum_psk_exchange( packet_tunnel: *const c_void, tcp_connection: *const c_void, peer_exchange_timeout: u64, - tokio_handle: TokioHandle, enable_post_quantum: bool, enable_daita: bool, ) -> Result { @@ -40,6 +39,14 @@ pub unsafe fn run_post_quantum_psk_exchange( Ok(runtime) => { let token = runtime.packet_tunnel.tcp_connection.clone(); + let tokio_handle = match crate::mullvad_ios_runtime() { + Ok(handle) => handle, + Err(err) => { + log::error!("Failed to obtain a handle to a tokio runtime: {err}"); + return Err(Error::UnableToCreateRuntime); + } + }; + runtime.run(tokio_handle); Ok(EphemeralPeerCancelToken { context: Arc::into_raw(token) as *mut _, diff --git a/mullvad-ios/src/post_quantum_proxy/mod.rs b/mullvad-ios/src/post_quantum_proxy/mod.rs index 4c72f7e6eb2d..a4090e58db7d 100644 --- a/mullvad-ios/src/post_quantum_proxy/mod.rs +++ b/mullvad-ios/src/post_quantum_proxy/mod.rs @@ -137,15 +137,6 @@ pub unsafe extern "C" fn request_ephemeral_peer( let pub_key: [u8; 32] = unsafe { std::ptr::read(public_key as *const [u8; 32]) }; let eph_key: [u8; 32] = unsafe { std::ptr::read(ephemeral_key as *const [u8; 32]) }; - let handle = match crate::mullvad_ios_runtime() { - Ok(handle) => handle, - Err(err) => { - log::error!("Failed to obtain a handle to a tokio runtime: {err}"); - - return -1; - } - }; - match unsafe { run_post_quantum_psk_exchange( pub_key, @@ -153,7 +144,6 @@ pub unsafe extern "C" fn request_ephemeral_peer( packet_tunnel, tcp_connection, peer_exchange_timeout, - handle, enable_post_quantum, enable_daita, )