summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2024-12-13 16:50:45 +0100
committerEmīls <emils@mullvad.net>2024-12-19 17:05:28 +0100
commitf1df6d52b322f19d15abf784d6ea66b02b7f501b (patch)
tree3f99a52879dc3ea3b3f7260e06aeebe037543d50
parentb3229881746dcc6c44db0489745f7a9f395b9eae (diff)
downloadmullvadvpn-f1df6d52b322f19d15abf784d6ea66b02b7f501b.tar.xz
mullvadvpn-f1df6d52b322f19d15abf784d6ea66b02b7f501b.zip
Use IAN TCP connection for ephemeral peer exchange
-rw-r--r--ios/MullvadRustRuntime/EphemeralPeerExchangeActor.swift89
-rw-r--r--ios/MullvadRustRuntime/EphemeralPeerNegotiator.swift40
-rw-r--r--ios/MullvadRustRuntime/EphemeralPeerReceiver.swift52
-rw-r--r--ios/MullvadRustRuntime/PacketTunnelProvider+TCPConnection.swift118
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h86
-rw-r--r--ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift37
-rw-r--r--ios/MullvadTypes/Promise.swift16
-rw-r--r--ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift33
-rw-r--r--ios/MullvadTypes/Protocols/TunnelProvider.swift27
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj17
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift6
-rw-r--r--ios/PacketTunnel/PostQuantum/MultiHopEphemeralPeerExchanger.swift32
-rw-r--r--ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift17
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift8
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift3
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift3
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift9
-rw-r--r--ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift24
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift4
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/KeyExchangingResultStub.swift12
-rw-r--r--ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift16
-rw-r--r--ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift16
-rw-r--r--mullvad-ios/src/encrypted_dns_proxy.rs6
-rw-r--r--mullvad-ios/src/ephemeral_peer_proxy/ios_runtime.rs188
-rw-r--r--mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs353
-rw-r--r--mullvad-ios/src/ephemeral_peer_proxy/mod.rs169
-rw-r--r--mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs173
-rw-r--r--talpid-tunnel-config-client/src/lib.rs6
28 files changed, 722 insertions, 838 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],
diff --git a/mullvad-ios/src/encrypted_dns_proxy.rs b/mullvad-ios/src/encrypted_dns_proxy.rs
index 2aa83d833d..f23482f355 100644
--- a/mullvad-ios/src/encrypted_dns_proxy.rs
+++ b/mullvad-ios/src/encrypted_dns_proxy.rs
@@ -1,8 +1,10 @@
use crate::ProxyHandle;
use libc::c_char;
-use mullvad_encrypted_dns_proxy::state::{EncryptedDnsProxyState as State, FetchConfigError};
-use mullvad_encrypted_dns_proxy::Forwarder;
+use mullvad_encrypted_dns_proxy::{
+ state::{EncryptedDnsProxyState as State, FetchConfigError},
+ Forwarder,
+};
use std::{
io, mem,
net::{Ipv4Addr, SocketAddr},
diff --git a/mullvad-ios/src/ephemeral_peer_proxy/ios_runtime.rs b/mullvad-ios/src/ephemeral_peer_proxy/ios_runtime.rs
deleted file mode 100644
index 19107689ab..0000000000
--- a/mullvad-ios/src/ephemeral_peer_proxy/ios_runtime.rs
+++ /dev/null
@@ -1,188 +0,0 @@
-use super::{
- ios_tcp_connection::*, EphemeralPeerCancelToken, EphemeralPeerParameters, PacketTunnelBridge,
-};
-use libc::c_void;
-use std::{
- io, ptr,
- sync::{Arc, Mutex},
-};
-use talpid_tunnel_config_client::{request_ephemeral_peer_with, Error, RelayConfigService};
-use talpid_types::net::wireguard::{PrivateKey, PublicKey};
-use tokio::runtime::Handle as TokioHandle;
-use tonic::transport::channel::Endpoint;
-use tower::util::service_fn;
-
-/// # Safety
-/// packet_tunnel and tcp_connection must be valid pointers to a packet tunnel and a TCP connection
-/// instances.
-pub unsafe fn run_ephemeral_peer_exchange(
- pub_key: [u8; 32],
- ephemeral_key: [u8; 32],
- packet_tunnel_bridge: PacketTunnelBridge,
- peer_parameters: EphemeralPeerParameters,
- tokio_handle: TokioHandle,
-) -> Result<EphemeralPeerCancelToken, Error> {
- match unsafe {
- IOSRuntime::new(
- pub_key,
- ephemeral_key,
- packet_tunnel_bridge,
- peer_parameters,
- )
- } {
- Ok(runtime) => {
- let token = runtime.packet_tunnel.tcp_connection.clone();
- runtime.run(tokio_handle);
- Ok(EphemeralPeerCancelToken {
- context: Arc::into_raw(token) as *mut _,
- })
- }
- Err(err) => {
- log::error!("Failed to create runtime {}", err);
- Err(Error::UnableToCreateRuntime)
- }
- }
-}
-
-#[derive(Clone)]
-pub struct SwiftContext {
- pub packet_tunnel: *const c_void,
- pub tcp_connection: Arc<Mutex<ConnectionContext>>,
-}
-
-unsafe impl Send for SwiftContext {}
-unsafe impl Sync for SwiftContext {}
-
-struct IOSRuntime {
- pub_key: [u8; 32],
- ephemeral_key: [u8; 32],
- packet_tunnel: SwiftContext,
- peer_parameters: EphemeralPeerParameters,
-}
-
-impl IOSRuntime {
- pub unsafe fn new(
- pub_key: [u8; 32],
- ephemeral_key: [u8; 32],
- packet_tunnel_bridge: PacketTunnelBridge,
- peer_parameters: EphemeralPeerParameters,
- ) -> io::Result<Self> {
- let context = SwiftContext {
- packet_tunnel: packet_tunnel_bridge.packet_tunnel,
- tcp_connection: Arc::new(Mutex::new(ConnectionContext::new(
- packet_tunnel_bridge.tcp_connection,
- ))),
- };
-
- Ok(Self {
- pub_key,
- ephemeral_key,
- packet_tunnel: context,
- peer_parameters,
- })
- }
-
- pub fn run(self, handle: TokioHandle) {
- handle.spawn(async move {
- self.run_service_inner().await;
- });
- }
- /// Creates a `RelayConfigService` using the in-tunnel TCP Connection provided by the Packet
- /// Tunnel Provider
- ///
- /// ## Safety
- /// It is unsafe to call this with an already used `SwiftContext`
- async unsafe fn ios_tcp_client(
- ctx: SwiftContext,
- ) -> Result<(RelayConfigService, IosTcpShutdownHandle), Error> {
- let endpoint = Endpoint::from_static("tcp://0.0.0.0:0");
-
- let (tcp_provider, conn_handle) = unsafe { IosTcpProvider::new(ctx.tcp_connection) };
- // One (1) TCP connection
- let mut one_tcp_connection = Some(tcp_provider);
- let conn = endpoint
- .connect_with_connector(service_fn(move |_| {
- let connection = one_tcp_connection
- .take()
- .map(hyper_util::rt::tokio::TokioIo::new)
- .ok_or(Error::TcpConnectionExpired);
- async { connection }
- }))
- .await
- .map_err(Error::GrpcConnectError)?;
-
- Ok((RelayConfigService::new(conn), conn_handle))
- }
-
- async fn run_service_inner(self) {
- let (async_provider, shutdown_handle) = unsafe {
- match Self::ios_tcp_client(self.packet_tunnel.clone()).await {
- Ok(result) => result,
- Err(error) => {
- log::error!("Failed to create iOS TCP client: {error}");
- swift_ephemeral_peer_ready(
- self.packet_tunnel.packet_tunnel,
- ptr::null(),
- ptr::null(),
- );
- return;
- }
- }
- };
- // Use `self.ephemeral_key` as the new private key when no PQ but yes DAITA
- let ephemeral_pub_key = PrivateKey::from(self.ephemeral_key).public_key();
-
- tokio::select! {
- ephemeral_peer = request_ephemeral_peer_with(
- async_provider,
- PublicKey::from(self.pub_key),
- ephemeral_pub_key,
- self.peer_parameters.enable_post_quantum,
- self.peer_parameters.enable_daita,
- ) => {
- shutdown_handle.shutdown();
- if let Ok(mut connection) = self.packet_tunnel.tcp_connection.lock() {
- connection.shutdown();
- }
- match ephemeral_peer {
- Ok(peer) => {
- match peer.psk {
- Some(preshared_key) => unsafe {
- let preshared_key_bytes = preshared_key.as_bytes();
- swift_ephemeral_peer_ready(self.packet_tunnel.packet_tunnel,
- preshared_key_bytes.as_ptr(),
- self.ephemeral_key.as_ptr());
- },
- None => {
- // Daita peer was requested, but without enabling post quantum keys
- unsafe {
- swift_ephemeral_peer_ready(self.packet_tunnel.packet_tunnel,
- ptr::null(),
- self.ephemeral_key.as_ptr());
- }
- }
- }
- },
- Err(error) => {
- log::error!("Key exchange failed {}", error);
- unsafe {
- swift_ephemeral_peer_ready(self.packet_tunnel.packet_tunnel,
- ptr::null(),
- ptr::null());
- }
- }
- }
- }
-
- _ = tokio::time::sleep(std::time::Duration::from_secs(self.peer_parameters.peer_exchange_timeout)) => {
- if let Ok(mut connection) = self.packet_tunnel.tcp_connection.lock() {
- connection.shutdown();
- };
- shutdown_handle.shutdown();
- unsafe { swift_ephemeral_peer_ready(self.packet_tunnel.packet_tunnel,
- ptr::null(),
- ptr::null()); }
- }
- }
- }
-}
diff --git a/mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs b/mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs
index d91081fe57..3244e5f2d9 100644
--- a/mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs
+++ b/mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs
@@ -1,35 +1,77 @@
use libc::c_void;
use std::{
- io::{self, Result},
- sync::{Arc, Mutex, MutexGuard, Weak},
- task::{Poll, Waker},
-};
-use tokio::{
- io::{AsyncRead, AsyncWrite},
- sync::mpsc,
+ ffi::CStr,
+ future::Future,
+ io::{self},
+ pin::Pin,
+ task::{ready, Poll},
+ time::Duration,
};
+use tokio::io::{AsyncRead, AsyncWrite};
+
+use super::EphemeralPeerParameters;
fn connection_closed_err() -> io::Error {
io::Error::new(io::ErrorKind::BrokenPipe, "TCP connection closed")
}
-extern "C" {
- /// 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.
- pub fn swift_nw_tcp_connection_send(
- connection: *const libc::c_void,
- data: *const libc::c_void,
- data_len: usize,
- sender: *const libc::c_void,
- );
+#[derive(Clone, Copy)]
+#[repr(C)]
+pub struct WgTcpConnectionFunctions {
+ pub open_fn:
+ unsafe extern "C" fn(tunnelHandle: i32, address: *const libc::c_char, timeout: u64) -> i32,
+ pub close_fn: unsafe extern "C" fn(tunnelHandle: i32, socketHandle: i32) -> i32,
+ pub recv_fn:
+ unsafe extern "C" fn(tunnelHandle: i32, socketHandle: i32, data: *mut u8, len: i32) -> i32,
+ pub send_fn: unsafe extern "C" fn(
+ tunnelHandle: i32,
+ socketHandle: i32,
+ data: *const u8,
+ len: i32,
+ ) -> i32,
+}
- /// 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.
- pub fn swift_nw_tcp_connection_read(
- connection: *const libc::c_void,
- sender: *const libc::c_void,
- );
+impl WgTcpConnectionFunctions {
+ /// # Safety
+ /// This function is safe to call so long as the function pointer is valid for its declared
+ /// signature.
+ pub unsafe fn open(&self, tunnel_handle: i32, address: *const u8, timeout: u64) -> i32 {
+ unsafe { (self.open_fn)(tunnel_handle, address.cast(), timeout) }
+ }
+
+ /// # Safety
+ /// This function is safe to call so long as the function pointer is valid for its declared
+ /// signature.
+ pub unsafe fn close(&self, tunnel_handle: i32, socket_handle: i32) -> i32 {
+ unsafe { (self.close_fn)(tunnel_handle, socket_handle) }
+ }
+
+ /// # Safety
+ /// This function is safe to call so long as the function pointer is valid for its declared
+ /// signature.
+ pub unsafe fn receive(&self, tunnel_handle: i32, socket_handle: i32, data: &mut [u8]) -> i32 {
+ let ptr = data.as_mut_ptr();
+ let len = data
+ .len()
+ .try_into()
+ .expect("Cannot receive a buffer larger than 2GiB");
+ unsafe { (self.recv_fn)(tunnel_handle, socket_handle, ptr.cast(), len) }
+ }
+
+ /// # Safety
+ /// This function is safe to call so long as the function pointer is valid for its declared
+ /// signature.
+ pub unsafe fn send(&self, tunnel_handle: i32, socket_handle: i32, data: &[u8]) -> i32 {
+ let ptr = data.as_ptr();
+ let len = data
+ .len()
+ .try_into()
+ .expect("Cannot send a buffer larger than 2GiB");
+ unsafe { (self.send_fn)(tunnel_handle, socket_handle, ptr.cast(), len) }
+ }
+}
+extern "C" {
/// Called when the preshared post quantum key is ready,
/// or when a Daita peer has been successfully requested.
/// `raw_preshared_key` will be NULL if:
@@ -40,181 +82,188 @@ extern "C" {
raw_preshared_key: *const u8,
raw_ephemeral_private_key: *const u8,
);
-}
-unsafe impl Send for IosTcpProvider {}
+}
+#[derive(Clone)]
pub struct IosTcpProvider {
- write_tx: Arc<mpsc::UnboundedSender<usize>>,
- write_rx: mpsc::UnboundedReceiver<usize>,
- read_tx: Arc<mpsc::UnboundedSender<Box<[u8]>>>,
- read_rx: mpsc::UnboundedReceiver<Box<[u8]>>,
- tcp_connection: Arc<Mutex<ConnectionContext>>,
- read_in_progress: bool,
- write_in_progress: bool,
+ tunnel_handle: i32,
+ timeout: Duration,
+ funcs: WgTcpConnectionFunctions,
}
-pub struct IosTcpShutdownHandle {
- context: Arc<Mutex<ConnectionContext>>,
-}
+type InFlightIoTask = Option<Pin<Box<tokio::task::JoinHandle<io::Result<Vec<u8>>>>>>;
-pub struct ConnectionContext {
- waker: Option<Waker>,
- tcp_connection: Option<*const c_void>,
+pub struct IosTcpConnection {
+ tunnel_handle: i32,
+ socket_handle: i32,
+ funcs: WgTcpConnectionFunctions,
+ in_flight_read: InFlightIoTask,
+ in_flight_write: InFlightIoTask,
}
-unsafe impl Send for ConnectionContext {}
+#[derive(Debug)]
+pub enum WgTcpError {
+ /// Failed to open the socket
+ Open,
+ /// Panicked during opening of the socket
+ Panic,
+}
impl IosTcpProvider {
- /// # Safety
- /// `connection` must be pointing to a valid instance of a `NWTCPConnection`, created by the
- /// `PacketTunnelProvider`
- pub unsafe fn new(connection: Arc<Mutex<ConnectionContext>>) -> (Self, IosTcpShutdownHandle) {
- let (tx, rx) = mpsc::unbounded_channel();
- let (recv_tx, recv_rx) = mpsc::unbounded_channel();
-
- (
- Self {
- write_tx: Arc::new(tx),
- write_rx: rx,
- read_tx: Arc::new(recv_tx),
- read_rx: recv_rx,
- tcp_connection: connection.clone(),
- read_in_progress: false,
- write_in_progress: false,
- },
- IosTcpShutdownHandle {
- context: connection,
- },
- )
+ pub fn new(tunnel_handle: i32, params: EphemeralPeerParameters) -> Self {
+ Self {
+ tunnel_handle,
+ timeout: Duration::from_secs(params.peer_exchange_timeout),
+ funcs: params.funcs,
+ }
}
- fn maybe_set_waker(new_waker: Waker, connection: &mut MutexGuard<'_, ConnectionContext>) {
- connection.waker = Some(new_waker);
+ pub async fn connect(&self, address: &'static CStr) -> Result<IosTcpConnection, WgTcpError> {
+ let tunnel_handle = self.tunnel_handle;
+ let timeout = self.timeout.as_secs();
+ let funcs = self.funcs;
+ let result = tokio::task::spawn_blocking(move || unsafe {
+ // SAFETY
+ // The `open_fn` function pointer in `funcs` must be valid.
+ funcs.open(tunnel_handle, address.as_ptr() as *const _, timeout)
+ })
+ .await
+ .map_err(|_| WgTcpError::Panic)?;
+
+ if result < 0 {
+ return Err(WgTcpError::Open);
+ }
+
+ Ok(IosTcpConnection {
+ tunnel_handle,
+ socket_handle: result,
+ funcs: self.funcs,
+ in_flight_read: None,
+ in_flight_write: None,
+ })
}
}
-impl IosTcpShutdownHandle {
- pub fn shutdown(self) {
- let Ok(mut context) = self.context.lock() else {
- return;
- };
-
- context.tcp_connection = None;
- if let Some(waker) = context.waker.take() {
- waker.wake();
- }
- std::mem::drop(context);
+impl Drop for IosTcpConnection {
+ fn drop(&mut self) {
+ // Safety
+ // `funcs.close_fn` must be a valid function pointer.
+ unsafe { self.funcs.close(self.tunnel_handle, self.socket_handle) };
}
}
-impl AsyncWrite for IosTcpProvider {
+impl AsyncWrite for IosTcpConnection {
fn poll_write(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
- ) -> std::task::Poll<Result<usize>> {
- let connection_lock = self.tcp_connection.clone();
- let Ok(mut connection) = connection_lock.lock() else {
- return Poll::Ready(Err(connection_closed_err()));
- };
- let Some(tcp_ptr) = connection.tcp_connection else {
- return Poll::Ready(Err(connection_closed_err()));
- };
- Self::maybe_set_waker(cx.waker().clone(), &mut connection);
-
- match self.write_rx.poll_recv(cx) {
- std::task::Poll::Ready(Some(bytes_sent)) => {
- self.write_in_progress = false;
- Poll::Ready(Ok(bytes_sent))
- }
- std::task::Poll::Ready(None) => {
- self.write_in_progress = false;
- Poll::Ready(Err(connection_closed_err()))
- }
- std::task::Poll::Pending => {
- if !self.write_in_progress {
- let raw_sender = Weak::into_raw(Arc::downgrade(&self.write_tx));
- unsafe {
- swift_nw_tcp_connection_send(
- tcp_ptr,
- buf.as_ptr() as _,
- buf.len(),
- raw_sender as _,
- );
- }
- self.write_in_progress = true;
+ ) -> std::task::Poll<io::Result<usize>> {
+ // If task is already spawned, poll it
+ if let Some(handle) = &mut self.in_flight_write {
+ let result = match ready!(handle.as_mut().poll(cx)) {
+ Ok(Ok(written)) => Ok(written.len()),
+ Ok(Err(e)) => Err(e),
+ Err(_) => Err(io::Error::new(io::ErrorKind::Other, "Write task panicked")),
+ };
+ // important to clear the in flight write here.
+ self.in_flight_write = None;
+ Poll::Ready(result)
+ } else {
+ // if no write task has been spawned, spawn one
+ let tunnel_handle = self.tunnel_handle;
+ let socket_handle = self.socket_handle;
+ // The data has to be cloned, since it will be moved into another thread and it has to
+ // outlive this function call.
+ let data = buf.to_vec();
+ let funcs = self.funcs;
+ let task = tokio::task::spawn_blocking(move || {
+ // Safety
+ // `funcs.send_fn` must be a valid function pointer.
+ let result = unsafe { funcs.send(tunnel_handle, socket_handle, data.as_slice()) };
+ if result < 0 {
+ Err(io::Error::new(
+ io::ErrorKind::Other,
+ format!("Write error: {}", result),
+ ))
+ } else {
+ Ok(data[..result as usize].to_vec())
}
- std::task::Poll::Pending
- }
+ });
+
+ self.in_flight_write = Some(Box::pin(task));
+ cx.waker().wake_by_ref();
+ Poll::Pending
}
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_: &mut std::task::Context<'_>,
- ) -> std::task::Poll<Result<()>> {
+ ) -> std::task::Poll<io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
_: &mut std::task::Context<'_>,
- ) -> std::task::Poll<Result<()>> {
+ ) -> std::task::Poll<io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
-impl AsyncRead for IosTcpProvider {
+
+impl AsyncRead for IosTcpConnection {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
- ) -> std::task::Poll<std::io::Result<()>> {
- let connection_lock = self.tcp_connection.clone();
- let Ok(mut connection) = connection_lock.lock() else {
- return Poll::Ready(Err(connection_closed_err()));
- };
- let Some(tcp_ptr) = connection.tcp_connection else {
- return Poll::Ready(Err(connection_closed_err()));
- };
- Self::maybe_set_waker(cx.waker().clone(), &mut connection);
-
- match self.read_rx.poll_recv(cx) {
- std::task::Poll::Ready(Some(data)) => {
- buf.put_slice(&data);
- self.read_in_progress = false;
- Poll::Ready(Ok(()))
- }
- std::task::Poll::Ready(None) => {
- self.read_in_progress = false;
- Poll::Ready(Err(connection_closed_err()))
- }
- std::task::Poll::Pending => {
- if !self.read_in_progress {
- let raw_sender = Weak::into_raw(Arc::downgrade(&self.read_tx));
- unsafe {
- swift_nw_tcp_connection_read(tcp_ptr, raw_sender as _);
- }
- self.read_in_progress = true;
+ ) -> std::task::Poll<io::Result<()>> {
+ // If task is already spawned, poll it
+ if let Some(handle) = &mut self.in_flight_read {
+ let result = match ready!(handle.as_mut().poll(cx)) {
+ Ok(Ok(data)) => {
+ // We are assuming that the buffer has not been used for anything else between
+ // spawning the task and writing to it now, since we expect `buf.remaining()`
+ // to return the same value between those two points in time.
+ let len = data.len().min(buf.remaining());
+ buf.put_slice(&data[..len]);
+ Ok(())
}
- Poll::Pending
- }
- }
- }
-}
+ Ok(Err(e)) => Err(e),
+ Err(_) => Err(io::Error::new(io::ErrorKind::Other, "Read task panicked")),
+ };
+ // Clear the in-flight read, since the read task finished
+ self.in_flight_read = None;
+ Poll::Ready(result)
+ } else {
+ // If no read task has been spawned, spawn one
+ let tunnel_handle = self.tunnel_handle;
+ let socket_handle = self.socket_handle;
+ let funcs = self.funcs;
+ let mut buffer = vec![0u8; buf.remaining()];
+ let task = tokio::task::spawn_blocking(move || {
+ // Safety
+ // `funcs.receive_fn` must be a valid function pointer.
+ let result =
+ unsafe { funcs.receive(tunnel_handle, socket_handle, buffer.as_mut_slice()) };
+ match result {
+ size @ 1.. => {
+ buffer.truncate(size as usize);
+ Ok(buffer)
+ }
-impl ConnectionContext {
- pub fn new(tcp_connection: *const c_void) -> Self {
- Self {
- tcp_connection: Some(tcp_connection),
- waker: None,
- }
- }
+ errval @ ..0 => Err(io::Error::new(
+ io::ErrorKind::Other,
+ format!("Read error: {}", errval),
+ )),
+
+ 0 => Err(connection_closed_err()),
+ }
+ });
- pub fn shutdown(&mut self) {
- self.tcp_connection = None;
- if let Some(waker) = self.waker.take() {
- waker.wake();
+ self.in_flight_read = Some(Box::pin(task));
+ cx.waker().wake_by_ref();
+ Poll::Pending
}
}
}
diff --git a/mullvad-ios/src/ephemeral_peer_proxy/mod.rs b/mullvad-ios/src/ephemeral_peer_proxy/mod.rs
index c69b7d6b3b..70d9347ad7 100644
--- a/mullvad-ios/src/ephemeral_peer_proxy/mod.rs
+++ b/mullvad-ios/src/ephemeral_peer_proxy/mod.rs
@@ -1,58 +1,46 @@
#![cfg(target_os = "ios")]
-pub mod ios_runtime;
pub mod ios_tcp_connection;
+pub mod peer_exchange;
-use ios_runtime::run_ephemeral_peer_exchange;
-use ios_tcp_connection::ConnectionContext;
+use ios_tcp_connection::swift_ephemeral_peer_ready;
use libc::c_void;
-use std::sync::{Arc, Mutex, Weak};
-use tokio::sync::mpsc;
+use peer_exchange::EphemeralPeerExchange;
-use std::sync::Once;
+use std::{ptr, sync::Once};
static INIT_LOGGING: Once = Once::new();
-#[repr(C)]
-pub struct EphemeralPeerCancelToken {
- // Must keep a pointer to a valid std::sync::Arc<tokio::mpsc::UnboundedSender>
- pub context: *mut c_void,
-}
-
+#[derive(Clone)]
pub struct PacketTunnelBridge {
pub packet_tunnel: *const c_void,
- pub tcp_connection: *const c_void,
+ pub tunnel_handle: i32,
}
-pub struct EphemeralPeerParameters {
- pub peer_exchange_timeout: u64,
- pub enable_post_quantum: bool,
- pub enable_daita: bool,
-}
+impl PacketTunnelBridge {
+ fn fail_exchange(self) {
+ unsafe { swift_ephemeral_peer_ready(self.packet_tunnel, ptr::null(), ptr::null()) };
+ }
-impl EphemeralPeerCancelToken {
- /// # Safety
- /// This function can only be called when the context pointer is valid.
- unsafe fn cancel(&self) {
- // # Safety
- // Try to take the value, if there is a value, we can safely send the message, otherwise,
- // assume it has been dropped and nothing happens
- let connection_context: Arc<Mutex<ConnectionContext>> =
- unsafe { Arc::from_raw(self.context as _) };
- if let Ok(mut connection) = connection_context.lock() {
- connection.shutdown();
- }
+ fn succeed_exchange(self, ephemeral_key: [u8; 32], preshared_key: Option<[u8; 32]>) {
+ let ephemeral_ptr = ephemeral_key.as_ptr();
+ let preshared_ptr = preshared_key
+ .as_ref()
+ .map(|key| key.as_ptr())
+ .unwrap_or(ptr::null());
- // Call std::mem::forget here to avoid dropping the channel.
- std::mem::forget(connection_context);
+ unsafe { swift_ephemeral_peer_ready(self.packet_tunnel, preshared_ptr, ephemeral_ptr) };
}
}
-impl Drop for EphemeralPeerCancelToken {
- fn drop(&mut self) {
- let _: Arc<Mutex<ConnectionContext>> = unsafe { Arc::from_raw(self.context as _) };
- }
-}
+unsafe impl Send for PacketTunnelBridge {}
-unsafe impl Send for EphemeralPeerCancelToken {}
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct EphemeralPeerParameters {
+ pub peer_exchange_timeout: u64,
+ pub enable_post_quantum: bool,
+ pub enable_daita: bool,
+ pub funcs: ios_tcp_connection::WgTcpConnectionFunctions,
+}
/// Called by the Swift side to signal that the ephemeral peer exchange should be cancelled.
/// After this call, the cancel token is no longer valid.
@@ -61,64 +49,25 @@ unsafe impl Send for EphemeralPeerCancelToken {}
/// `sender` must be pointing to a valid instance of a `EphemeralPeerCancelToken` created by the
/// `PacketTunnelProvider`.
#[no_mangle]
-pub unsafe extern "C" fn cancel_ephemeral_peer_exchange(sender: *const EphemeralPeerCancelToken) {
- let sender = unsafe { &*sender };
+pub unsafe extern "C" fn cancel_ephemeral_peer_exchange(
+ sender: *mut peer_exchange::ExchangeCancelToken,
+) {
+ let sender = unsafe { Box::from_raw(sender) };
sender.cancel();
}
-/// 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`.
#[no_mangle]
pub unsafe extern "C" fn drop_ephemeral_peer_exchange_token(
- sender: *const EphemeralPeerCancelToken,
+ sender: *mut peer_exchange::ExchangeCancelToken,
) {
- let _sender = unsafe { std::ptr::read(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.
-#[no_mangle]
-pub unsafe extern "C" fn handle_sent(bytes_sent: usize, sender: *const c_void) {
- let weak_tx: Weak<mpsc::UnboundedSender<usize>> = unsafe { Weak::from_raw(sender as _) };
- if let Some(send_tx) = weak_tx.upgrade() {
- _ = send_tx.send(bytes_sent);
- }
-}
-
-/// 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.
-#[no_mangle]
-pub unsafe extern "C" fn handle_recv(data: *const u8, mut data_len: usize, sender: *const c_void) {
- let weak_tx: Weak<mpsc::UnboundedSender<Box<[u8]>>> = unsafe { Weak::from_raw(sender as _) };
-
- if data.is_null() {
- data_len = 0;
- }
- let mut bytes = vec![0u8; data_len];
- if !data.is_null() {
- std::ptr::copy_nonoverlapping(data, bytes.as_mut_ptr(), data_len);
- }
- if let Some(read_tx) = weak_tx.upgrade() {
- _ = read_tx.send(bytes.into_boxed_slice());
- }
+ // drop the cancel token
+ let _sender = unsafe { Box::from_raw(sender) };
}
/// Entry point for requesting ephemeral peers on iOS.
@@ -126,61 +75,43 @@ pub unsafe extern "C" fn handle_recv(data: *const u8, mut data_len: usize, sende
/// # 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.
+/// `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.
#[no_mangle]
pub unsafe extern "C" fn request_ephemeral_peer(
public_key: *const u8,
ephemeral_key: *const u8,
packet_tunnel: *const c_void,
- tcp_connection: *const c_void,
- cancel_token: *mut EphemeralPeerCancelToken,
- peer_exchange_timeout: u64,
- enable_post_quantum: bool,
- enable_daita: bool,
-) -> i32 {
+ tunnel_handle: i32,
+ peer_parameters: EphemeralPeerParameters,
+) -> *mut peer_exchange::ExchangeCancelToken {
INIT_LOGGING.call_once(|| {
let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.TTCC")
.level_filter(log::LevelFilter::Debug)
.init();
});
- 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 pub_key: [u8; 32] = unsafe { ptr::read(public_key as *const [u8; 32]) };
+ let eph_key: [u8; 32] = unsafe { 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;
+ return ptr::null_mut();
}
};
let packet_tunnel_bridge = PacketTunnelBridge {
packet_tunnel,
- tcp_connection,
- };
- let peer_parameters = EphemeralPeerParameters {
- peer_exchange_timeout,
- enable_post_quantum,
- enable_daita,
+ tunnel_handle,
};
- match unsafe {
- run_ephemeral_peer_exchange(
- pub_key,
- eph_key,
- packet_tunnel_bridge,
- peer_parameters,
- handle,
- )
- } {
- Ok(token) => {
- unsafe { std::ptr::write(cancel_token, token) };
- 0
- }
- Err(_) => -1,
- }
+ let cancel_token =
+ EphemeralPeerExchange::new(pub_key, eph_key, packet_tunnel_bridge, peer_parameters)
+ .run(handle);
+
+ Box::into_raw(Box::new(cancel_token))
}
diff --git a/mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs b/mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs
new file mode 100644
index 0000000000..ee9a512777
--- /dev/null
+++ b/mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs
@@ -0,0 +1,173 @@
+use super::{ios_tcp_connection::*, EphemeralPeerParameters, PacketTunnelBridge};
+use std::{ffi::CStr, sync::Mutex, thread};
+use talpid_tunnel_config_client::{request_ephemeral_peer_with, Error, RelayConfigService};
+use talpid_types::net::wireguard::{PrivateKey, PublicKey};
+use tokio::{runtime::Handle as TokioHandle, task::JoinHandle};
+use tonic::transport::channel::Endpoint;
+use tower::util::service_fn;
+
+const GRPC_HOST_CSTR: &CStr = c"10.64.0.1:1337";
+
+pub struct ExchangeCancelToken {
+ inner: Mutex<CancelToken>,
+}
+
+impl ExchangeCancelToken {
+ fn new(tokio_handle: TokioHandle, task: JoinHandle<()>) -> Self {
+ let inner = CancelToken {
+ tokio_handle,
+ task: Some(task),
+ };
+ Self {
+ inner: Mutex::new(inner),
+ }
+ }
+
+ /// Blocks until the associated ephemeral peer exchange task is finished.
+ pub fn cancel(&self) {
+ if let Ok(mut inner) = self.inner.lock() {
+ if let Some(task) = inner.task.take() {
+ task.abort();
+ let _ = inner.tokio_handle.block_on(task);
+ }
+ }
+ }
+}
+
+struct CancelToken {
+ tokio_handle: TokioHandle,
+ task: Option<JoinHandle<()>>,
+}
+
+pub struct EphemeralPeerExchange {
+ pub_key: [u8; 32],
+ ephemeral_key: [u8; 32],
+ packet_tunnel: PacketTunnelBridge,
+ peer_parameters: EphemeralPeerParameters,
+}
+
+// # Safety
+// This is safe because the void pointer in PacketTunnelBridge is valid for the lifetime of the
+// process where this type is intended to be used.
+unsafe impl Send for EphemeralPeerExchange {}
+
+impl EphemeralPeerExchange {
+ pub fn new(
+ pub_key: [u8; 32],
+ ephemeral_key: [u8; 32],
+ packet_tunnel: PacketTunnelBridge,
+ peer_parameters: EphemeralPeerParameters,
+ ) -> EphemeralPeerExchange {
+ Self {
+ pub_key,
+ ephemeral_key,
+ packet_tunnel,
+ peer_parameters,
+ }
+ }
+
+ pub fn run(self, tokio: TokioHandle) -> ExchangeCancelToken {
+ let task = tokio.spawn(async move {
+ self.run_service_inner().await;
+ });
+
+ ExchangeCancelToken::new(tokio, task)
+ }
+
+ /// Creates a `RelayConfigService` using the in-tunnel TCP Connection provided by the Packet
+ /// Tunnel Provider
+ async fn ios_tcp_client(
+ tunnel_handle: i32,
+ peer_parameters: EphemeralPeerParameters,
+ ) -> Result<RelayConfigService, Error> {
+ let endpoint = Endpoint::from_static("tcp://0.0.0.0:0");
+
+ let tcp_provider = IosTcpProvider::new(tunnel_handle, peer_parameters);
+
+ let conn = endpoint
+ // it is assumend that the service function will only be called once.
+ // Yet, by its signature, it is forced to be callable multiple times.
+ // The tcp_provider appeases this constraint, maybe we should rewrite this back to
+ // explicitly only allow a single invocation? It is due to this mismatch between how we
+ // use it and what the interface expects that we are using a oneshot channel to
+ // transfer the shutdown handle.
+ .connect_with_connector(service_fn(move |_| {
+ let provider = tcp_provider.clone();
+ async move {
+ provider
+ .connect(GRPC_HOST_CSTR)
+ .await
+ .map(hyper_util::rt::tokio::TokioIo::new)
+ .map_err(|_| Error::TcpConnectionOpen)
+ }
+ }))
+ .await
+ .map_err(Error::GrpcConnectError)?;
+
+ Ok(RelayConfigService::new(conn))
+ }
+
+ fn report_failure(self) {
+ thread::spawn(move || {
+ self.packet_tunnel.fail_exchange();
+ });
+ }
+
+ async fn run_service_inner(self) {
+ let async_provider = match Self::ios_tcp_client(
+ self.packet_tunnel.tunnel_handle,
+ self.peer_parameters,
+ )
+ .await
+ {
+ Ok(result) => result,
+ Err(error) => {
+ log::error!("Failed to create iOS TCP client: {error}");
+ self.report_failure();
+ return;
+ }
+ };
+ // Use `self.ephemeral_key` as the new private key when no PQ but yes DAITA
+ let ephemeral_pub_key = PrivateKey::from(self.ephemeral_key).public_key();
+
+ tokio::select! {
+ ephemeral_peer = request_ephemeral_peer_with(
+ async_provider,
+ PublicKey::from(self.pub_key),
+ ephemeral_pub_key,
+ self.peer_parameters.enable_post_quantum,
+ self.peer_parameters.enable_daita,
+ ) => {
+ match ephemeral_peer {
+ Ok(peer) => {
+ match peer.psk {
+ Some(preshared_key) => {
+ let preshared_key_bytes = *preshared_key.as_bytes();
+ thread::spawn(move || {
+ let Self{ ephemeral_key, packet_tunnel, .. } = self;
+ packet_tunnel.succeed_exchange(ephemeral_key, Some(preshared_key_bytes));
+ });
+
+ },
+ None => {
+ // Daita peer was requested, but without enabling post quantum keys
+ thread::spawn(move || {
+ let Self{ ephemeral_key, packet_tunnel, .. } = self;
+ packet_tunnel.succeed_exchange(ephemeral_key, None);
+ });
+ }
+ }
+ },
+ Err(error) => {
+ log::error!("Key exchange failed {}", error);
+ self.report_failure();
+ }
+ }
+ }
+
+ _ = tokio::time::sleep(std::time::Duration::from_secs(self.peer_parameters.peer_exchange_timeout)) => {
+ self.report_failure();
+ }
+ }
+ }
+}
diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs
index f7d559f641..7c80d4f3e5 100644
--- a/talpid-tunnel-config-client/src/lib.rs
+++ b/talpid-tunnel-config-client/src/lib.rs
@@ -40,7 +40,7 @@ pub enum Error {
},
MissingDaitaResponse,
#[cfg(target_os = "ios")]
- TcpConnectionExpired,
+ TcpConnectionOpen,
#[cfg(target_os = "ios")]
UnableToCreateRuntime,
}
@@ -49,7 +49,7 @@ impl std::fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Error::*;
match self {
- GrpcConnectError(_) => "Failed to connect to config service".fmt(f),
+ GrpcConnectError(err) => write!(f, "Failed to connect to config service: {err:?}"),
GrpcError(status) => write!(f, "RPC failed: {status}"),
MissingCiphertexts => write!(f, "Found no ciphertexts in response"),
InvalidCiphertextLength {
@@ -65,7 +65,7 @@ impl std::fmt::Display for Error {
}
MissingDaitaResponse => "Expected DAITA configuration in response".fmt(f),
#[cfg(target_os = "ios")]
- TcpConnectionExpired => "TCP connection is already shut down".fmt(f),
+ TcpConnectionOpen => "Failed to open TCP connection".fmt(f),
#[cfg(target_os = "ios")]
UnableToCreateRuntime => "Unable to create iOS PQ PSK runtime".fmt(f),
}