summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadPostQuantum/MullvadPostQuantum.h11
-rw-r--r--ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift68
-rw-r--r--ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift12
-rw-r--r--ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h39
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift6
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift24
-rw-r--r--ios/PacketTunnel/PostQuantumKeyExchangeActor.swift43
-rw-r--r--ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift1
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift84
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift94
-rw-r--r--talpid-routing/src/unix/mod.rs2
-rw-r--r--talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs23
-rw-r--r--talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs68
-rw-r--r--talpid-tunnel-config-client/src/ios_ffi/mod.rs85
-rw-r--r--talpid-tunnel-config-client/src/lib.rs42
16 files changed, 390 insertions, 216 deletions
diff --git a/ios/MullvadPostQuantum/MullvadPostQuantum.h b/ios/MullvadPostQuantum/MullvadPostQuantum.h
index f48ca77519..c196b0527b 100644
--- a/ios/MullvadPostQuantum/MullvadPostQuantum.h
+++ b/ios/MullvadPostQuantum/MullvadPostQuantum.h
@@ -7,13 +7,4 @@
//
#import <Foundation/Foundation.h>
-
-//! Project version number for MullvadPostQuantum.
-FOUNDATION_EXPORT double MullvadPostQuantumVersionNumber;
-
-//! Project version string for MullvadPostQuantum.
-FOUNDATION_EXPORT const unsigned char MullvadPostQuantumVersionString[];
-
-// In this header, you should import all the public headers of your framework using statements like #import <MullvadPostQuantum/PublicHeader.h>
-
-
+#import "talpid_tunnel_config_client.h"
diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift
index 8b06c7b178..b3bf815b1b 100644
--- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift
+++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift
@@ -12,46 +12,82 @@ import NetworkExtension
import TalpidTunnelConfigClientProxy
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(
- connection: UnsafeMutableRawPointer?,
- data: UnsafeMutableRawPointer,
+ rawConnection: UnsafeMutableRawPointer?,
+ rawData: UnsafeMutableRawPointer,
dataLength: UInt,
- sender: UnsafeMutableRawPointer?
+ rawWriteAcknowledgement: UnsafeMutableRawPointer?
) {
- guard let connection, let sender else { return }
- let tcpConnection = Unmanaged<NWTCPConnection>.fromOpaque(connection).takeUnretainedValue()
- let rawData = Data(bytes: data, count: Int(dataLength))
+ 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 no more than 2 writes happen in parallel is done by virtue of not returning the execution context
+ // 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(rawData, completionHandler: { maybeError in
+ tcpConnection.write(data, completionHandler: { maybeError in
if maybeError != nil {
- handle_sent(0, sender)
+ handle_sent(0, rawWriteAcknowledgement)
} else {
- handle_sent(dataLength, sender)
+ 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(
- connection: UnsafeMutableRawPointer?,
- sender: UnsafeMutableRawPointer?
+ rawConnection: UnsafeMutableRawPointer?,
+ rawReadAcknowledgement: UnsafeMutableRawPointer?
) {
- guard let connection, let sender else { return }
- let tcpConnection = Unmanaged<NWTCPConnection>.fromOpaque(connection).takeUnretainedValue()
+ 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(Data().map { $0 }, 0, sender)
+ handle_recv(nil, 0, rawReadAcknowledgement)
} else {
- handle_recv(data.map { $0 }, UInt(data.count), sender)
+ handle_recv(data.map { $0 }, UInt(data.count), rawReadAcknowledgement)
}
}
}
}
+/// End sequence of a quantum-secure pre shared key exchange.
+///
+/// This FFI function is called by Rust when the quantum-secure pre shared key exchange has either failed, or succeeded.
+/// When both the `rawPresharedKey` and the `rawEphemeralKey` are raw pointers to 32 bytes data arrays,
+/// the quantum-secure key exchange is considered successful. In any other case, the exchange is considered failed.
+///
+/// - Parameters:
+/// - rawPacketTunnel: 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_post_quantum_key_ready")
func receivePostQuantumKey(
rawPacketTunnel: UnsafeMutableRawPointer?,
diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift
index 8e813f47df..16f7a24931 100644
--- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift
+++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift
@@ -11,23 +11,25 @@ import NetworkExtension
import TalpidTunnelConfigClientProxy
import WireGuardKitTypes
+/**
+ Attempt to start the asynchronous process of key negotiation. Returns true if successfully started, false if failed.
+ */
public class PostQuantumKeyNegotiator {
public init() {}
var cancelToken: PostQuantumCancelToken?
- public func negotiateKey(
+ public func startNegotiation(
gatewayIP: IPv4Address,
devicePublicKey: PublicKey,
presharedKey: PrivateKey,
packetTunnel: NEPacketTunnelProvider,
tcpConnection: NWTCPConnection
- ) {
+ ) -> Bool {
let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque()
let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque()
var cancelToken = PostQuantumCancelToken()
- // TODO: Any non 0 return is considered a failure, and should be handled gracefully
let result = negotiate_post_quantum_key(
devicePublicKey.rawValue.map { $0 },
presharedKey.rawValue.map { $0 },
@@ -36,10 +38,10 @@ public class PostQuantumKeyNegotiator {
&cancelToken
)
guard result == 0 else {
- // Handle failure here
- return
+ return false
}
self.cancelToken = cancelToken
+ return true
}
public func cancelKeyNegotiation() {
diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h b/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h
index 7b9315c12f..04180db289 100644
--- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h
+++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h
@@ -13,23 +13,44 @@ typedef struct PostQuantumCancelToken {
} PostQuantumCancelToken;
/**
- * * # Safety * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`
+ * Called by the Swift side to signal that the quantum-secure key exchange should be cancelled.
+ *
+ * # Safety
+ * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`.
*/
void cancel_post_quantum_key_exchange(const struct PostQuantumCancelToken *sender);
/**
- * * # Safety * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`.
+ * Called by the Swift side to signal that the Rust `PostQuantumCancelToken` can be safely dropped from memory.
+ *
+ * # Safety
+ * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`.
*/
void drop_post_quantum_key_exchange_token(const struct PostQuantumCancelToken *sender);
/**
- * * # 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.
+ * Called by Swift whenever data has been written to the in-tunnel TCP connection when exchanging
+ * quantum-resistant pre shared keys.
+ *
+ * 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);
+void handle_sent(uintptr_t bytes_sent, const void *sender);
/**
- * * # 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.
+ * Called by Swift whenever data has been read from the in-tunnel TCP connection when exchanging
+ * quantum-resistant pre shared keys.
+ *
+ * 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,
@@ -39,7 +60,11 @@ void handle_recv(const uint8_t *data,
* Entry point for exchanging post quantum keys on iOS.
* The TCP connection must be created to go through the tunnel.
* # Safety
- * This function is safe to call
+ * `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 negotiate_post_quantum_key(const uint8_t *public_key,
const uint8_t *ephemeral_key,
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index c5b6fb9042..ad0f0ed740 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -51,6 +51,7 @@
44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */; };
44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; };
44DD7D2D2B74E44A0005F67F /* QuantumResistanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */; };
+ 44DF8AC42BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DF8AC32BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift */; };
5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */; };
5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4B12940A48700C23744 /* TunnelStore.swift */; };
5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; };
@@ -1429,6 +1430,7 @@
44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = "<group>"; };
44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = "<group>"; };
44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuantumResistanceSettings.swift; sourceTree = "<group>"; };
+ 44DF8AC32BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+PostQuantum.swift"; sourceTree = "<group>"; };
5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = "<group>"; };
5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteProtocol.swift; sourceTree = "<group>"; };
5802EBC82A8E45BA00E5CE4C /* ApplicationRouterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRouterDelegate.swift; sourceTree = "<group>"; };
@@ -3122,6 +3124,7 @@
58342C032AAB61FB003BA12D /* State+Extensions.swift */,
586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */,
58DDA18E2ABC32380039C360 /* Timings.swift */,
+ 44DF8AC32BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift */,
);
path = Actor;
sourceTree = "<group>";
@@ -5483,6 +5486,7 @@
58FE25DA2AA72A8F003D1918 /* PacketTunnelActor.swift in Sources */,
587A5E522ADD7569003A70F1 /* ObservedState+Extensions.swift in Sources */,
58FE25E62AA738E8003D1918 /* TunnelAdapterProtocol.swift in Sources */,
+ 44DF8AC42BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift in Sources */,
583832252AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift in Sources */,
58C7A4552A863FB90060C66F /* TunnelMonitor.swift in Sources */,
58C7AF182ABD84AB007EDD7A /* ProxyURLResponse.swift in Sources */,
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index 897f0074ac..f162810199 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -670,15 +670,11 @@ final class TunnelManager: StorePaymentObserver {
refreshDeviceState()
}
switch newTunnelStatus.state {
- case .connecting, .reconnecting:
+ case .connecting, .reconnecting, .negotiatingPostQuantumKey:
// Start polling tunnel status to keep the relay information up to date
// while the tunnel process is trying to connect.
startPollingTunnelStatus(interval: establishingTunnelStatusPollInterval)
- case .negotiatingPostQuantumKey:
- // No need to poll the tunnel while negotiating post quantum keys, assume the connection will work
- break
-
case .connected, .waitingForConnectivity(.noConnection):
// Start polling tunnel status to keep connectivity status up to date.
startPollingTunnelStatus(interval: establishedTunnelStatusPollInterval)
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index ab64040f0a..6b27aa59f8 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -82,7 +82,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
protocolObfuscator: ProtocolObfuscator<UDPOverTCPObfuscator>()
)
- postQuantumActor = PostQuantumKeyExchangeActor(packetTunnel: self)
+ postQuantumActor = PostQuantumKeyExchangeActor(
+ packetTunnel: self,
+ onFailure: self.keyExchangeFailed
+ )
let urlRequestProxy = URLRequestProxy(dispatchQueue: internalQueue, transportProvider: transportProvider)
@@ -249,6 +252,7 @@ extension PacketTunnelProvider {
lastConnectionAttempt = connectionAttempt
case let .negotiatingPostQuantumKey(_, privateKey):
+ postQuantumActor.endCurrentNegotiation()
postQuantumActor.startNegotiation(with: privateKey)
case .initial, .connected, .disconnecting, .disconnected, .error:
@@ -258,16 +262,6 @@ extension PacketTunnelProvider {
}
}
- func createTCPConnectionForPQPSK(_ gatewayAddress: String) -> NWTCPConnection {
- let gatewayEndpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337")
- return createTCPConnectionThroughTunnel(
- to: gatewayEndpoint,
- enableTLS: false,
- tlsParameters: nil,
- delegate: nil
- )
- }
-
private func stopObservingActorState() {
stateObserverTask?.cancel()
stateObserverTask = nil
@@ -306,11 +300,13 @@ extension PacketTunnelProvider {
extension PacketTunnelProvider: PostQuantumKeyReceiving {
func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey)
- postQuantumActor.acknowledgeNegotiationConcluded()
+ postQuantumActor.endCurrentNegotiation()
}
func keyExchangeFailed() {
- postQuantumActor.acknowledgeNegotiationConcluded()
- actor.reconnect(to: .current)
+ postQuantumActor.endCurrentNegotiation()
+ // Do not try reconnecting to the `.current` relay, else the actor's `State` equality check will fail
+ // and it will not try to reconnect
+ actor.reconnect(to: .random)
}
}
diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift
index e9189d947c..33291d8ea5 100644
--- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift
+++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift
@@ -26,9 +26,14 @@ class PostQuantumKeyExchangeActor {
unowned let packetTunnel: PacketTunnelProvider
private var negotiation: Negotiation?
+ private var timer: DispatchSourceTimer?
- init(packetTunnel: PacketTunnelProvider) {
+ // Callback in the event of the negotiation failing on startup
+ var onFailure: () -> Void
+
+ init(packetTunnel: PacketTunnelProvider, onFailure: @escaping (() -> Void)) {
self.packetTunnel = packetTunnel
+ self.onFailure = onFailure
}
private func createTCPConnection(_ gatewayEndpoint: NWHostEndpoint) -> NWTCPConnection {
@@ -45,24 +50,33 @@ class PostQuantumKeyExchangeActor {
let gatewayAddress = "10.64.0.1"
let IPv4Gateway = IPv4Address(gatewayAddress)!
- let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337")
+ let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "\(CONFIG_SERVICE_PORT)")
let inTunnelTCPConnection = createTCPConnection(endpoint)
- let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device
+ // This will become the new private key of the device
+ let ephemeralSharedKey = PrivateKey()
+
+ // If the connection never becomes viable, force a reconnection after 10 seconds
+ scheduleInTunnelConnectionTimeout(startTime: .now() + 10)
let tcpConnectionObserver = inTunnelTCPConnection.observe(\.isViable, options: [
.initial,
.new,
]) { [weak self] observedConnection, _ in
guard let self, observedConnection.isViable else { return }
- negotiator.negotiateKey(
+ self.negotiation?.tcpConnectionObserver.invalidate()
+ self.timer?.cancel()
+
+ if !negotiator.startNegotiation(
gatewayIP: IPv4Gateway,
devicePublicKey: privateKey.publicKey,
presharedKey: ephemeralSharedKey,
packetTunnel: packetTunnel,
tcpConnection: inTunnelTCPConnection
- )
- self.negotiation?.tcpConnectionObserver.invalidate()
+ ) {
+ self.negotiation = nil
+ self.onFailure()
+ }
}
negotiation = Negotiation(
negotiator: negotiator,
@@ -71,8 +85,23 @@ class PostQuantumKeyExchangeActor {
)
}
- func acknowledgeNegotiationConcluded() {
+ func endCurrentNegotiation() {
negotiation?.cancel()
negotiation = nil
}
+
+ 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/PacketTunnelCore/Actor/ConfigurationBuilder.swift b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
index 79833d16c2..226b8b05fe 100644
--- a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
+++ b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
@@ -27,7 +27,6 @@ public struct ConfigurationBuilder {
var dns: SelectedDNSServers?
var endpoint: MullvadEndpoint?
var allowedIPs: [IPAddressRange]
- // or should this go in MullvadEndpoint?
var preSharedKey: PreSharedKey?
public init(
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
new file mode 100644
index 0000000000..53066ce437
--- /dev/null
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
@@ -0,0 +1,84 @@
+//
+// PacketTunnelActor+PostQuantum.swift
+// PacketTunnelCore
+//
+// Created by Andrew Bulhak on 2024-05-13.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import WireGuardKitTypes
+
+extension PacketTunnelActor {
+ /**
+ Attempt to start the process of negotiating a post-quantum secure key, setting up an initial
+ connection restricted to the negotiation host and entering the negotiating state.
+ */
+ internal func tryStartPostQuantumNegotiation(
+ withSettings settings: Settings,
+ nextRelay: NextRelay,
+ reason: ReconnectReason
+ ) async throws {
+ if let connectionState = try makeConnectionState(nextRelay: nextRelay, settings: settings, reason: reason) {
+ let selectedEndpoint = connectionState.selectedRelay.endpoint
+ let activeKey = activeKey(from: connectionState, in: settings)
+
+ let configurationBuilder = ConfigurationBuilder(
+ privateKey: activeKey,
+ interfaceAddresses: settings.interfaceAddresses,
+ dns: settings.dnsServers,
+ endpoint: selectedEndpoint,
+ allowedIPs: [
+ IPAddressRange(from: "10.64.0.1/32")!,
+ ]
+ )
+
+ try await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration())
+ state = .negotiatingPostQuantumKey(connectionState, activeKey)
+ }
+ }
+
+ /**
+ Called on receipt of the new PQ-negotiated key, to reconnect to the relay, in PQ-secure mode.
+ */
+ internal func postQuantumConnect(with key: PreSharedKey, privateKey: PrivateKey) async {
+ guard
+ // It is important to select the same relay that was saved in the connection state as the key negotiation happened with this specific relay.
+ let selectedRelay = state.connectionData?.selectedRelay,
+ let settings: Settings = try? settingsReader.read(),
+ let connectionState = try? obfuscateConnection(
+ nextRelay: .preSelected(selectedRelay),
+ settings: settings,
+ reason: .userInitiated
+ )
+ else {
+ logger.error("Could not create connection state in PostQuantumConnect")
+
+ let nextRelay: NextRelay = (state.connectionData?.selectedRelay).map { .preSelected($0) } ?? .current
+ commandChannel.send(.reconnect(nextRelay))
+ return
+ }
+
+ let configurationBuilder = ConfigurationBuilder(
+ privateKey: privateKey,
+ interfaceAddresses: settings.interfaceAddresses,
+ dns: settings.dnsServers,
+ endpoint: connectionState.connectedEndpoint,
+ allowedIPs: [
+ IPAddressRange(from: "0.0.0.0/0")!,
+ IPAddressRange(from: "::/0")!,
+ ],
+ preSharedKey: key
+ )
+ stopDefaultPathObserver()
+
+ state = .connecting(connectionState)
+
+ try? await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration())
+ // Resume tunnel monitoring and use IPv4 gateway as a probe address.
+ tunnelMonitor.start(probeAddress: connectionState.selectedRelay.endpoint.ipv4Gateway)
+ // Restart default path observer and notify the observer with the current path that might have changed while
+ // path observer was paused.
+ startDefaultPathObserver(notifyObserverWithCurrentPath: false)
+ }
+}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index 552624d504..153c1c0430 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -224,48 +224,28 @@ extension PacketTunnelActor {
}
}
- private func postQuantumConnect(with key: PreSharedKey, privateKey: PrivateKey) async {
- guard
- // It is important to select the same relay that was saved in the connection state as the key negotiation happened with this specific relay.
- let selectedRelay = state.connectionData?.selectedRelay,
- let settings: Settings = try? settingsReader.read(),
- let connectionState = try? obfuscateConnection(
- nextRelay: .preSelected(selectedRelay),
- settings: settings,
- reason: .userInitiated
- )
- else { return }
-
- let configurationBuilder = ConfigurationBuilder(
- privateKey: privateKey,
- interfaceAddresses: settings.interfaceAddresses,
- dns: settings.dnsServers,
- endpoint: connectionState.connectedEndpoint,
- allowedIPs: [
- IPAddressRange(from: "0.0.0.0/0")!,
- IPAddressRange(from: "::/0")!,
- ],
- preSharedKey: key
- )
- stopDefaultPathObserver()
+ /**
+ Entry point for attempting to start the tunnel by performing the following steps:
- state = .connecting(connectionState)
+ - Read settings
+ - Start either a direct connection or the post-quantum key negotiation process, depending on settings.
+ */
+ private func tryStart(
+ nextRelay: NextRelay,
+ reason: ReconnectReason = .userInitiated
+ ) async throws {
+ let settings: Settings = try settingsReader.read()
- defer {
- // Restart default path observer and notify the observer with the current path that might have changed while
- // path observer was paused.
- startDefaultPathObserver(notifyObserverWithCurrentPath: false)
+ if settings.quantumResistance.isEnabled {
+ try await tryStartPostQuantumNegotiation(withSettings: settings, nextRelay: nextRelay, reason: reason)
+ } else {
+ try await tryStartConnection(withSettings: settings, nextRelay: nextRelay, reason: reason)
}
-
- try? await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration())
- // Resume tunnel monitoring and use IPv4 gateway as a probe address.
- tunnelMonitor.start(probeAddress: connectionState.selectedRelay.endpoint.ipv4Gateway)
}
/**
- Attempt to start the tunnel by performing the following steps:
+ Attempt to start a direct (non-quantum) connection to the tunnel by performing the following steps:
- - Read settings.
- Determine target state, it can either be `.connecting` or `.reconnecting`. (See `TargetStateForReconnect`)
- Bail if target state cannot be determined. That means that the actor is past the point when it could logically connect or reconnect, i.e it can already be in
`.disconnecting` state.
@@ -277,33 +257,11 @@ extension PacketTunnelActor {
- nextRelay: which relay should be selected next.
- reason: reason for reconnect
*/
- private func tryStart(
+ private func tryStartConnection(
+ withSettings settings: Settings,
nextRelay: NextRelay,
- reason: ReconnectReason = .userInitiated
+ reason: ReconnectReason
) async throws {
- let settings: Settings = try settingsReader.read()
-
- if settings.quantumResistance.isEnabled {
- if let connectionState = try makeConnectionState(nextRelay: nextRelay, settings: settings, reason: reason) {
- let selectedEndpoint = connectionState.selectedRelay.endpoint
- let activeKey = activeKey(from: connectionState, in: settings)
-
- let configurationBuilder = ConfigurationBuilder(
- privateKey: activeKey,
- interfaceAddresses: settings.interfaceAddresses,
- dns: settings.dnsServers,
- endpoint: selectedEndpoint,
- allowedIPs: [
- IPAddressRange(from: "10.64.0.1/32")!,
- ]
- )
-
- try await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration())
- state = .negotiatingPostQuantumKey(connectionState, activeKey)
- }
- return
- }
-
guard let connectionState = try obfuscateConnection(nextRelay: nextRelay, settings: settings, reason: reason),
let targetState = state.targetStateForReconnect else { return }
@@ -356,7 +314,7 @@ extension PacketTunnelActor {
- Returns: New connection state or `nil` if current state is at or past `.disconnecting` phase.
*/
- private func makeConnectionState(
+ internal func makeConnectionState(
nextRelay: NextRelay,
settings: Settings,
reason: ReconnectReason
@@ -382,7 +340,13 @@ extension PacketTunnelActor {
connectionState.incrementAttemptCount()
}
fallthrough
- case let .negotiatingPostQuantumKey(connectionState, _):
+ case var .negotiatingPostQuantumKey(connectionState, _):
+ let selectedRelay = try callRelaySelector(
+ connectionState.selectedRelay,
+ connectionState.connectionAttemptCount
+ )
+ connectionState.selectedRelay = selectedRelay
+ connectionState.relayConstraints = settings.relayConstraints
return connectionState
case var .connected(connectionState):
let selectedRelay = try callRelaySelector(
@@ -416,7 +380,7 @@ extension PacketTunnelActor {
)
}
- private func activeKey(from state: State.ConnectionData, in settings: Settings) -> PrivateKey {
+ internal func activeKey(from state: State.ConnectionData, in settings: Settings) -> PrivateKey {
switch state.keyPolicy {
case .useCurrent:
settings.privateKey
@@ -425,7 +389,7 @@ extension PacketTunnelActor {
}
}
- private func obfuscateConnection(
+ internal func obfuscateConnection(
nextRelay: NextRelay,
settings: Settings,
reason: ReconnectReason
@@ -451,7 +415,7 @@ extension PacketTunnelActor {
connectedEndpoint: obfuscatedEndpoint,
transportLayer: transportLayer,
remotePort: protocolObfuscator.remotePort,
- isPostQuantum: connectionState.isPostQuantum
+ isPostQuantum: settings.quantumResistance.isEnabled
)
}
diff --git a/talpid-routing/src/unix/mod.rs b/talpid-routing/src/unix/mod.rs
index ca3f440935..5f14d88d67 100644
--- a/talpid-routing/src/unix/mod.rs
+++ b/talpid-routing/src/unix/mod.rs
@@ -18,7 +18,7 @@ use futures::stream::Stream;
use std::net::IpAddr;
#[allow(clippy::module_inception)]
-#[cfg(any(target_os = "macos", target_os = "ios"))]
+#[cfg(target_os = "macos")]
#[path = "macos/mod.rs"]
pub mod imp;
diff --git a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs
index ce0faf4ddb..37fdb0d16d 100644
--- a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs
+++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs
@@ -1,5 +1,5 @@
use super::{ios_tcp_connection::*, PostQuantumCancelToken};
-use crate::{request_ephemeral_peer, Error, RelayConfigService};
+use crate::{request_ephemeral_peer_with, Error, RelayConfigService};
use libc::c_void;
use std::{future::Future, io, pin::Pin, ptr, sync::Arc};
use talpid_types::net::wireguard::{PrivateKey, PublicKey};
@@ -10,12 +10,12 @@ 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_ios_runtime(
+pub unsafe fn run_post_quantum_psk_exchange(
pub_key: [u8; 32],
ephemeral_key: [u8; 32],
packet_tunnel: *const c_void,
tcp_connection: *const c_void,
-) -> Result<PostQuantumCancelToken, i32> {
+) -> Result<PostQuantumCancelToken, Error> {
match unsafe { IOSRuntime::new(pub_key, ephemeral_key, packet_tunnel, tcp_connection) } {
Ok(runtime) => {
let token = runtime.cancel_token_tx.clone();
@@ -27,7 +27,7 @@ pub unsafe fn run_ios_runtime(
}
Err(err) => {
log::error!("Failed to create runtime {}", err);
- Err(-1)
+ Err(Error::UnableToCreateRuntime)
}
}
}
@@ -85,7 +85,10 @@ impl IOSRuntime {
});
}
- pub async fn ios_tcp_client(
+ /// 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");
@@ -117,25 +120,23 @@ impl IOSRuntime {
let packet_tunnel_ptr = self.packet_tunnel.packet_tunnel;
runtime.block_on(async move {
- let (async_provider, shutdown_handle) = match Self::ios_tcp_client(self.packet_tunnel).await {
+ let (async_provider, shutdown_handle) = unsafe { match Self::ios_tcp_client(self.packet_tunnel).await {
Ok(result) => result,
Err(error) => {
log::error!("Failed to create iOS TCP client: {error}");
- unsafe {
swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null());
- }
return;
}
- };
+ }};
let ephemeral_pub_key = PrivateKey::from(self.ephemeral_key).public_key();
tokio::select! {
- ephemeral_peer = request_ephemeral_peer(
+ ephemeral_peer = request_ephemeral_peer_with(
+ async_provider,
PublicKey::from(self.pub_key),
ephemeral_pub_key,
true,
false,
- async_provider,
) => {
shutdown_handle.shutdown();
match ephemeral_peer {
diff --git a/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs b/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs
index 7609511b53..86a3114edf 100644
--- a/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs
+++ b/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs
@@ -3,9 +3,9 @@ use std::{
io::{self, Result},
sync::{
atomic::{self, AtomicBool},
- Arc,
+ Arc, Mutex, Weak,
},
- task::Poll,
+ task::{Poll, Waker},
};
use tokio::{
io::{AsyncRead, AsyncWrite},
@@ -45,18 +45,20 @@ extern "C" {
unsafe impl Send for IosTcpProvider {}
pub struct IosTcpProvider {
- write_tx: mpsc::UnboundedSender<usize>,
+ write_tx: Arc<mpsc::UnboundedSender<usize>>,
write_rx: mpsc::UnboundedReceiver<usize>,
- read_tx: mpsc::UnboundedSender<Box<[u8]>>,
+ read_tx: Arc<mpsc::UnboundedSender<Box<[u8]>>>,
read_rx: mpsc::UnboundedReceiver<Box<[u8]>>,
tcp_connection: *const c_void,
read_in_progress: bool,
write_in_progress: bool,
shutdown: Arc<AtomicBool>,
+ waker: Arc<Mutex<Option<Waker>>>,
}
pub struct IosTcpShutdownHandle {
shutdown: Arc<AtomicBool>,
+ waker: Arc<Mutex<Option<Waker>>>,
}
impl IosTcpProvider {
@@ -68,30 +70,41 @@ impl IosTcpProvider {
let (tx, rx) = mpsc::unbounded_channel();
let (recv_tx, recv_rx) = mpsc::unbounded_channel();
let shutdown = Arc::new(AtomicBool::new(false));
+ let waker = Arc::new(Mutex::new(None));
(
Self {
- write_tx: tx,
+ write_tx: Arc::new(tx),
write_rx: rx,
- read_tx: recv_tx,
+ read_tx: Arc::new(recv_tx),
read_rx: recv_rx,
tcp_connection,
read_in_progress: false,
write_in_progress: false,
shutdown: shutdown.clone(),
+ waker: waker.clone(),
},
- IosTcpShutdownHandle { shutdown },
+ IosTcpShutdownHandle { shutdown, waker },
)
}
fn is_shutdown(&self) -> bool {
self.shutdown.load(atomic::Ordering::SeqCst)
}
+
+ fn maybe_set_waker(&self, new_waker: Waker) {
+ if let Ok(mut waker) = self.waker.lock() {
+ *waker = Some(new_waker);
+ }
+ }
}
impl IosTcpShutdownHandle {
pub fn shutdown(&self) {
self.shutdown.store(true, atomic::Ordering::SeqCst);
+ if let Some(waker) = self.waker.lock().ok().and_then(|mut waker| waker.take()) {
+ waker.wake();
+ }
}
}
@@ -101,8 +114,7 @@ impl AsyncWrite for IosTcpProvider {
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize>> {
- let raw_sender = Box::into_raw(Box::new(self.write_tx.clone()));
-
+ self.maybe_set_waker(cx.waker().clone());
match self.write_rx.poll_recv(cx) {
std::task::Poll::Ready(Some(bytes_sent)) => {
self.write_in_progress = false;
@@ -116,17 +128,17 @@ impl AsyncWrite for IosTcpProvider {
if self.is_shutdown() {
return Poll::Ready(Err(connection_closed_err()));
}
- if self.write_in_progress {
- return std::task::Poll::Pending;
- }
- self.write_in_progress = true;
- unsafe {
- swift_nw_tcp_connection_send(
- self.tcp_connection,
- buf.as_ptr() as _,
- buf.len(),
- raw_sender as _,
- );
+ if !self.write_in_progress {
+ let raw_sender = Weak::into_raw(Arc::downgrade(&self.write_tx));
+ unsafe {
+ swift_nw_tcp_connection_send(
+ self.tcp_connection,
+ buf.as_ptr() as _,
+ buf.len(),
+ raw_sender as _,
+ );
+ }
+ self.write_in_progress = true;
}
std::task::Poll::Pending
}
@@ -153,7 +165,6 @@ impl AsyncRead for IosTcpProvider {
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
- let raw_sender = Box::into_raw(Box::new(self.read_tx.clone()));
if self.is_shutdown() {
return Poll::Ready(Err(connection_closed_err()));
}
@@ -169,15 +180,14 @@ impl AsyncRead for IosTcpProvider {
Poll::Ready(Err(connection_closed_err()))
}
std::task::Poll::Pending => {
- if self.read_in_progress {
- return std::task::Poll::Pending;
- }
- self.read_in_progress = true;
- unsafe {
- swift_nw_tcp_connection_read(self.tcp_connection, raw_sender as _);
+ if !self.read_in_progress {
+ let raw_sender = Weak::into_raw(Arc::downgrade(&self.read_tx));
+ unsafe {
+ swift_nw_tcp_connection_read(self.tcp_connection, raw_sender as _);
+ }
+ self.read_in_progress = true;
}
-
- std::task::Poll::Pending
+ Poll::Pending
}
}
}
diff --git a/talpid-tunnel-config-client/src/ios_ffi/mod.rs b/talpid-tunnel-config-client/src/ios_ffi/mod.rs
index 9941bf4340..94dcf95b6f 100644
--- a/talpid-tunnel-config-client/src/ios_ffi/mod.rs
+++ b/talpid-tunnel-config-client/src/ios_ffi/mod.rs
@@ -1,9 +1,9 @@
pub mod ios_runtime;
pub mod ios_tcp_connection;
-use crate::ios_ffi::ios_runtime::run_ios_runtime;
+use crate::ios_ffi::ios_runtime::run_post_quantum_psk_exchange;
use libc::c_void;
-use std::sync::Arc;
+use std::sync::{Arc, Weak};
use tokio::sync::mpsc;
use std::sync::Once;
@@ -16,12 +16,14 @@ pub struct PostQuantumCancelToken {
}
impl PostQuantumCancelToken {
- /// #Safety
+ /// # 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 send_tx: Arc<mpsc::UnboundedSender<()>> = unsafe { Arc::from_raw(self.context as _) };
let _ = send_tx.send(());
+ // Call std::mem::forget here to avoid dropping the channel.
std::mem::forget(send_tx);
}
}
@@ -33,20 +35,19 @@ impl Drop for PostQuantumCancelToken {
}
unsafe impl Send for PostQuantumCancelToken {}
+/// Called by the Swift side to signal that the quantum-secure key exchange should be cancelled.
+///
+/// # Safety
+/// `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`.
#[no_mangle]
-/**
- * # Safety
- * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`
- */
pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const PostQuantumCancelToken) {
let sender = unsafe { &*sender };
sender.cancel();
}
-
-/**
- * # Safety
- * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`.
- */
+/// Called by the Swift side to signal that the Rust `PostQuantumCancelToken` can be safely dropped from memory.
+///
+/// # Safety
+/// `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`.
#[no_mangle]
pub unsafe extern "C" fn drop_post_quantum_key_exchange_token(
sender: *const PostQuantumCancelToken,
@@ -54,38 +55,56 @@ pub unsafe extern "C" fn drop_post_quantum_key_exchange_token(
let _sender = unsafe { std::ptr::read(sender) };
}
-/**
- * # 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.
- */
+/// Called by Swift whenever data has been written to the in-tunnel TCP connection when exchanging
+/// quantum-resistant pre shared keys.
+///
+/// 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 send_tx: Box<mpsc::UnboundedSender<usize>> = unsafe { Box::from_raw(sender as _) };
- _ = send_tx.send(bytes_sent);
+ 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);
+ }
}
-/**
- * # 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.
- */
+/// Called by Swift whenever data has been read from the in-tunnel TCP connection when exchanging
+/// quantum-resistant pre shared keys.
+///
+/// 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, data_len: usize, sender: *const c_void) {
- let read_tx: Box<mpsc::UnboundedSender<Box<[u8]>>> = unsafe { Box::from_raw(sender as _) };
+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);
}
- _ = read_tx.send(bytes.into_boxed_slice());
+ if let Some(read_tx) = weak_tx.upgrade() {
+ _ = read_tx.send(bytes.into_boxed_slice());
+ }
}
/// Entry point for exchanging post quantum keys on iOS.
/// The TCP connection must be created to go through the tunnel.
/// # Safety
-/// This function is safe to call
+/// `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.
#[no_mangle]
pub unsafe extern "C" fn negotiate_post_quantum_key(
public_key: *const u8,
@@ -103,11 +122,13 @@ pub unsafe extern "C" fn negotiate_post_quantum_key(
let pub_key_copy: [u8; 32] = unsafe { std::ptr::read(public_key as *const [u8; 32]) };
let eph_key_copy: [u8; 32] = unsafe { std::ptr::read(ephemeral_key as *const [u8; 32]) };
- match unsafe { run_ios_runtime(pub_key_copy, eph_key_copy, packet_tunnel, tcp_connection) } {
+ match unsafe {
+ run_post_quantum_psk_exchange(pub_key_copy, eph_key_copy, packet_tunnel, tcp_connection)
+ } {
Ok(token) => {
unsafe { std::ptr::write(cancel_token, token) };
0
}
- Err(err) => err,
+ Err(_) => -1,
}
}
diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs
index 04a81d924b..18ad9a49f8 100644
--- a/talpid-tunnel-config-client/src/lib.rs
+++ b/talpid-tunnel-config-client/src/lib.rs
@@ -24,8 +24,6 @@ mod proto {
#[cfg(target_os = "ios")]
pub mod ios_ffi;
-#[cfg(target_os = "ios")]
-use proto::ephemeral_peer_client::EphemeralPeerClient;
#[cfg(not(target_os = "ios"))]
use libc::setsockopt;
@@ -61,6 +59,8 @@ pub enum Error {
FailedDecapsulateKyber(kyber::KyberError),
#[cfg(target_os = "ios")]
TcpConnectionExpired,
+ #[cfg(target_os = "ios")]
+ UnableToCreateRuntime,
}
impl std::fmt::Display for Error {
@@ -84,6 +84,8 @@ impl std::fmt::Display for Error {
FailedDecapsulateKyber(_) => "Failed to decapsulate Kyber1024 ciphertext".fmt(f),
#[cfg(target_os = "ios")]
TcpConnectionExpired => "TCP connection is already shut down".fmt(f),
+ #[cfg(target_os = "ios")]
+ UnableToCreateRuntime => "Unable to create iOS PQ PSK runtime".fmt(f),
}
}
}
@@ -119,24 +121,18 @@ pub struct EphemeralPeer {
pub psk: Option<PresharedKey>,
}
-/// Negotiate a short-lived peer with a PQ-safe PSK or with DAITA enabled.
-pub async fn request_ephemeral_peer(
- #[cfg(not(target_os = "ios"))] service_address: IpAddr,
+pub async fn request_ephemeral_peer_with(
+ mut client: RelayConfigService,
parent_pubkey: PublicKey,
ephemeral_pubkey: PublicKey,
enable_post_quantum: bool,
enable_daita: bool,
- #[cfg(target_os = "ios")] mut client: EphemeralPeerClient<Channel>,
) -> Result<EphemeralPeer, Error> {
let (pq_request, kem_secrets) = post_quantum_secrets(enable_post_quantum).await;
-
let daita = Some(proto::DaitaRequestV1 {
activate_daita: enable_daita,
});
- #[cfg(not(target_os = "ios"))]
- let mut client = new_client(service_address).await?;
-
let response = client
.register_peer_v1(proto::EphemeralPeerRequestV1 {
wg_parent_pubkey: parent_pubkey.as_bytes().to_vec(),
@@ -194,13 +190,34 @@ pub async fn request_ephemeral_peer(
Ok(EphemeralPeer { psk })
}
+/// Negotiate a short-lived peer with a PQ-safe PSK or with DAITA enabled.
+#[cfg(not(target_os = "ios"))]
+pub async fn request_ephemeral_peer(
+ service_address: IpAddr,
+ parent_pubkey: PublicKey,
+ ephemeral_pubkey: PublicKey,
+ enable_post_quantum: bool,
+ enable_daita: bool,
+) -> Result<EphemeralPeer, Error> {
+ let client = new_client(service_address).await?;
+
+ request_ephemeral_peer_with(
+ client,
+ parent_pubkey,
+ ephemeral_pubkey,
+ enable_post_quantum,
+ enable_daita,
+ )
+ .await
+}
+
async fn post_quantum_secrets(
enable_post_quantum: bool,
) -> (
Option<PostQuantumRequestV1>,
Option<(classic_mceliece_rust::SecretKey<'static>, [u8; 3168])>,
) {
- let (pq_request, kem_secrets) = if enable_post_quantum {
+ if enable_post_quantum {
let (cme_kem_pubkey, cme_kem_secret) = classic_mceliece::generate_keys().await;
let kyber_keypair = kyber::keypair(&mut rand::thread_rng());
@@ -221,8 +238,7 @@ async fn post_quantum_secrets(
)
} else {
(None, None)
- };
- (pq_request, kem_secrets)
+ }
}
/// Performs `dst = dst ^ src`.