Skip to content

Commit

Permalink
Merge branch 'ian-tcp'
Browse files Browse the repository at this point in the history
  • Loading branch information
pinkisemils committed Dec 19, 2024
2 parents 1e100f9 + ee5c10b commit fed889e
Show file tree
Hide file tree
Showing 35 changed files with 897 additions and 917 deletions.
158 changes: 114 additions & 44 deletions Cargo.lock

Large diffs are not rendered by default.

91 changes: 30 additions & 61 deletions ios/MullvadRustRuntime/EphemeralPeerExchangeActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand All @@ -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.
Expand All @@ -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()

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()
}
let peerParameters = EphemeralPeerParameters(
peer_exchange_timeout: UInt64(tcpConnectionTimeout.timeInterval),
enable_post_quantum: enablePostQuantum,
enable_daita: enableDaita,
funcs: mapWgFunctions(functions: packetTunnel.wgFunctions())
)

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()
Expand All @@ -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
}
}
40 changes: 16 additions & 24 deletions ios/MullvadRustRuntime/EphemeralPeerNegotiator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -33,49 +29,45 @@ 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
return true
}

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)
}
}

Expand Down
52 changes: 52 additions & 0 deletions ios/MullvadRustRuntime/EphemeralPeerReceiver.swift
Original file line number Diff line number Diff line change
@@ -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
}
118 changes: 0 additions & 118 deletions ios/MullvadRustRuntime/PacketTunnelProvider+TCPConnection.swift

This file was deleted.

Loading

0 comments on commit fed889e

Please sign in to comment.