Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IAN TCP connections #7289

Merged
merged 4 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading