Skip to content

Commit

Permalink
Use IAN TCP connection for ephemeral peer exchange
Browse files Browse the repository at this point in the history
  • Loading branch information
pinkisemils committed Dec 17, 2024
1 parent 8a69fbc commit e3acff5
Show file tree
Hide file tree
Showing 26 changed files with 665 additions and 779 deletions.
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: mapWgFuncs(funcs: packetTunnel.wgFuncs())
)

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 mapWgFuncs(funcs: WgFuncPointers) -> WgTcpConnectionFuncs {
var mappedFuncs = WgTcpConnectionFuncs()

mappedFuncs.close_fn = funcs.close
mappedFuncs.open_fn = funcs.open
mappedFuncs.send_fn = funcs.send
mappedFuncs.recv_fn = funcs.receive

return mappedFuncs
}

/// 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
66 changes: 0 additions & 66 deletions ios/MullvadRustRuntime/PacketTunnelProvider+TCPConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,72 +12,6 @@ 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.
Expand Down
Loading

0 comments on commit e3acff5

Please sign in to comment.