summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock158
-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.swift28
-rw-r--r--ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift49
-rw-r--r--ios/MullvadTypes/Protocols/EphemeralPeerReceiving.swift4
-rw-r--r--ios/MullvadTypes/Protocols/TunnelProvider.swift27
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj19
-rw-r--r--ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved2
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift22
-rw-r--r--ios/PacketTunnel/PostQuantum/EphemeralPeerExchangingPipeline.swift16
-rw-r--r--ios/PacketTunnel/PostQuantum/MultiHopEphemeralPeerExchanger.swift32
-rw-r--r--ios/PacketTunnel/PostQuantum/SingleHopEphemeralPeerExchanger.swift16
-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/PacketTunnelCore/Actor/Protocols/EphemeralPeerExchangingProtocol.swift6
-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/Cargo.toml2
-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
35 files changed, 895 insertions, 915 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3cae9294d5..ef4e8d2746 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -191,7 +191,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -202,7 +202,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -413,7 +413,25 @@ dependencies = [
"serde_json",
"syn 1.0.109",
"tempfile",
- "toml",
+ "toml 0.5.11",
+]
+
+[[package]]
+name = "cbindgen"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
+dependencies = [
+ "heck 0.4.1",
+ "indexmap 2.2.6",
+ "log",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "syn 2.0.65",
+ "tempfile",
+ "toml 0.8.19",
]
[[package]]
@@ -537,7 +555,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -710,7 +728,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -734,7 +752,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -745,7 +763,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -826,7 +844,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -836,7 +854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
dependencies = [
"derive_builder_core",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -879,7 +897,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -948,7 +966,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -968,7 +986,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -1177,7 +1195,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -1740,7 +1758,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -1844,7 +1862,7 @@ version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -2308,7 +2326,7 @@ name = "mullvad-api"
version = "0.0.0"
dependencies = [
"async-trait",
- "cbindgen",
+ "cbindgen 0.24.5",
"chrono",
"futures",
"http 1.1.0",
@@ -2441,7 +2459,7 @@ dependencies = [
name = "mullvad-ios"
version = "0.0.0"
dependencies = [
- "cbindgen",
+ "cbindgen 0.27.0",
"hyper-util",
"libc",
"log",
@@ -2503,7 +2521,7 @@ dependencies = [
name = "mullvad-nsis"
version = "0.0.0"
dependencies = [
- "cbindgen",
+ "cbindgen 0.24.5",
"mullvad-paths",
"talpid-platform-metadata",
]
@@ -2635,7 +2653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6813fde79b646e47e7ad75f480aa80ef76a5d9599e2717407961531169ee38b"
dependencies = [
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
"syn-mid",
]
@@ -3121,7 +3139,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -3212,7 +3230,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -3261,7 +3279,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -3327,7 +3345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550"
dependencies = [
"proc-macro2",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -3405,7 +3423,7 @@ dependencies = [
"prost 0.12.4",
"prost-types 0.12.4",
"regex",
- "syn 2.0.60",
+ "syn 2.0.65",
"tempfile",
]
@@ -3419,7 +3437,7 @@ dependencies = [
"itertools 0.12.1",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -3432,7 +3450,7 @@ dependencies = [
"itertools 0.12.1",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -3884,7 +3902,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -3900,6 +3918,15 @@ dependencies = [
]
[[package]]
+name = "serde_spanned"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4203,9 +4230,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.60"
+version = "2.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
+checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106"
dependencies = [
"proc-macro2",
"quote",
@@ -4220,7 +4247,7 @@ checksum = "b5dc35bb08dd1ca3dfb09dce91fd2d13294d6711c88897d9a9d60acf39bce049"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -4243,7 +4270,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -4603,7 +4630,7 @@ checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -4676,7 +4703,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -4778,6 +4805,40 @@ dependencies = [
]
[[package]]
+name = "toml"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
+dependencies = [
+ "indexmap 2.2.6",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
name = "tonic"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4817,7 +4878,7 @@ dependencies = [
"proc-macro2",
"prost-build",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -4885,7 +4946,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -5109,7 +5170,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
"wasm-bindgen-shared",
]
@@ -5131,7 +5192,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -5237,7 +5298,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -5259,7 +5320,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -5550,6 +5611,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
+name = "winnow"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5575,7 +5645,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
dependencies = [
- "toml",
+ "toml 0.5.11",
]
[[package]]
@@ -5648,7 +5718,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
"synstructure",
]
@@ -5669,7 +5739,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
"synstructure",
]
@@ -5690,7 +5760,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
[[package]]
@@ -5712,5 +5782,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.65",
]
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..ab80c31695 100644
--- a/ios/MullvadTypes/Promise.swift
+++ b/ios/MullvadTypes/Promise.swift
@@ -47,3 +47,31 @@ public final class Promise<Success, Failure: Error> {
}
}
}
+
+// This object can be used like an async semaphore with exactly 1 writer. It
+// allows the waiter to wait to `receive()` from another operation
+// asynchronously. It is important not to forget to call `send`, otherwise this
+// operation will block indefinitely.
+public struct OneshotChannel {
+ private var continuation: AsyncStream<Void>.Continuation?
+ private var stream: AsyncStream<Void>
+
+ public init() {
+ var ownedContinuation: AsyncStream<Void>.Continuation?
+ stream = AsyncStream { continuation in
+ ownedContinuation = continuation
+ }
+ self.continuation = ownedContinuation
+ }
+
+ public func send() {
+ continuation?.yield()
+ continuation?.finish()
+ }
+
+ public func receive() async {
+ for await _ in stream {
+ return
+ }
+ }
+}
diff --git a/ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift b/ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift
index e5fc68f68a..7f45669468 100644
--- a/ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift
+++ b/ios/MullvadTypes/Protocols/EphemeralPeerReceiver.swift
@@ -11,42 +11,43 @@ 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
public func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
- guard let receiver = tunnelProvider as? EphemeralPeerReceiving else { return }
- receiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
+ let semaphore = DispatchSemaphore(value: 0)
+ Task {
+ await keyReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
+ semaphore.signal()
+ }
+ semaphore.wait()
}
public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) {
- guard let receiver = tunnelProvider as? EphemeralPeerReceiving else { return }
- receiver.receiveEphemeralPeerPrivateKey(ephemeralPeerPrivateKey)
+ let semaphore = DispatchSemaphore(value: 0)
+ Task {
+ await keyReceiver.receiveEphemeralPeerPrivateKey(ephemeralPeerPrivateKey)
+ semaphore.signal()
+ }
+ semaphore.wait()
}
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/EphemeralPeerReceiving.swift b/ios/MullvadTypes/Protocols/EphemeralPeerReceiving.swift
index d55ec09f1f..cc1c8f0f8a 100644
--- a/ios/MullvadTypes/Protocols/EphemeralPeerReceiving.swift
+++ b/ios/MullvadTypes/Protocols/EphemeralPeerReceiving.swift
@@ -15,11 +15,11 @@ public protocol EphemeralPeerReceiving {
/// - Parameters:
/// - key: The preshared key used by the Ephemeral Peer
/// - ephemeralKey: The private key used by the Ephemeral Peer
- func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey)
+ func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) async
/// Called when successfully requesting an ephemeral peer with Daita enabled, and Post Quantum PSK disabled
/// - Parameter _:_ The private key used by the Ephemeral Peer
- func receiveEphemeralPeerPrivateKey(_: PrivateKey)
+ func receiveEphemeralPeerPrivateKey(_: PrivateKey) async
/// Called when an ephemeral peer could not be successfully negotiated
func 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 fe09f04daf..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;
@@ -9513,7 +9512,7 @@
repositoryURL = "https://github.com/mullvad/wireguard-apple.git";
requirement = {
kind = revision;
- revision = ee90a96a20d42d231b878277d0a3fe4dfb63d93d;
+ revision = 4499596651b4fe8a162e587c75a01615168f29a6;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 0995ea8187..1e8521cd2a 100644
--- a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -14,7 +14,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/mullvad/wireguard-apple.git",
"state" : {
- "revision" : "ee90a96a20d42d231b878277d0a3fe4dfb63d93d"
+ "revision" : "4499596651b4fe8a162e587c75a01615168f29a6"
}
}
],
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index 84ff437b20..75678417ef 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,12 @@ 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
+ )
+ await channel.receive()
}, onFinish: { [unowned self] in
actor.notifyEphemeralPeerNegotiated()
}
@@ -311,7 +316,10 @@ extension PacketTunnelProvider {
lastConnectionAttempt = connectionAttempt
case let .negotiatingEphemeralPeer(observedConnectionState, privateKey):
- ephemeralPeerExchangingPipeline.startNegotiation(observedConnectionState, privateKey: privateKey)
+ await ephemeralPeerExchangingPipeline.startNegotiation(
+ observedConnectionState,
+ privateKey: privateKey
+ )
case .initial, .connected, .disconnecting, .disconnected, .error:
break
}
@@ -368,12 +376,12 @@ extension PacketTunnelProvider {
}
extension PacketTunnelProvider: EphemeralPeerReceiving {
- func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
- ephemeralPeerExchangingPipeline.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
+ func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) async {
+ await ephemeralPeerExchangingPipeline.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
}
- public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) {
- ephemeralPeerExchangingPipeline.receiveEphemeralPeerPrivateKey(ephemeralPeerPrivateKey)
+ public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) async {
+ await ephemeralPeerExchangingPipeline.receiveEphemeralPeerPrivateKey(ephemeralPeerPrivateKey)
}
func ephemeralPeerExchangeFailed() {
diff --git a/ios/PacketTunnel/PostQuantum/EphemeralPeerExchangingPipeline.swift b/ios/PacketTunnel/PostQuantum/EphemeralPeerExchangingPipeline.swift
index 0d0da931f0..1619889003 100644
--- a/ios/PacketTunnel/PostQuantum/EphemeralPeerExchangingPipeline.swift
+++ b/ios/PacketTunnel/PostQuantum/EphemeralPeerExchangingPipeline.swift
@@ -13,14 +13,14 @@ import WireGuardKitTypes
final public class EphemeralPeerExchangingPipeline {
let keyExchanger: EphemeralPeerExchangeActorProtocol
- let onUpdateConfiguration: (EphemeralPeerNegotiationState) -> Void
+ let onUpdateConfiguration: (EphemeralPeerNegotiationState) async -> Void
let onFinish: () -> Void
private var ephemeralPeerExchanger: EphemeralPeerExchangingProtocol!
public init(
_ keyExchanger: EphemeralPeerExchangeActorProtocol,
- onUpdateConfiguration: @escaping (EphemeralPeerNegotiationState) -> Void,
+ onUpdateConfiguration: @escaping (EphemeralPeerNegotiationState) async -> Void,
onFinish: @escaping () -> Void
) {
self.keyExchanger = keyExchanger
@@ -28,7 +28,7 @@ final public class EphemeralPeerExchangingPipeline {
self.onFinish = onFinish
}
- public func startNegotiation(_ connectionState: ObservedConnectionState, privateKey: PrivateKey) {
+ public func startNegotiation(_ connectionState: ObservedConnectionState, privateKey: PrivateKey) async {
keyExchanger.reset()
let entryPeer = connectionState.selectedRelays.entry
let exitPeer = connectionState.selectedRelays.exit
@@ -56,14 +56,14 @@ final public class EphemeralPeerExchangingPipeline {
onFinish: onFinish
)
}
- ephemeralPeerExchanger.start()
+ await ephemeralPeerExchanger.start()
}
- public func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
- ephemeralPeerExchanger.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
+ public func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) async {
+ await ephemeralPeerExchanger.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
}
- public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) {
- ephemeralPeerExchanger.receiveEphemeralPeerPrivateKey(ephemeralPeerPrivateKey)
+ public func receiveEphemeralPeerPrivateKey(_ ephemeralPeerPrivateKey: PrivateKey) async {
+ await ephemeralPeerExchanger.receiveEphemeralPeerPrivateKey(ephemeralPeerPrivateKey)
}
}
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/PostQuantum/SingleHopEphemeralPeerExchanger.swift b/ios/PacketTunnel/PostQuantum/SingleHopEphemeralPeerExchanger.swift
index f959a335ef..3c6ab5631a 100644
--- a/ios/PacketTunnel/PostQuantum/SingleHopEphemeralPeerExchanger.swift
+++ b/ios/PacketTunnel/PostQuantum/SingleHopEphemeralPeerExchanger.swift
@@ -18,7 +18,7 @@ struct SingleHopEphemeralPeerExchanger: 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
@@ -28,7 +28,7 @@ struct SingleHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol {
keyExchanger: EphemeralPeerExchangeActorProtocol,
enablePostQuantum: Bool,
enableDaita: Bool,
- onUpdateConfiguration: @escaping (EphemeralPeerNegotiationState) -> Void,
+ onUpdateConfiguration: @escaping (EphemeralPeerNegotiationState) async -> Void,
onFinish: @escaping () -> Void
) {
self.devicePrivateKey = devicePrivateKey
@@ -40,8 +40,8 @@ struct SingleHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol {
self.onFinish = onFinish
}
- func start() {
- onUpdateConfiguration(.single(EphemeralPeerRelayConfiguration(
+ func start() async {
+ await onUpdateConfiguration(.single(EphemeralPeerRelayConfiguration(
relay: exit,
configuration: EphemeralPeerConfiguration(
privateKey: devicePrivateKey,
@@ -55,8 +55,8 @@ struct SingleHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol {
)
}
- public func receiveEphemeralPeerPrivateKey(_ ephemeralKey: PrivateKey) {
- onUpdateConfiguration(.single(EphemeralPeerRelayConfiguration(
+ public func receiveEphemeralPeerPrivateKey(_ ephemeralKey: PrivateKey) async {
+ await onUpdateConfiguration(.single(EphemeralPeerRelayConfiguration(
relay: exit,
configuration: EphemeralPeerConfiguration(
privateKey: ephemeralKey,
@@ -73,8 +73,8 @@ struct SingleHopEphemeralPeerExchanger: EphemeralPeerExchangingProtocol {
func receivePostQuantumKey(
_ preSharedKey: WireGuardKitTypes.PreSharedKey,
ephemeralKey: WireGuardKitTypes.PrivateKey
- ) {
- onUpdateConfiguration(.single(EphemeralPeerRelayConfiguration(
+ ) async {
+ await onUpdateConfiguration(.single(EphemeralPeerRelayConfiguration(
relay: exit,
configuration: EphemeralPeerConfiguration(
privateKey: ephemeralKey,
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/PacketTunnelCore/Actor/Protocols/EphemeralPeerExchangingProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/EphemeralPeerExchangingProtocol.swift
index bffc4f7a21..a0d596fd9e 100644
--- a/ios/PacketTunnelCore/Actor/Protocols/EphemeralPeerExchangingProtocol.swift
+++ b/ios/PacketTunnelCore/Actor/Protocols/EphemeralPeerExchangingProtocol.swift
@@ -9,7 +9,7 @@
import WireGuardKitTypes
public protocol EphemeralPeerExchangingProtocol {
- func start()
- func receivePostQuantumKey(_ preSharedKey: PreSharedKey, ephemeralKey: PrivateKey)
- func receiveEphemeralPeerPrivateKey(_: PrivateKey)
+ func start() async
+ func receivePostQuantumKey(_ preSharedKey: PreSharedKey, ephemeralKey: PrivateKey) async
+ func receiveEphemeralPeerPrivateKey(_: PrivateKey) async
}
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/Cargo.toml b/mullvad-ios/Cargo.toml
index 294d707e79..91c2e99b6b 100644
--- a/mullvad-ios/Cargo.toml
+++ b/mullvad-ios/Cargo.toml
@@ -31,7 +31,7 @@ shadowsocks-service = { workspace = true, features = [
] }
[target.'cfg(target_os = "macos")'.build-dependencies]
-cbindgen = { version = "0.24.3", default-features = false }
+cbindgen = { version = "0.27.0", default-features = false }
[lib]
crate-type = ["staticlib"]
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),
}