Skip to content

Commit

Permalink
Tunnel-based Pinger integrated into tunnel monitor; currently dependi…
Browse files Browse the repository at this point in the history
…ng on a commit of a WIP branch.
  • Loading branch information
acb-mv committed Aug 23, 2024
1 parent 0fc7976 commit 9649a2a
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 34 deletions.
6 changes: 3 additions & 3 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
0697D6E728F01513007A9E99 /* TransportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0697D6E628F01513007A9E99 /* TransportMonitor.swift */; };
06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
449275422C3570CA000526DE /* ICMP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449275412C3570CA000526DE /* ICMP.swift */; };
449275442C3C3029000526DE /* TunnelPinger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449275432C3C3029000526DE /* TunnelPinger.swift */; };
449872E12B7BBC5400094DDC /* TunnelSettingsUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */; };
449872E42B7CB96300094DDC /* TunnelSettingsUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449872E32B7CB96300094DDC /* TunnelSettingsUpdateTests.swift */; };
449EBA262B975B9700DFA4EB /* PostQuantumKeyReceiving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449EBA252B975B9700DFA4EB /* PostQuantumKeyReceiving.swift */; };
Expand All @@ -50,6 +49,7 @@
44B3C43D2C00CBBD0079782C /* PacketTunnelActorReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B3C43C2C00CBBC0079782C /* PacketTunnelActorReducerTests.swift */; };
44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; };
44BB5F982BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; };
44C18DE32C74DF93009BE3E1 /* TunnelPinger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449275432C3C3029000526DE /* TunnelPinger.swift */; };
44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */; };
44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */; };
44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; };
Expand Down Expand Up @@ -5448,7 +5448,6 @@
583832272AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift in Sources */,
58FE25DC2AA72A8F003D1918 /* AnyTask.swift in Sources */,
F04AF92D2C466013004A8314 /* PostQuantumNegotiationState.swift in Sources */,
449275442C3C3029000526DE /* TunnelPinger.swift in Sources */,
58FE25D92AA72A8F003D1918 /* AutoCancellingTask.swift in Sources */,
58FE25E12AA72A9B003D1918 /* SettingsReaderProtocol.swift in Sources */,
58C7A4582A863FB90060C66F /* TunnelMonitorProtocol.swift in Sources */,
Expand Down Expand Up @@ -5879,6 +5878,7 @@
58F3F36A2AA08E3C00D3B0A4 /* PacketTunnelProvider.swift in Sources */,
58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */,
F0570CD12C4FB8E1007BDF2D /* PostQuantumKeyExchangingPipeline.swift in Sources */,
44C18DE32C74DF93009BE3E1 /* TunnelPinger.swift in Sources */,
F0570CD22C4FB8E1007BDF2D /* SingleHopPostQuantumKeyExchanging.swift in Sources */,
F0570CD42C4FB8E1007BDF2D /* MultiHopPostQuantumKeyExchanging.swift in Sources */,
58C7A45B2A8640030060C66F /* PacketTunnelPathObserver.swift in Sources */,
Expand Down Expand Up @@ -9153,7 +9153,7 @@
repositoryURL = "https://github.com/mullvad/wireguard-apple.git";
requirement = {
kind = revision;
revision = 9d71a84ae5de0b56e78490938ca1ce43ee4f94fe;
revision = 863748512442c9675298f5cb14ed8566f1e2d019;
};
};
/* End XCRemoteSwiftPackageReference section */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/mullvad/wireguard-apple.git",
"state" : {
"revision" : "9d71a84ae5de0b56e78490938ca1ce43ee4f94fe"
"revision" : "863748512442c9675298f5cb14ed8566f1e2d019"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {

adapter = WgAdapter(packetTunnelProvider: self)

let pinger = TunnelPinger(pingProvider: adapter.icmpPingProvider, replyQueue: internalQueue)

let tunnelMonitor = TunnelMonitor(
eventQueue: internalQueue,
pinger: Pinger(replyQueue: internalQueue),
pinger: pinger,
tunnelDeviceInfo: adapter,
timings: TunnelMonitorTimings()
)
Expand Down
4 changes: 4 additions & 0 deletions ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ struct WgAdapter: TunnelAdapterProtocol {
let isUsingSameIP = (hasIPv4SameAddress || hasIPv6SameAddress) ? "" : "NOT "
logger.debug("Same IP is \(isUsingSameIP)being used")
}

public var icmpPingProvider: ICMPPingProvider {
adapter
}
}

extension WgAdapter: TunnelDeviceInfoProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ extension WireGuardAdapterError: LocalizedError {
return "Failure to start WireGuard backend (error code: \(code))."
case .noInterfaceIp:
return "Interface has no IP address specified."
case .noSuchTunnel:
return "No such WireGuard tunnel"
case .noTunnelVirtualInterface:
return "Tunnel has no virtual (IAN) interface"
case .icmpSocketNotOpen:
return "ICMP socket not open"
case .internalError:
return "Internal error"
}
}
}
19 changes: 14 additions & 5 deletions ios/PacketTunnelCore/Pinger/Pinger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import Foundation
import Network

