summaryrefslogtreecommitdiffhomepage
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
parent965777945868ee7dff0892945fbb67ecf500ffc6 (diff)
downloadmullvadvpn-dee9d4e90af2dda070cbe72cc76cf4a375197012.tar.xz
mullvadvpn-dee9d4e90af2dda070cbe72cc76cf4a375197012.zip
Add IAN-based TunnelPinger refactoring the pinger protocol accordingly
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj6
-rw-r--r--ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved2
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift4
-rw-r--r--ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift4
-rw-r--r--ios/PacketTunnel/WireGuardAdapter/WireGuardAdapterError+Localization.swift8
-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
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PingerMock.swift10
10 files changed, 148 insertions, 19 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 1441486f87..f939a41780 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -49,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 */; };
@@ -1371,6 +1372,7 @@
06FAE67B28F83CA50033DD93 /* REST.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = REST.swift; sourceTree = "<group>"; };
06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = "<group>"; };
449275412C3570CA000526DE /* ICMP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICMP.swift; sourceTree = "<group>"; };
+ 449275432C3C3029000526DE /* TunnelPinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelPinger.swift; sourceTree = "<group>"; };
449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsUpdate.swift; sourceTree = "<group>"; };
449872E32B7CB96300094DDC /* TunnelSettingsUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsUpdateTests.swift; sourceTree = "<group>"; };
449EB9FC2B95F8AD00DFA4EB /* DeviceMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceMock.swift; sourceTree = "<group>"; };
@@ -3347,6 +3349,7 @@
5838318A27C40A3900000571 /* Pinger.swift */,
58799A352A84FC9F007BE51F /* PingerProtocol.swift */,
449275412C3570CA000526DE /* ICMP.swift */,
+ 449275432C3C3029000526DE /* TunnelPinger.swift */,
);
path = Pinger;
sourceTree = "<group>";
@@ -5938,6 +5941,7 @@
F0570CD12C4FB8E1007BDF2D /* EphemeralPeerExchangingPipeline.swift in Sources */,
F0570CD22C4FB8E1007BDF2D /* SingleHopEphemeralPeerExchanger.swift in Sources */,
F0570CD42C4FB8E1007BDF2D /* MultiHopEphemeralPeerExchanger.swift in Sources */,
+ 44C18DE32C74DF93009BE3E1 /* TunnelPinger.swift in Sources */,
58C7A45B2A8640030060C66F /* PacketTunnelPathObserver.swift in Sources */,
580D6B8E2AB33BBF00B2D6E0 /* BlockedStateErrorMapper.swift in Sources */,
06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */,
@@ -9206,7 +9210,7 @@
repositoryURL = "https://github.com/mullvad/wireguard-apple.git";
requirement = {
kind = revision;
- revision = 143776f946e1da2566aa3e830f95b3e75f914f35;
+ revision = 82ae19d03fcaa83b9636e560ce9bea8fec9dc96f;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 6212e4cc1c..4ac54b324d 100644
--- a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -14,7 +14,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/mullvad/wireguard-apple.git",
"state" : {
- "revision" : "143776f946e1da2566aa3e830f95b3e75f914f35"
+ "revision" : "82ae19d03fcaa83b9636e560ce9bea8fec9dc96f"
}
}
],
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index 5062076688..d76bf3b81e 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -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()
)
diff --git a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
index cf52553a5e..dd2d562c2e 100644
--- a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
+++ b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
@@ -100,6 +100,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 {
diff --git a/ios/PacketTunnel/WireGuardAdapter/WireGuardAdapterError+Localization.swift b/ios/PacketTunnel/WireGuardAdapter/WireGuardAdapterError+Localization.swift
index 69806809d4..4ad6ffce0e 100644
--- a/ios/PacketTunnel/WireGuardAdapter/WireGuardAdapterError+Localization.swift
+++ b/ios/PacketTunnel/WireGuardAdapter/WireGuardAdapterError+Localization.swift
@@ -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 let .internalError(code):
+ return "Internal error \(code)"
}
}
}
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()
diff --git a/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift b/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift
index 9ed50fcd3c..ab80bee914 100644
--- a/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift
@@ -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
}
}
@@ -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) }
@@ -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