summaryrefslogtreecommitdiffhomepage
path: root/ios/PacketTunnelCore
diff options
context:
space:
mode:
authorAndrew Bulhak <andrew.bulhak@mullvad.net>2024-07-15 11:13:20 +0200
committerBug Magnet <marco.nikic@mullvad.net>2024-09-18 16:26:47 +0200
commitdee9d4e90af2dda070cbe72cc76cf4a375197012 (patch)
tree01a923d9d29c774abe992cebad9684cfbae615ea /ios/PacketTunnelCore
parent965777945868ee7dff0892945fbb67ecf500ffc6 (diff)
downloadmullvadvpn-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.swift17
-rw-r--r--ios/PacketTunnelCore/Pinger/PingerProtocol.swift9
-rw-r--r--ios/PacketTunnelCore/Pinger/TunnelPinger.swift95
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift12
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()