// This is the legacy Pinger using native TCP/IP networking.

/// ICMP client.
public final class Pinger: PingerProtocol {
// Socket read buffer size.
Expand All @@ -22,6 +24,7 @@ public final class Pinger: PingerProtocol {
private var readBuffer = [UInt8](repeating: 0, count: bufferSize)
private let stateLock = NSRecursiveLock()
private let replyQueue: DispatchQueue
private var destAddress: IPv4Address?

public var onReply: ((PingerReply) -> Void)? {
get {
Expand Down Expand Up @@ -49,12 +52,14 @@ public final class Pinger: PingerProtocol {

/// Open socket and optionally bind it to the given interface.
/// Automatically closes the previously opened socket when called multiple times in a row.
public func openSocket(bindTo interfaceName: String?) throws {
public func openSocket(bindTo interfaceName: String?, destAddress: IPv4Address) throws {
stateLock.lock()
defer { stateLock.unlock() }

closeSocket()

self.destAddress = destAddress

var context = CFSocketContext()
context.info = Unmanaged.passUnretained(self).toOpaque()

Expand Down Expand Up @@ -109,18 +114,22 @@ public final class Pinger: PingerProtocol {

/// Send ping packet to the given address.
/// Returns `PingerSendResult` on success, otherwise throws a `Pinger.Error`.
public func send(to address: IPv4Address) throws -> PingerSendResult {
public func send() throws -> PingerSendResult {
stateLock.lock()
defer { stateLock.unlock() }

guard let socket else {
throw Error.closedSocket
}

guard let destAddress else {
throw Error.parseIPAddress
}

var sa = sockaddr_in()
sa.sin_len = UInt8(MemoryLayout.size(ofValue: sa))
sa.sin_family = sa_family_t(AF_INET)
sa.sin_addr = address.rawValue.withUnsafeBytes { buffer in
sa.sin_addr = destAddress.rawValue.withUnsafeBytes { buffer in
buffer.bindMemory(to: in_addr.self).baseAddress!.pointee
}

Expand Down Expand Up @@ -149,7 +158,7 @@ public final class Pinger: PingerProtocol {
throw Error.sendPacket(errno)
}

return PingerSendResult(sequenceNumber: sequenceNumber, bytesSent: UInt(bytesSent))
return PingerSendResult(sequenceNumber: sequenceNumber)
}

private func nextSequenceNumber() -> UInt16 {
Expand Down Expand Up @@ -217,7 +226,7 @@ public final class Pinger: PingerProtocol {
}
}

private class func makeIPAddress(from sa: sockaddr) -> IPAddress? {
private static func makeIPAddress(from sa: sockaddr) -> IPAddress? {
if sa.sa_family == AF_INET {
return withUnsafeBytes(of: sa) { buffer -> IPAddress? in
buffer.bindMemory(to: sockaddr_in.self).baseAddress.flatMap { boundPointer in
Expand Down
9 changes: 5 additions & 4 deletions ios/PacketTunnelCore/Pinger/PingerProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ public struct PingerSendResult {
/// Sequence id.
public var sequenceNumber: UInt16

/// How many bytes were sent.
public var bytesSent: UInt
public init(sequenceNumber: UInt16) {
self.sequenceNumber = sequenceNumber
}
}

/// A type capable of sending and receving ICMP traffic.
public protocol PingerProtocol {
var onReply: ((PingerReply) -> Void)? { get set }

func openSocket(bindTo interfaceName: String?) throws
func openSocket(bindTo interfaceName: String?, destAddress: IPv4Address) throws
func closeSocket()
func send(to address: IPv4Address) throws -> PingerSendResult
func send() throws -> PingerSendResult
}
68 changes: 56 additions & 12 deletions ios/PacketTunnelCore/Pinger/TunnelPinger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@

import Foundation
import Network
import PacketTunnelCore
import WireGuardKit

public final class TunnelPinger: PingerProtocol {
private var sequenceNumber: UInt16 = 0
private let stateLock = NSRecursiveLock()
private let pingQueue: DispatchQueue
private let replyQueue: DispatchQueue
private var destAddress: IPv4Address?
private var _onReply: ((PingerReply) -> Void)?
public var onReply: ((PingerReply) -> Void)? {
get {
Expand All @@ -24,20 +30,58 @@ public final class TunnelPinger: PingerProtocol {
}
}
}
// Sender identifier passed along with ICMP packet.
private let identifier: UInt16

public func openSocket(bindTo interfaceName: String?) throws {
<#code#>

var socketHandle: Int32?

var pingProvider: ICMPPingProvider

init(pingProvider: ICMPPingProvider, replyQueue: DispatchQueue) {
self.pingProvider = pingProvider
self.replyQueue = replyQueue
self.pingQueue = DispatchQueue(label: "PacketTunnel.icmp")
}

public func openSocket(bindTo interfaceName: String?, destAddress: IPv4Address) throws {
try pingProvider.openICMP(address: destAddress)
self.destAddress = destAddress
}

public func closeSocket() {
<#code#>
pingProvider.closeICMP()
self.destAddress = nil
}

public func send() throws -> PingerSendResult {
let sequenceNumber = nextSequenceNumber()
print("*** sending ping \(sequenceNumber)")

pingQueue.async { [weak self] in
guard let self, let destAddress else { return }
let reply: PingerReply
do {
try pingProvider.sendICMPPing(seqNumber: sequenceNumber)
reply = .success(destAddress, sequenceNumber)
} catch {
reply = .parseError(error)
}
print("--- Pinger reply: \(reply)")

replyQueue.async { [weak self] in
guard let self else { return }
// NOTE: we cheat here by returning the destination address we were passed, rather than parsing it from the packet on the other side of the FFI boundary.
self.onReply?(reply)
}
}

return PingerSendResult(sequenceNumber: UInt16(sequenceNumber))
}

public func send(to address: IPv4Address) throws -> PingerSendResult {
<#code#>

private func nextSequenceNumber() -> UInt16 {
stateLock.lock()
let (nextValue, _) = sequenceNumber.addingReportingOverflow(1)
sequenceNumber = nextValue
stateLock.unlock()

return nextValue
}


}
12 changes: 6 additions & 6 deletions ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public final class TunnelMonitor: TunnelMonitorProtocol {
state.isHeartbeatSuspended = false
state.timeoutReference = now
}
sendPing(to: probeAddress, now: now)
sendPing(now: now)
}
}

Expand Down Expand Up @@ -252,9 +252,9 @@ public final class TunnelMonitor: TunnelMonitorProtocol {
sendConnectionLostEvent()
}

private func sendPing(to receiver: IPv4Address, now: Date) {
private func sendPing(now: Date) {
do {
let sendResult = try pinger.send(to: receiver)
let sendResult = try pinger.send()
state.updatePingStats(sendResult: sendResult, now: now)

logger.trace("Send ping icmp_seq=\(sendResult.sequenceNumber).")
Expand Down Expand Up @@ -298,12 +298,12 @@ public final class TunnelMonitor: TunnelMonitorProtocol {

private func startMonitoring() {
do {
guard let interfaceName = tunnelDeviceInfo.interfaceName else {
logger.debug("Failed to obtain utun interface name.")
guard let interfaceName = tunnelDeviceInfo.interfaceName, let probeAddress else {
logger.debug("Failed to obtain utun interface name or probe address.")
return
}

try pinger.openSocket(bindTo: interfaceName)
try pinger.openSocket(bindTo: interfaceName, destAddress: probeAddress)

state.connectionState = .connecting
startConnectivityCheckTimer()
Expand Down
10 changes: 8 additions & 2 deletions ios/PacketTunnelCoreTests/Mocks/PingerMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ class PingerMock: PingerProtocol {
self.decideOutcome = decideOutcome
}

func openSocket(bindTo interfaceName: String?) throws {
func openSocket(bindTo interfaceName: String?, destAddress: IPv4Address) throws {
stateLock.withLock {
state.destAddress = destAddress
state.isSocketOpen = true
}
}
Expand All @@ -46,11 +47,15 @@ class PingerMock: PingerProtocol {
}
}

func send(to address: IPv4Address) throws -> PingerSendResult {
func send() throws -> PingerSendResult {
// Used for simulation. In reality can be any number.
// But for realism it is: IPv4 header (20 bytes) + ICMP header (8 bytes)
let icmpPacketSize: UInt = 28

guard let address = state.destAddress else {
fatalError("Address somehow not set when sending ping")
}

let nextSequenceId = try stateLock.withLock {
guard state.isSocketOpen else { throw POSIXError(.ENOTCONN) }

Expand Down Expand Up @@ -91,6 +96,7 @@ class PingerMock: PingerProtocol {
var sequenceId: UInt16 = 0
var isSocketOpen = false
var onReply: ((PingerReply) -> Void)?
var destAddress: IPv4Address? = nil

mutating func incrementSequenceId() -> UInt16 {
sequenceId += 1
Expand Down

0 comments on commit 9649a2a

Please sign in to comment.