diff options
| author | Emīls <emils@mullvad.net> | 2024-12-13 16:50:45 +0100 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2024-12-19 17:05:28 +0100 |
| commit | f1df6d52b322f19d15abf784d6ea66b02b7f501b (patch) | |
| tree | 3f99a52879dc3ea3b3f7260e06aeebe037543d50 /ios | |
| parent | b3229881746dcc6c44db0489745f7a9f395b9eae (diff) | |
| download | mullvadvpn-f1df6d52b322f19d15abf784d6ea66b02b7f501b.tar.xz mullvadvpn-f1df6d52b322f19d15abf784d6ea66b02b7f501b.zip | |
Use IAN TCP connection for ephemeral peer exchange
Diffstat (limited to 'ios')
22 files changed, 291 insertions, 374 deletions
diff --git a/ios/MullvadRustRuntime/EphemeralPeerExchangeActor.swift b/ios/MullvadRustRuntime/EphemeralPeerExchangeActor.swift index 397b656d61..3f38ccb762 100644 --- a/ios/MullvadRustRuntime/EphemeralPeerExchangeActor.swift +++ b/ios/MullvadRustRuntime/EphemeralPeerExchangeActor.swift @@ -21,13 +21,9 @@ public protocol EphemeralPeerExchangeActorProtocol { public class EphemeralPeerExchangeActor: EphemeralPeerExchangeActorProtocol { struct Negotiation { var negotiator: EphemeralPeerNegotiating - var inTunnelTCPConnection: NWTCPConnection - var tcpConnectionObserver: NSKeyValueObservation func cancel() { negotiator.cancelKeyNegotiation() - tcpConnectionObserver.invalidate() - inTunnelTCPConnection.cancel() } } @@ -54,15 +50,6 @@ public class EphemeralPeerExchangeActor: EphemeralPeerExchangeActorProtocol { self.keyExchangeRetriesIterator = iteratorProvider() } - private func createTCPConnection(_ gatewayEndpoint: NWHostEndpoint) -> NWTCPConnection { - self.packetTunnel.createTCPConnectionThroughTunnel( - to: gatewayEndpoint, - enableTLS: false, - tlsParameters: nil, - delegate: nil - ) - } - /// Starts a new key exchange. /// /// Any ongoing key negotiation is stopped before starting a new one. @@ -75,49 +62,46 @@ public class EphemeralPeerExchangeActor: EphemeralPeerExchangeActorProtocol { endCurrentNegotiation() let negotiator = negotiationProvider.init() - let gatewayAddress = LocalNetworkIPs.gatewayAddress.rawValue - let IPv4Gateway = IPv4Address(gatewayAddress)! - let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "\(CONFIG_SERVICE_PORT)") - let inTunnelTCPConnection = createTCPConnection(endpoint) - // This will become the new private key of the device let ephemeralSharedKey = PrivateKey() let tcpConnectionTimeout = keyExchangeRetriesIterator.next() ?? .seconds(10) // If the connection never becomes viable, force a reconnection after 10 seconds - scheduleInTunnelConnectionTimeout(startTime: .now() + tcpConnectionTimeout) - - let tcpConnectionObserver = inTunnelTCPConnection.observe(\.isViable, options: [ - .initial, - .new, - ]) { [weak self] observedConnection, _ in - guard let self, observedConnection.isViable else { return } - self.negotiation?.tcpConnectionObserver.invalidate() - self.timer?.cancel() + let peerParameters = EphemeralPeerParameters( + peer_exchange_timeout: UInt64(tcpConnectionTimeout.timeInterval), + enable_post_quantum: enablePostQuantum, + enable_daita: enableDaita, + funcs: mapWgFunctions(functions: packetTunnel.wgFunctions()) + ) - if !negotiator.startNegotiation( - gatewayIP: IPv4Gateway, - devicePublicKey: privateKey.publicKey, - presharedKey: ephemeralSharedKey, - peerReceiver: packetTunnel, - tcpConnection: inTunnelTCPConnection, - peerExchangeTimeout: tcpConnectionTimeout, - enablePostQuantum: enablePostQuantum, - enableDaita: enableDaita - ) { - // Cancel the negotiation to shut down any remaining use of the TCP connection on the Rust side - self.negotiation?.cancel() - self.negotiation = nil - self.onFailure() - } + if !negotiator.startNegotiation( + devicePublicKey: privateKey.publicKey, + presharedKey: ephemeralSharedKey, + peerReceiver: packetTunnel, + ephemeralPeerParams: peerParameters + ) { + // Cancel the negotiation to shut down any remaining use of the TCP connection on the Rust side + self.negotiation?.cancel() + self.negotiation = nil + self.onFailure() } + negotiation = Negotiation( - negotiator: negotiator, - inTunnelTCPConnection: inTunnelTCPConnection, - tcpConnectionObserver: tcpConnectionObserver + negotiator: negotiator ) } + private func mapWgFunctions(functions: WgFunctionPointers) -> WgTcpConnectionFunctions { + var mappedFunctions = WgTcpConnectionFunctions() + + mappedFunctions.close_fn = functions.close + mappedFunctions.open_fn = functions.open + mappedFunctions.send_fn = functions.send + mappedFunctions.recv_fn = functions.receive + + return mappedFunctions + } + /// Cancels the ongoing key exchange. public func endCurrentNegotiation() { negotiation?.cancel() @@ -129,19 +113,4 @@ public class EphemeralPeerExchangeActor: EphemeralPeerExchangeActorProtocol { keyExchangeRetriesIterator = iteratorProvider() endCurrentNegotiation() } - - private func scheduleInTunnelConnectionTimeout(startTime: DispatchWallTime) { - let newTimer = DispatchSource.makeTimerSource() - - newTimer.setEventHandler { [weak self] in - self?.onFailure() - self?.timer?.cancel() - } - - newTimer.schedule(wallDeadline: startTime) - newTimer.activate() - - timer?.cancel() - timer = newTimer - } } diff --git a/ios/MullvadRustRuntime/EphemeralPeerNegotiator.swift b/ios/MullvadRustRuntime/EphemeralPeerNegotiator.swift index ffc0dc15b3..8346b2686d 100644 --- a/ios/MullvadRustRuntime/EphemeralPeerNegotiator.swift +++ b/ios/MullvadRustRuntime/EphemeralPeerNegotiator.swift @@ -14,14 +14,10 @@ import WireGuardKitTypes // swiftlint:disable function_parameter_count public protocol EphemeralPeerNegotiating { func startNegotiation( - gatewayIP: IPv4Address, devicePublicKey: PublicKey, presharedKey: PrivateKey, peerReceiver: any TunnelProvider, - tcpConnection: NWTCPConnection, - peerExchangeTimeout: Duration, - enablePostQuantum: Bool, - enableDaita: Bool + ephemeralPeerParams: EphemeralPeerParameters ) -> Bool func cancelKeyNegotiation() @@ -33,35 +29,30 @@ public protocol EphemeralPeerNegotiating { public class EphemeralPeerNegotiator: EphemeralPeerNegotiating { required public init() {} - var cancelToken: EphemeralPeerCancelToken? + var cancelToken: OpaquePointer? public func startNegotiation( - gatewayIP: IPv4Address, devicePublicKey: PublicKey, presharedKey: PrivateKey, peerReceiver: any TunnelProvider, - tcpConnection: NWTCPConnection, - peerExchangeTimeout: Duration, - enablePostQuantum: Bool, - enableDaita: Bool + ephemeralPeerParams: EphemeralPeerParameters ) -> Bool { // swiftlint:disable:next force_cast let ephemeralPeerReceiver = Unmanaged.passUnretained(peerReceiver as! EphemeralPeerReceiver) .toOpaque() - let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque() - var cancelToken = EphemeralPeerCancelToken() - let result = request_ephemeral_peer( + guard let tunnelHandle = try? peerReceiver.tunnelHandle() else { + return false + } + + let cancelToken = request_ephemeral_peer( devicePublicKey.rawValue.map { $0 }, presharedKey.rawValue.map { $0 }, ephemeralPeerReceiver, - opaqueConnection, - &cancelToken, - UInt64(peerExchangeTimeout.timeInterval), - enablePostQuantum, - enableDaita + tunnelHandle, + ephemeralPeerParams ) - guard result == 0 else { + guard let cancelToken else { return false } self.cancelToken = cancelToken @@ -69,13 +60,14 @@ public class EphemeralPeerNegotiator: EphemeralPeerNegotiating { } public func cancelKeyNegotiation() { - guard var cancelToken else { return } - cancel_ephemeral_peer_exchange(&cancelToken) + guard let cancelToken else { return } + cancel_ephemeral_peer_exchange(cancelToken) + self.cancelToken = nil } deinit { - guard var cancelToken else { return } - drop_ephemeral_peer_exchange_token(&cancelToken) + guard let cancelToken else { return } + drop_ephemeral_peer_exchange_token(cancelToken) } } diff --git a/ios/MullvadRustRuntime/EphemeralPeerReceiver.swift b/ios/MullvadRustRuntime/EphemeralPeerReceiver.swift new file mode 100644 index 0000000000..2b1b4adac5 --- /dev/null +++ b/ios/MullvadRustRuntime/EphemeralPeerReceiver.swift @@ -0,0 +1,52 @@ +// +// EphemeralPeerReceiver.swift +// PacketTunnel +// +// Created by Marco Nikic on 2024-02-15. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadRustRuntimeProxy +import MullvadTypes +import NetworkExtension +import WireGuardKitTypes + +/// End sequence of an ephemeral peer exchange. +/// +/// This FFI function is called by Rust when an ephemeral peer negotiation succeeded or failed. +/// When both the `rawPresharedKey` and the `rawEphemeralKey` are raw pointers to 32 bytes data arrays, +/// the quantum-secure key exchange is considered successful. +/// If the `rawPresharedKey` is nil, but there is a valid `rawEphemeralKey`, it means a Daita peer has been negotiated with. +/// If `rawEphemeralKey` is nil, the negotiation is considered failed. +/// +/// - Parameters: +/// - rawEphemeralPeerReceiver: A raw pointer to the running instance of `NEPacketTunnelProvider` +/// - rawPresharedKey: A raw pointer to the quantum-secure pre shared key +/// - rawEphemeralKey: A raw pointer to the ephemeral private key of the device +@_cdecl("swift_ephemeral_peer_ready") +func receivePostQuantumKey( + rawEphemeralPeerReceiver: UnsafeMutableRawPointer?, + rawPresharedKey: UnsafeMutableRawPointer?, + rawEphemeralKey: UnsafeMutableRawPointer? +) { + guard let rawEphemeralPeerReceiver else { return } + let ephemeralPeerReceiver = Unmanaged<EphemeralPeerReceiver>.fromOpaque(rawEphemeralPeerReceiver) + .takeUnretainedValue() + + // If there are no private keys for the ephemeral peer, then the negotiation either failed, or timed out. + guard let rawEphemeralKey, + let ephemeralKey = PrivateKey(rawValue: Data(bytes: rawEphemeralKey, count: 32)) else { + ephemeralPeerReceiver.ephemeralPeerExchangeFailed() + return + } + + // If there is a pre-shared key, an ephemeral peer was negotiated with Post Quantum options + // Otherwise, a Daita enabled ephemeral peer was requested + if let rawPresharedKey, let key = PreSharedKey(rawValue: Data(bytes: rawPresharedKey, count: 32)) { + ephemeralPeerReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey) + } else { + ephemeralPeerReceiver.receiveEphemeralPeerPrivateKey(ephemeralKey) + } + return +} diff --git a/ios/MullvadRustRuntime/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadRustRuntime/PacketTunnelProvider+TCPConnection.swift deleted file mode 100644 index e19750d4dc..0000000000 --- a/ios/MullvadRustRuntime/PacketTunnelProvider+TCPConnection.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// PacketTunnelProvider+TCPConnection.swift -// PacketTunnel -// -// Created by Marco Nikic on 2024-02-15. -// Copyright © 2023 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import MullvadRustRuntimeProxy -import MullvadTypes -import NetworkExtension -import WireGuardKitTypes - -/// Writes data to the in-tunnel TCP connection -/// -/// This FFI function is called by Rust whenever there is data to be written to the in-tunnel TCP connection when exchanging -/// quantum-resistant pre shared keys. -/// -/// Whenever the flow control is given back from the connection, acknowledge that data was written using `rawWriteAcknowledgement`. -/// - Parameters: -/// - rawConnection: A raw pointer to the in-tunnel TCP connection -/// - rawData: A raw pointer to the data to write in the connection -/// - dataLength: The length of data to write in the connection -/// - rawWriteAcknowledgement: An opaque pointer needed for write acknowledgement -@_cdecl("swift_nw_tcp_connection_send") -func tcpConnectionSend( - rawConnection: UnsafeMutableRawPointer?, - rawData: UnsafeMutableRawPointer, - dataLength: UInt, - rawWriteAcknowledgement: UnsafeMutableRawPointer? -) { - guard let rawConnection, let rawWriteAcknowledgement else { - handle_sent(0, rawWriteAcknowledgement) - return - } - let tcpConnection = Unmanaged<NWTCPConnection>.fromOpaque(rawConnection).takeUnretainedValue() - let data = Data(bytes: rawData, count: Int(dataLength)) - - // The guarantee that all writes are sequential is done by virtue of not returning the execution context - // to Rust before this closure is done executing. - tcpConnection.write(data, completionHandler: { maybeError in - if maybeError != nil { - handle_sent(0, rawWriteAcknowledgement) - } else { - handle_sent(dataLength, rawWriteAcknowledgement) - } - }) -} - -/// Reads data to the in-tunnel TCP connection -/// -/// This FFI function is called by Rust whenever there is data to be read from the in-tunnel TCP connection when exchanging -/// quantum-resistant pre shared keys. -/// -/// Whenever the flow control is given back from the connection, acknowledge that data was read using `rawReadAcknowledgement`. -/// - Parameters: -/// - rawConnection: A raw pointer to the in-tunnel TCP connection -/// - rawReadAcknowledgement: An opaque pointer needed for read acknowledgement -@_cdecl("swift_nw_tcp_connection_read") -func tcpConnectionReceive( - rawConnection: UnsafeMutableRawPointer?, - rawReadAcknowledgement: UnsafeMutableRawPointer? -) { - guard let rawConnection, let rawReadAcknowledgement else { - handle_recv(nil, 0, rawReadAcknowledgement) - return - } - let tcpConnection = Unmanaged<NWTCPConnection>.fromOpaque(rawConnection).takeUnretainedValue() - tcpConnection.readMinimumLength(0, maximumLength: Int(UInt16.max)) { data, maybeError in - if let data { - if maybeError != nil { - handle_recv(nil, 0, rawReadAcknowledgement) - } else { - handle_recv(data.map { $0 }, UInt(data.count), rawReadAcknowledgement) - } - } - } -} - -/// End sequence of an ephemeral peer exchange. -/// -/// This FFI function is called by Rust when an ephemeral peer negotiation succeeded or failed. -/// When both the `rawPresharedKey` and the `rawEphemeralKey` are raw pointers to 32 bytes data arrays, -/// the quantum-secure key exchange is considered successful. -/// If the `rawPresharedKey` is nil, but there is a valid `rawEphemeralKey`, it means a Daita peer has been negotiated with. -/// If `rawEphemeralKey` is nil, the negotiation is considered failed. -/// -/// - Parameters: -/// - rawEphemeralPeerReceiver: A raw pointer to the running instance of `NEPacketTunnelProvider` -/// - rawPresharedKey: A raw pointer to the quantum-secure pre shared key -/// - rawEphemeralKey: A raw pointer to the ephemeral private key of the device -@_cdecl("swift_ephemeral_peer_ready") -func receivePostQuantumKey( - rawEphemeralPeerReceiver: UnsafeMutableRawPointer?, - rawPresharedKey: UnsafeMutableRawPointer?, - rawEphemeralKey: UnsafeMutableRawPointer? -) { - guard let rawEphemeralPeerReceiver else { return } - let ephemeralPeerReceiver = Unmanaged<EphemeralPeerReceiver>.fromOpaque(rawEphemeralPeerReceiver) - .takeUnretainedValue() - - // If there are no private keys for the ephemeral peer, then the negotiation either failed, or timed out. - guard let rawEphemeralKey, - let ephemeralKey = PrivateKey(rawValue: Data(bytes: rawEphemeralKey, count: 32)) else { - ephemeralPeerReceiver.ephemeralPeerExchangeFailed() - return - } - - // If there is a pre-shared key, an ephemeral peer was negotiated with Post Quantum options - // Otherwise, a Daita enabled ephemeral peer was requested - if let rawPresharedKey, let key = PreSharedKey(rawValue: Data(bytes: rawPresharedKey, count: 32)) { - ephemeralPeerReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey) - } else { - ephemeralPeerReceiver.receiveEphemeralPeerPrivateKey(ephemeralKey) - } - return -} diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h index a45c6ed6c3..93c04587f1 100644 --- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h +++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h @@ -20,14 +20,26 @@ typedef uint8_t TunnelObfuscatorProtocol; */ typedef struct EncryptedDnsProxyState EncryptedDnsProxyState; +typedef struct ExchangeCancelToken ExchangeCancelToken; + typedef struct ProxyHandle { void *context; uint16_t port; } ProxyHandle; -typedef struct EphemeralPeerCancelToken { - void *context; -} EphemeralPeerCancelToken; +typedef struct WgTcpConnectionFunctions { + int32_t (*open_fn)(int32_t tunnelHandle, const char *address, uint64_t timeout); + int32_t (*close_fn)(int32_t tunnelHandle, int32_t socketHandle); + int32_t (*recv_fn)(int32_t tunnelHandle, int32_t socketHandle, uint8_t *data, int32_t len); + int32_t (*send_fn)(int32_t tunnelHandle, int32_t socketHandle, const uint8_t *data, int32_t len); +} WgTcpConnectionFunctions; + +typedef struct EphemeralPeerParameters { + uint64_t peer_exchange_timeout; + bool enable_post_quantum; + bool enable_daita; + struct WgTcpConnectionFunctions funcs; +} EphemeralPeerParameters; extern const uint16_t CONFIG_SERVICE_PORT; @@ -84,43 +96,17 @@ int32_t encrypted_dns_proxy_stop(struct ProxyHandle *proxy_config); * `sender` must be pointing to a valid instance of a `EphemeralPeerCancelToken` created by the * `PacketTunnelProvider`. */ -void cancel_ephemeral_peer_exchange(const struct EphemeralPeerCancelToken *sender); +void cancel_ephemeral_peer_exchange(struct ExchangeCancelToken *sender); /** - * Called by the Swift side to signal that the Rust `EphemeralPeerCancelToken` can be safely dropped - * from memory. + * Called by the Swift side to signal that the Rust `EphemeralPeerCancelToken` can be safely + * dropped from memory. * * # Safety * `sender` must be pointing to a valid instance of a `EphemeralPeerCancelToken` created by the * `PacketTunnelProvider`. */ -void drop_ephemeral_peer_exchange_token(const struct EphemeralPeerCancelToken *sender); - -/** - * Called by Swift whenever data has been written to the in-tunnel TCP connection when exchanging - * quantum-resistant pre shared keys, or ephemeral peers. - * - * If `bytes_sent` is 0, this indicates that the connection was closed or that an error occurred. - * - * # Safety - * `sender` must be pointing to a valid instance of a `write_tx` created by the `IosTcpProvider` - * Callback to call when the TCP connection has written data. - */ -void handle_sent(uintptr_t bytes_sent, const void *sender); - -/** - * Called by Swift whenever data has been read from the in-tunnel TCP connection when exchanging - * quantum-resistant pre shared keys, or ephemeral peers. - * - * If `data` is null or empty, this indicates that the connection was closed or that an error - * occurred. An empty buffer is sent to the underlying reader to signal EOF. - * - * # Safety - * `sender` must be pointing to a valid instance of a `read_tx` created by the `IosTcpProvider` - * - * Callback to call when the TCP connection has received data. - */ -void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender); +void drop_ephemeral_peer_exchange_token(struct ExchangeCancelToken *sender); /** * Entry point for requesting ephemeral peers on iOS. @@ -128,33 +114,15 @@ void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender); * # Safety * `public_key` and `ephemeral_key` must be valid respective `PublicKey` and `PrivateKey` types. * They will not be valid after this function is called, and thus must be copied here. - * `packet_tunnel` and `tcp_connection` must be valid pointers to a packet tunnel and a TCP - * connection instances. - * `cancel_token` should be owned by the caller of this function. - */ -int32_t request_ephemeral_peer(const uint8_t *public_key, - const uint8_t *ephemeral_key, - const void *packet_tunnel, - const void *tcp_connection, - struct EphemeralPeerCancelToken *cancel_token, - uint64_t peer_exchange_timeout, - bool enable_post_quantum, - bool enable_daita); - -/** - * Called when there is data to send on the TCP connection. - * The TCP connection must write data on the wire, then call the `handle_sent` function. - */ -extern void swift_nw_tcp_connection_send(const void *connection, - const void *data, - uintptr_t data_len, - const void *sender); - -/** - * Called when there is data to read on the TCP connection. - * The TCP connection must read data from the wire, then call the `handle_read` function. + * `packet_tunnel` must be valid pointers to a packet tunnel, the packet tunnel pointer must + * outlive the ephemeral peer exchange. `cancel_token` should be owned by the caller of this + * function. */ -extern void swift_nw_tcp_connection_read(const void *connection, const void *sender); +struct ExchangeCancelToken *request_ephemeral_peer(const uint8_t *public_key, + const uint8_t *ephemeral_key, + const void *packet_tunnel, + int32_t tunnel_handle, + struct EphemeralPeerParameters peer_parameters); /** * Called when the preshared post quantum key is ready, diff --git a/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift b/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift index 683e1ab8de..30ce49a891 100644 --- a/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift +++ b/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift @@ -27,6 +27,19 @@ class NWTCPConnectionStub: NWTCPConnection { } class TunnelProviderStub: TunnelProvider { + func tunnelHandle() throws -> Int32 { + 0 + } + + func wgFunctions() -> MullvadTypes.WgFuncPointers { + return MullvadTypes.WgFuncPointers( + open: { _, _, _ in return 0 }, + close: { _, _ in return 0 }, + receive: { _, _, _, _ in return 0 }, + send: { _, _, _, _ in return 0 } + ) + } + let tcpConnection: NWTCPConnectionStub init(tcpConnection: NWTCPConnectionStub) { @@ -55,15 +68,13 @@ class FailedNegotiatorStub: EphemeralPeerNegotiating { } func startNegotiation( - gatewayIP: IPv4Address, devicePublicKey: WireGuardKitTypes.PublicKey, presharedKey: WireGuardKitTypes.PrivateKey, - peerReceiver packetTunnel: any MullvadTypes.TunnelProvider, - tcpConnection: NWTCPConnection, - peerExchangeTimeout: MullvadTypes.Duration, - enablePostQuantum: Bool, - enableDaita: Bool - ) -> Bool { false } + peerReceiver: any MullvadTypes.TunnelProvider, + ephemeralPeerParams: EphemeralPeerParameters + ) -> Bool { + false + } func cancelKeyNegotiation() { onCancelKeyNegotiation?() @@ -81,15 +92,13 @@ class SuccessfulNegotiatorStub: EphemeralPeerNegotiating { } func startNegotiation( - gatewayIP: IPv4Address, devicePublicKey: WireGuardKitTypes.PublicKey, presharedKey: WireGuardKitTypes.PrivateKey, - peerReceiver packetTunnel: any MullvadTypes.TunnelProvider, - tcpConnection: NWTCPConnection, - peerExchangeTimeout: MullvadTypes.Duration, - enablePostQuantum: Bool, - enableDaita: Bool - ) -> Bool { true } + peerReceiver: any MullvadTypes.TunnelProvider, + ephemeralPeerParams: EphemeralPeerParameters + ) -> Bool { + true + } func cancelKeyNegotiation() { onCancelKeyNegotiation?() diff --git a/ios/MullvadTypes/Promise.swift b/ios/MullvadTypes/Promise.swift index 886f00f633..48a12f8182 100644 --- a/ios/MullvadTypes/Promise.swift +++ b/ios/MullvadTypes/Promise.swift @@ -47,3 +47,19 @@ public final class Promise<Success, Failure: Error> { } } } + + +public struct OneshotChannel { + private let semaphore = DispatchSemaphore(value: 0) + + public init() { + } + + public mutating func send() { + semaphore.signal() + } + + public func receive() { + semaphore.wait() + } +} diff --git a/ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift b/ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift index e5fc68f68a..f7fbe8e4a1 100644 --- a/ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift +++ b/ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift @@ -11,10 +11,20 @@ import NetworkExtension import WireGuardKitTypes public class EphemeralPeerReceiver: EphemeralPeerReceiving, TunnelProvider { - unowned let tunnelProvider: NEPacketTunnelProvider + public func tunnelHandle() throws -> Int32 { + try tunnelProvider.tunnelHandle() + } + + public func wgFunctions() -> WgFunctionPointers { + tunnelProvider.wgFunctions() + } + + unowned let tunnelProvider: any TunnelProvider + let keyReceiver: any EphemeralPeerReceiving - public init(tunnelProvider: NEPacketTunnelProvider) { + public init(tunnelProvider: TunnelProvider, keyReceiver: any EphemeralPeerReceiving) { self.tunnelProvider = tunnelProvider + self.keyReceiver = keyReceiver } // MARK: - EphemeralPeerReceiving @@ -30,23 +40,6 @@ public class EphemeralPeerReceiver: EphemeralPeerReceiving, TunnelProvider { } public func ephemeralPeerExchangeFailed() { - guard let receiver = tunnelProvider as? EphemeralPeerReceiving else { return } - receiver.ephemeralPeerExchangeFailed() - } - - // MARK: - TunnelProvider - - public func createTCPConnectionThroughTunnel( - to remoteEndpoint: NWEndpoint, - enableTLS: Bool, - tlsParameters TLSParameters: NWTLSParameters?, - delegate: Any? - ) -> NWTCPConnection { - tunnelProvider.createTCPConnectionThroughTunnel( - to: remoteEndpoint, - enableTLS: enableTLS, - tlsParameters: TLSParameters, - delegate: delegate - ) + keyReceiver.ephemeralPeerExchangeFailed() } } diff --git a/ios/MullvadTypes/Protocols/TunnelProvider.swift b/ios/MullvadTypes/Protocols/TunnelProvider.swift index 61aa99ba7d..06524bd826 100644 --- a/ios/MullvadTypes/Protocols/TunnelProvider.swift +++ b/ios/MullvadTypes/Protocols/TunnelProvider.swift @@ -10,12 +10,25 @@ import Foundation import NetworkExtension public protocol TunnelProvider: AnyObject { - func createTCPConnectionThroughTunnel( - to remoteEndpoint: NWEndpoint, - enableTLS: Bool, - tlsParameters TLSParameters: NWTLSParameters?, - delegate: Any? - ) -> NWTCPConnection + func tunnelHandle() throws -> Int32 + func wgFunctions() -> WgFunctionPointers } -extension NEPacketTunnelProvider: TunnelProvider {} +public typealias TcpOpenFunction = @convention(c) (Int32, UnsafePointer<CChar>?, UInt64) -> Int32 +public typealias TcpCloseFunction = @convention(c) (Int32, Int32) -> Int32 +public typealias TcpSendFunction = @convention(c) (Int32, Int32, UnsafePointer<UInt8>?, Int32) -> Int32 +public typealias TcpRecvFunction = @convention(c) (Int32, Int32, UnsafeMutablePointer<UInt8>?, Int32) -> Int32 + +public struct WgFunctionPointers { + public let open: TcpOpenFunction + public let close: TcpCloseFunction + public let receive: TcpRecvFunction + public let send: TcpSendFunction + + public init(open: TcpOpenFunction, close: TcpCloseFunction, receive: TcpRecvFunction, send: TcpSendFunction) { + self.open = open + self.close = close + self.receive = receive + self.send = send + } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 1379f20a73..31cab31228 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -659,10 +659,10 @@ 7AF9BE902A39F26000DBFEDB /* Collection+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */; }; 7AF9BE952A40461100DBFEDB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */; }; 7AF9BE972A41C71F00DBFEDB /* ChipViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */; }; - 7AFBE38B2D09AAFF002335FC /* SinglehopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */; }; - 7AFBE38D2D09AB2E002335FC /* MultihopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */; }; 7AFBE3872D084C9D002335FC /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */; }; 7AFBE3892D089163002335FC /* FI_TunnelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */; }; + 7AFBE38B2D09AAFF002335FC /* SinglehopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */; }; + 7AFBE38D2D09AB2E002335FC /* MultihopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */; }; 850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DA2B503D7700EF8C96 /* RelayTests.swift */; }; 850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */; }; 850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */; }; @@ -729,7 +729,7 @@ A91614D12B108D1B00F416EB /* TransportLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91614D02B108D1B00F416EB /* TransportLayer.swift */; }; A91614D62B10B26B00F416EB /* TunnelControlViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91614D52B10B26B00F416EB /* TunnelControlViewModel.swift */; }; A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; }; - A9173C322C36CCDD00F6A08C /* PacketTunnelProvider+TCPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */; }; + A9173C322C36CCDD00F6A08C /* EphemeralPeerReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A557F42B7E3E5C0017ADA8 /* EphemeralPeerReceiver.swift */; }; A9173C372C36CD2B00F6A08C /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; platformFilter = ios; }; A91D78E42B03C01600FCD5D3 /* MullvadSettings.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */; }; A91EBEDA2C1337040004A84D /* RetryStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EBED92C1337040004A84D /* RetryStrategyTests.swift */; }; @@ -2020,10 +2020,10 @@ 7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Sorting.swift"; sourceTree = "<group>"; }; 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; }; 7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipViewCell.swift; sourceTree = "<group>"; }; - 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglehopPicker.swift; sourceTree = "<group>"; }; - 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPicker.swift; sourceTree = "<group>"; }; 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; }; 7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FI_TunnelViewController.swift; sourceTree = "<group>"; }; + 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglehopPicker.swift; sourceTree = "<group>"; }; + 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPicker.swift; sourceTree = "<group>"; }; 85006A8E2B73EF67004AD8FB /* MullvadVPNUITestsSmoke.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITestsSmoke.xctestplan; sourceTree = "<group>"; }; 850201DA2B503D7700EF8C96 /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = "<group>"; }; 850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationPage.swift; sourceTree = "<group>"; }; @@ -2138,7 +2138,7 @@ A99E5EDF2B7628150033F241 /* ProblemReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportViewModel.swift; sourceTree = "<group>"; }; A99E5EE12B762ED30033F241 /* ProblemReportViewController+ViewManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProblemReportViewController+ViewManagement.swift"; sourceTree = "<group>"; }; A9A1DE782AD5708E0073F689 /* TransportStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportStrategy.swift; sourceTree = "<group>"; }; - A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelProvider+TCPConnection.swift"; sourceTree = "<group>"; }; + A9A557F42B7E3E5C0017ADA8 /* EphemeralPeerReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerReceiver.swift; sourceTree = "<group>"; }; A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManagerTests.swift; sourceTree = "<group>"; }; A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = "<group>"; }; A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = "<group>"; }; @@ -3928,7 +3928,6 @@ 7A0EAE982D01B29E00D3EB8B /* Recovered References */ = { isa = PBXGroup; children = ( - 7AA1309C2D0072F900640DF9 /* View+Size.swift */, ); name = "Recovered References"; sourceTree = "<group>"; @@ -4255,7 +4254,7 @@ children = ( A9D9A4D32C36E1EA004088DD /* mullvad_rust_runtime.h */, A992DA1F2C24709F00DE7CE5 /* MullvadRustRuntime.h */, - A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */, + A9A557F42B7E3E5C0017ADA8 /* EphemeralPeerReceiver.swift */, A948809A2BC9308D0090A44C /* EphemeralPeerExchangeActor.swift */, A9EB4F9C2B7FAB21002A2D7A /* EphemeralPeerNegotiator.swift */, F0DDE40F2B220458006B57A7 /* ShadowSocksProxy.swift */, @@ -6447,7 +6446,7 @@ 014449952CA293B100C0C2F2 /* EncryptedDNSProxy.swift in Sources */, A9D9A4BB2C36D397004088DD /* EphemeralPeerNegotiator.swift in Sources */, A9D9A4B22C36D12D004088DD /* TunnelObfuscator.swift in Sources */, - A9173C322C36CCDD00F6A08C /* PacketTunnelProvider+TCPConnection.swift in Sources */, + A9173C322C36CCDD00F6A08C /* EphemeralPeerReceiver.swift in Sources */, F05919802C45515200C301F3 /* EphemeralPeerExchangeActor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 84ff437b20..3216773eb5 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -34,7 +34,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private let tunnelSettingsListener = TunnelSettingsListener() private lazy var ephemeralPeerReceiver = { - EphemeralPeerReceiver(tunnelProvider: self) + EphemeralPeerReceiver(tunnelProvider: adapter, keyReceiver: self) }() // swiftlint:disable:next function_body_length @@ -110,7 +110,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider { iteratorProvider: { REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() } ), onUpdateConfiguration: { [unowned self] configuration in - actor.changeEphemeralPeerNegotiationState(configuration: configuration) + let channel = OneshotChannel() + actor.changeEphemeralPeerNegotiationState(configuration: configuration, reconfigurationSemaphore: channel) + channel.receive() }, onFinish: { [unowned self] in actor.notifyEphemeralPeerNegotiated() } diff --git a/ios/PacketTunnel/PostQuantum/MultiHopEphemeralPeerExchanger.swift b/ios/PacketTunnel/PostQuantum/MultiHopEphemeralPeerExchanger.swift index ee1a3afe6c..0e2bc97778 100644 --- a/ios/PacketTunnel/PostQuantum/MultiHopEphemeralPeerExchanger.swift +++ b/ios/PacketTunnel/PostQuantum/MultiHopEphemeralPeerExchanger.swift @@ -19,7 +19,7 @@ final class MultiHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol { let keyExchanger: EphemeralPeerExchangeActorProtocol let devicePrivateKey: PrivateKey let onFinish: () -> Void - let onUpdateConfiguration: (EphemeralPeerNegotiationState) -> Void + let onUpdateConfiguration: (EphemeralPeerNegotiationState) async -> Void let enablePostQuantum: Bool let enableDaita: Bool @@ -48,7 +48,7 @@ final class MultiHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol { keyExchanger: EphemeralPeerExchangeActorProtocol, enablePostQuantum: Bool, enableDaita: Bool, - onUpdateConfiguration: @escaping (EphemeralPeerNegotiationState) -> Void, + onUpdateConfiguration: @escaping (EphemeralPeerNegotiationState) async -> Void, onFinish: @escaping () -> Void ) { self.entry = entry @@ -61,37 +61,37 @@ final class MultiHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol { self.onFinish = onFinish } - func start() { + func start() async { guard state == .initial else { return } - negotiateWithEntry() + await negotiateWithEntry() } - public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) { + public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) async { if state == .negotiatingWithEntry { entryPeerKey = EphemeralPeerKey(ephemeralKey: ephemeralPeerPrivateKey) - negotiateBetweenEntryAndExit() + await negotiateBetweenEntryAndExit() } else if state == .negotiatingBetweenEntryAndExit { exitPeerKey = EphemeralPeerKey(ephemeralKey: ephemeralPeerPrivateKey) - makeConnection() + await makeConnection() } } func receivePostQuantumKey( _ preSharedKey: PreSharedKey, ephemeralKey: PrivateKey - ) { + ) async { if state == .negotiatingWithEntry { entryPeerKey = EphemeralPeerKey(preSharedKey: preSharedKey, ephemeralKey: ephemeralKey) - negotiateBetweenEntryAndExit() + await negotiateBetweenEntryAndExit() } else if state == .negotiatingBetweenEntryAndExit { exitPeerKey = EphemeralPeerKey(preSharedKey: preSharedKey, ephemeralKey: ephemeralKey) - makeConnection() + await makeConnection() } } - private func negotiateWithEntry() { + private func negotiateWithEntry() async { state = .negotiatingWithEntry - onUpdateConfiguration(.single(EphemeralPeerRelayConfiguration( + await onUpdateConfiguration(.single(EphemeralPeerRelayConfiguration( relay: entry, configuration: EphemeralPeerConfiguration( privateKey: devicePrivateKey, @@ -105,9 +105,9 @@ final class MultiHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol { ) } - private func negotiateBetweenEntryAndExit() { + private func negotiateBetweenEntryAndExit() async { state = .negotiatingBetweenEntryAndExit - onUpdateConfiguration(.multi( + await onUpdateConfiguration(.multi( entry: EphemeralPeerRelayConfiguration( relay: entry, configuration: EphemeralPeerConfiguration( @@ -132,9 +132,9 @@ final class MultiHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol { ) } - private func makeConnection() { + private func makeConnection() async { state = .makeConnection - onUpdateConfiguration(.multi( + await onUpdateConfiguration(.multi( entry: EphemeralPeerRelayConfiguration( relay: entry, configuration: EphemeralPeerConfiguration( diff --git a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift index 4bfd9b8091..eca0774e74 100644 --- a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift +++ b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift @@ -13,7 +13,7 @@ import NetworkExtension import PacketTunnelCore import WireGuardKit -struct WgAdapter: TunnelAdapterProtocol { +class WgAdapter: TunnelAdapterProtocol { let logger = Logger(label: "WgAdapter") let adapter: WireGuardAdapter @@ -212,3 +212,18 @@ private extension WgStats { return UInt64(value) } + +extension WgAdapter: TunnelProvider { + public func tunnelHandle() throws -> Int32 { + return try self.adapter.tunnelHandle() + } + + public func wgFunctions() -> WgFunctionPointers { + WgFunctionPointers( + open: adapter.inTunnelTcpOpen, + close: adapter.inTunnelTcpClose, + receive: adapter.inTunnelTcpRecv, + send: adapter.inTunnelTcpSend + ) + } +} diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift index 05b69deb35..9a9fb531c3 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift @@ -7,6 +7,7 @@ // import Foundation +import MullvadTypes import WireGuardKitTypes /** @@ -64,8 +65,11 @@ extension PacketTunnelActor { - Parameter key: the new key */ - nonisolated public func changeEphemeralPeerNegotiationState(configuration: EphemeralPeerNegotiationState) { - eventChannel.send(.ephemeralPeerNegotiationStateChanged(configuration)) + nonisolated public func changeEphemeralPeerNegotiationState( + configuration: EphemeralPeerNegotiationState, + reconfigurationSemaphore: OneshotChannel + ) { + eventChannel.send(.ephemeralPeerNegotiationStateChanged(configuration, reconfigurationSemaphore)) } /** diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 74f8a5a0e2..d9cce79f57 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -139,13 +139,14 @@ public actor PacketTunnelActor { case let .cacheActiveKey(lastKeyRotation): cacheActiveKey(lastKeyRotation: lastKeyRotation) - case let .reconfigureForEphemeralPeer(configuration): + case let .reconfigureForEphemeralPeer(configuration, configurationSemaphore): do { try await updateEphemeralPeerNegotiationState(configuration: configuration) } catch { logger.error(error: error, message: "Failed to reconfigure tunnel after each hop negotiation.") await setErrorStateInternal(with: error) } + configurationSemaphore.send() case .connectWithEphemeralPeer: await connectWithEphemeralPeer() case .setDisconnectedState: diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift index b677986d04..3e7ab37e54 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift @@ -7,6 +7,7 @@ // import Foundation +import MullvadTypes import WireGuardKitTypes extension PacketTunnelActor { @@ -37,7 +38,7 @@ extension PacketTunnelActor { case networkReachability(NetworkPath) /// Update the device private key, as per post-quantum protocols - case ephemeralPeerNegotiationStateChanged(EphemeralPeerNegotiationState) + case ephemeralPeerNegotiationStateChanged(EphemeralPeerNegotiationState, OneshotChannel) /// Notify that an ephemeral peer exchanging took place case notifyEphemeralPeerNegotiated diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift index 3382baf209..5b01778f2d 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift @@ -7,6 +7,7 @@ // import Foundation +import MullvadTypes import WireGuardKitTypes extension PacketTunnelActor { @@ -25,7 +26,7 @@ extension PacketTunnelActor { case stopTunnelAdapter case configureForErrorState(BlockedStateReason) case cacheActiveKey(Date?) - case reconfigureForEphemeralPeer(EphemeralPeerNegotiationState) + case reconfigureForEphemeralPeer(EphemeralPeerNegotiationState, OneshotChannel) case connectWithEphemeralPeer // acknowledge that the disconnection process has concluded, go to .disconnected. @@ -45,7 +46,7 @@ extension PacketTunnelActor { case (.stopTunnelAdapter, .stopTunnelAdapter): true case let (.configureForErrorState(r0), .configureForErrorState(r1)): r0 == r1 case let (.cacheActiveKey(d0), .cacheActiveKey(d1)): d0 == d1 - case let (.reconfigureForEphemeralPeer(eph0), .reconfigureForEphemeralPeer(eph1)): eph0 == eph1 + case let (.reconfigureForEphemeralPeer(eph0, _), .reconfigureForEphemeralPeer(eph1, _)): eph0 == eph1 case (.connectWithEphemeralPeer, .connectWithEphemeralPeer): true case (.setDisconnectedState, .setDisconnectedState): true default: false @@ -89,8 +90,8 @@ extension PacketTunnelActor { state.mutateAssociatedData { $0.networkReachability = newReachability } return [.updateTunnelMonitorPath(defaultPath)] - case let .ephemeralPeerNegotiationStateChanged(configuration): - return [.reconfigureForEphemeralPeer(configuration)] + case let .ephemeralPeerNegotiationStateChanged(configuration, reconfigurationSemaphore): + return [.reconfigureForEphemeralPeer(configuration, reconfigurationSemaphore)] case .notifyEphemeralPeerNegotiated: return [.connectWithEphemeralPeer] diff --git a/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift index 2af86eedfe..bbce8c8b44 100644 --- a/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift +++ b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift @@ -60,7 +60,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { ) } - func testSingleHopPostQuantumKeyExchange() throws { + func testSingleHopPostQuantumKeyExchange() async throws { let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") reconfigurationExpectation.expectedFulfillmentCount = 2 @@ -78,11 +78,11 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { } keyExchangeActor.delegate = KeyExchangingResultStub(onReceivePostQuantumKey: { preSharedKey, privateKey in - postQuantumKeyExchangingPipeline.receivePostQuantumKey(preSharedKey, ephemeralKey: privateKey) + await postQuantumKeyExchangingPipeline.receivePostQuantumKey(preSharedKey, ephemeralKey: privateKey) }) let connectionState = stubConnectionState(enableMultiHop: false, enablePostQuantum: true, enableDaita: false) - postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) + await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) wait( for: [reconfigurationExpectation, negotiationSuccessful], @@ -90,7 +90,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { ) } - func testSingleHopDaitaPeerExchange() throws { + func testSingleHopDaitaPeerExchange() async throws { let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") reconfigurationExpectation.expectedFulfillmentCount = 2 @@ -108,11 +108,11 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { } keyExchangeActor.delegate = KeyExchangingResultStub(onReceiveEphemeralPeerPrivateKey: { privateKey in - postQuantumKeyExchangingPipeline.receiveEphemeralPeerPrivateKey(privateKey) + await postQuantumKeyExchangingPipeline.receiveEphemeralPeerPrivateKey(privateKey) }) let connectionState = stubConnectionState(enableMultiHop: false, enablePostQuantum: false, enableDaita: true) - postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) + await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) wait( for: [reconfigurationExpectation, negotiationSuccessful], @@ -120,7 +120,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { ) } - func testMultiHopPostQuantumKeyExchange() throws { + func testMultiHopPostQuantumKeyExchange() async throws { let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") reconfigurationExpectation.expectedFulfillmentCount = 3 @@ -138,11 +138,11 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { } keyExchangeActor.delegate = KeyExchangingResultStub(onReceivePostQuantumKey: { preSharedKey, privateKey in - postQuantumKeyExchangingPipeline.receivePostQuantumKey(preSharedKey, ephemeralKey: privateKey) + await postQuantumKeyExchangingPipeline.receivePostQuantumKey(preSharedKey, ephemeralKey: privateKey) }) let connectionState = stubConnectionState(enableMultiHop: true, enablePostQuantum: true, enableDaita: false) - postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) + await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) wait( for: [reconfigurationExpectation, negotiationSuccessful], @@ -150,7 +150,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { ) } - func testMultiHopDaitaExchange() throws { + func testMultiHopDaitaExchange() async throws { let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") reconfigurationExpectation.expectedFulfillmentCount = 3 @@ -168,11 +168,11 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase { } keyExchangeActor.delegate = KeyExchangingResultStub(onReceiveEphemeralPeerPrivateKey: { privateKey in - postQuantumKeyExchangingPipeline.receiveEphemeralPeerPrivateKey(privateKey) + await postQuantumKeyExchangingPipeline.receiveEphemeralPeerPrivateKey(privateKey) }) let connectionState = stubConnectionState(enableMultiHop: true, enablePostQuantum: false, enableDaita: true) - postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) + await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey()) wait( for: [reconfigurationExpectation, negotiationSuccessful], diff --git a/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift b/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift index 7f17af56be..2f21748820 100644 --- a/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift +++ b/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift @@ -22,9 +22,9 @@ final class EphemeralPeerExchangeActorStub: EphemeralPeerExchangeActorProtocol { switch result { case let .success((preSharedKey, ephemeralKey)): if enablePostQuantum { - delegate?.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey) + Task { await delegate?.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey) } } else { - delegate?.receiveEphemeralPeerPrivateKey(ephemeralKey) + Task { await delegate?.receiveEphemeralPeerPrivateKey(ephemeralKey) } } case .failure: delegate?.ephemeralPeerExchangeFailed() diff --git a/ios/PacketTunnelCoreTests/Mocks/KeyExchangingResultStub.swift b/ios/PacketTunnelCoreTests/Mocks/KeyExchangingResultStub.swift index 9dc9dca58c..250524ec06 100644 --- a/ios/PacketTunnelCoreTests/Mocks/KeyExchangingResultStub.swift +++ b/ios/PacketTunnelCoreTests/Mocks/KeyExchangingResultStub.swift @@ -12,15 +12,15 @@ struct KeyExchangingResultStub: EphemeralPeerReceiving { var onFailure: (() -> Void)? - var onReceivePostQuantumKey: ((PreSharedKey, PrivateKey) -> Void)? - var onReceiveEphemeralPeerPrivateKey: ((PrivateKey) -> Void)? + var onReceivePostQuantumKey: ((PreSharedKey, PrivateKey) async -> Void)? + var onReceiveEphemeralPeerPrivateKey: ((PrivateKey) async -> Void)? - func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) { - onReceivePostQuantumKey?(key, ephemeralKey) + func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) async { + await onReceivePostQuantumKey?(key, ephemeralKey) } - public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) { - onReceiveEphemeralPeerPrivateKey?(ephemeralPeerPrivateKey) + public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) async { + await onReceiveEphemeralPeerPrivateKey?(ephemeralPeerPrivateKey) } func ephemeralPeerExchangeFailed() { diff --git a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift index a4c1d09155..c55f5b4d65 100644 --- a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift +++ b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift @@ -59,7 +59,7 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { ) } - func testEphemeralPeerExchangeFailsWhenNegotiationCannotStart() { + func testEphemeralPeerExchangeFailsWhenNegotiationCannotStart() async { let expectedNegotiationFailure = expectation(description: "Negotiation failed.") let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") @@ -88,7 +88,7 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { expectedNegotiationFailure.fulfill() } - multiHopExchanger.start() + await multiHopExchanger.start() wait( for: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], @@ -96,7 +96,7 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { ) } - func testEphemeralPeerExchangeSuccessWhenPostQuantumNegotiationStarts() throws { + func testEphemeralPeerExchangeSuccessWhenPostQuantumNegotiationStarts() async throws { let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.") unexpectedNegotiationFailure.isInverted = true @@ -124,9 +124,9 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { } peerExchangeActor.delegate = KeyExchangingResultStub(onReceivePostQuantumKey: { preSharedKey, ephemeralKey in - multiHopPeerExchanger.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey) + await multiHopPeerExchanger.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey) }) - multiHopPeerExchanger.start() + await multiHopPeerExchanger.start() wait( for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], @@ -134,7 +134,7 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { ) } - func testEphemeralPeerExchangeSuccessWhenDaitaNegotiationStarts() throws { + func testEphemeralPeerExchangeSuccessWhenDaitaNegotiationStarts() async throws { let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.") unexpectedNegotiationFailure.isInverted = true @@ -162,9 +162,9 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase { } peerExchangeActor.delegate = KeyExchangingResultStub(onReceiveEphemeralPeerPrivateKey: { ephemeralKey in - multiHopPeerExchanger.receiveEphemeralPeerPrivateKey(ephemeralKey) + await multiHopPeerExchanger.receiveEphemeralPeerPrivateKey(ephemeralKey) }) - multiHopPeerExchanger.start() + await multiHopPeerExchanger.start() wait( for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], diff --git a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift index 2ce3558fba..deb402abeb 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 testEphemeralPeerExchangeFailsWhenNegotiationCannotStart() { + func testEphemeralPeerExchangeFailsWhenNegotiationCannotStart() async { let expectedNegotiationFailure = expectation(description: "Negotiation failed.") let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place") @@ -66,7 +66,7 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { expectedNegotiationFailure.fulfill() } - singleHopPostQuantumKeyExchanging.start() + await singleHopPostQuantumKeyExchanging.start() wait( for: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], @@ -74,7 +74,7 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { ) } - func testEphemeralPeerExchangeSuccessWhenPostQuantumNegotiationStarts() throws { + func testEphemeralPeerExchangeSuccessWhenPostQuantumNegotiationStarts() async throws { let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.") unexpectedNegotiationFailure.isInverted = true @@ -101,9 +101,9 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { } keyExchangeActor.delegate = KeyExchangingResultStub(onReceivePostQuantumKey: { preSharedKey, ephemeralKey in - singleHopPostQuantumKeyExchanging.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey) + await singleHopPostQuantumKeyExchanging.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey) }) - singleHopPostQuantumKeyExchanging.start() + await singleHopPostQuantumKeyExchanging.start() wait( for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], @@ -111,7 +111,7 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { ) } - func testEphemeralPeerExchangeSuccessWhenDaitaNegotiationStarts() throws { + func testEphemeralPeerExchangeSuccessWhenDaitaNegotiationStarts() async throws { let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.") unexpectedNegotiationFailure.isInverted = true @@ -138,9 +138,9 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase { } peerExchangeActor.delegate = KeyExchangingResultStub(onReceiveEphemeralPeerPrivateKey: { ephemeralKey in - multiHopPeerExchanger.receiveEphemeralPeerPrivateKey(ephemeralKey) + await multiHopPeerExchanger.receiveEphemeralPeerPrivateKey(ephemeralKey) }) - multiHopPeerExchanger.start() + await multiHopPeerExchanger.start() wait( for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful], |
