blob: 20defd7a90358c2a4b8041cf65aa3c85aed81b11 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
//
// TunnelPinger.swift
// PacketTunnelCore
//
// Created by Andrew Bulhak on 2024-07-08.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadLogging
import Network
import PacketTunnelCore
import WireGuardKit
public final class TunnelPinger: PingerProtocol, @unchecked Sendable {
private var sequenceNumber: UInt16 = 0
private let stateLock = NSLock()
private let pingReceiveQueue: DispatchQueue
private let replyQueue: DispatchQueue
private var destAddress: IPv4Address?
/// Always accessed from the `replyQueue` and is assigned once, on the main thread of the PacketTunnel. It is thread safe.
public var onReply: ((PingerReply) -> Void)?
private var pingProvider: ICMPPingProvider
private let logger: Logger
init(pingProvider: ICMPPingProvider, replyQueue: DispatchQueue) {
self.pingProvider = pingProvider
self.replyQueue = replyQueue
self.pingReceiveQueue = DispatchQueue(label: "PacketTunnel.Receive.icmp")
self.logger = Logger(label: "TunnelPinger")
}
public func startPinging(destAddress: IPv4Address) throws {
stateLock.withLock {
self.destAddress = destAddress
}
pingReceiveQueue.async { [weak self] in
while let self {
do {
let seq = try pingProvider.receiveICMP()
replyQueue.async { [weak self] in
self?.stateLock.withLock {
self?.onReply?(PingerReply.success(destAddress, UInt16(seq)))
}
}
} catch {
replyQueue.async { [weak self] in
self?.stateLock.withLock {
if self?.destAddress != nil {
self?.onReply?(PingerReply.parseError(error))
}
}
}
return
}
}
}
}
public func stopPinging() {
stateLock.withLock {
self.destAddress = nil
}
}
public func send() throws -> PingerSendResult {
let sequenceNumber = nextSequenceNumber()
stateLock.lock()
defer { stateLock.unlock() }
guard destAddress != nil else { throw WireGuardAdapterError.invalidState }
// 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.
try pingProvider.sendICMPPing(seqNumber: sequenceNumber)
return PingerSendResult(sequenceNumber: UInt16(sequenceNumber))
}
private func nextSequenceNumber() -> UInt16 {
stateLock.lock()
let (nextValue, _) = sequenceNumber.addingReportingOverflow(1)
sequenceNumber = nextValue
stateLock.unlock()
return nextValue
}
}
|