diff options
| author | Andrew Bulhak <andrew.bulhak@mullvad.net> | 2024-07-15 11:13:20 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2024-09-18 16:26:47 +0200 |
| commit | dee9d4e90af2dda070cbe72cc76cf4a375197012 (patch) | |
| tree | 01a923d9d29c774abe992cebad9684cfbae615ea /ios/PacketTunnelCore | |
| parent | 965777945868ee7dff0892945fbb67ecf500ffc6 (diff) | |
| download | mullvadvpn-dee9d4e90af2dda070cbe72cc76cf4a375197012.tar.xz mullvadvpn-dee9d4e90af2dda070cbe72cc76cf4a375197012.zip | |
Add IAN-based TunnelPinger refactoring the pinger protocol accordingly
Diffstat (limited to 'ios/PacketTunnelCore')
| -rw-r--r-- | ios/PacketTunnelCore/Pinger/Pinger.swift | 17 | ||||
| -rw-r--r-- | ios/PacketTunnelCore/Pinger/PingerProtocol.swift | 9 | ||||
| -rw-r--r-- | ios/PacketTunnelCore/Pinger/TunnelPinger.swift | 95 | ||||
| -rw-r--r-- | ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift | 12 |
4 files changed, 119 insertions, 14 deletions
diff --git a/ios/PacketTunnelCore/Pinger/Pinger.swift b/ios/PacketTunnelCore/Pinger/Pinger.swift index 68a06a7028..69ae1ab5af 100644 --- a/ios/PacketTunnelCore/Pinger/Pinger.swift +++ b/ios/PacketTunnelCore/Pinger/Pinger.swift @@ -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. @@ -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 { @@ -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() @@ -109,7 +114,7 @@ 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() } @@ -117,10 +122,14 @@ public final class Pinger: PingerProtocol { 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 } @@ -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 { diff --git a/ios/PacketTunnelCore/Pinger/PingerProtocol.swift b/ios/PacketTunnelCore/Pinger/PingerProtocol.swift index 2f1c9d544c..67c64c1448 100644 --- a/ios/PacketTunnelCore/Pinger/PingerProtocol.swift +++ b/ios/PacketTunnelCore/Pinger/PingerProtocol.swift @@ -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 } diff --git a/ios/PacketTunnelCore/Pinger/TunnelPinger.swift b/ios/PacketTunnelCore/Pinger/TunnelPinger.swift new file mode 100644 index 0000000000..d1f7973b5e --- /dev/null +++ b/ios/PacketTunnelCore/Pinger/TunnelPinger.swift @@ -0,0 +1,95 @@ +// +// TunnelPinger.swift +// PacketTunnelCore +// +// Created by Andrew Bulhak on 2024-07-08. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadLogging +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 { + stateLock.withLock { + return _onReply + } + } + set { + stateLock.withLock { + _onReply = newValue + } + } + } + + var socketHandle: Int32? + + var pingProvider: ICMPPingProvider + + private let logger: Logger + + init(pingProvider: ICMPPingProvider, replyQueue: DispatchQueue) { + self.pingProvider = pingProvider + self.replyQueue = replyQueue + self.pingQueue = DispatchQueue(label: "PacketTunnel.icmp") + self.logger = Logger(label: "TunnelPinger") + } + + deinit { + pingProvider.closeICMP() + } + + public func openSocket(bindTo interfaceName: String?, destAddress: IPv4Address) throws { + try pingProvider.openICMP(address: destAddress) + self.destAddress = destAddress + } + + public func closeSocket() { + pingProvider.closeICMP() + self.destAddress = nil + } + + public func send() throws -> PingerSendResult { + let sequenceNumber = nextSequenceNumber() + logger.debug("*** 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) + } + self.logger.debug("--- 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)) + } + + private func nextSequenceNumber() -> UInt16 { + stateLock.lock() + let (nextValue, _) = sequenceNumber.addingReportingOverflow(1) + sequenceNumber = nextValue + stateLock.unlock() + + return nextValue + } +} diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift index 40adf180ce..b723862191 100644 --- a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift +++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift @@ -222,7 +222,7 @@ public final class TunnelMonitor: TunnelMonitorProtocol { state.isHeartbeatSuspended = false state.timeoutReference = now } - sendPing(to: probeAddress, now: now) + sendPing(now: now) } } @@ -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).") @@ -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() |
