punchnet-macos/Tun/Punchnet/Arp/ArpResolver.swift
2026-05-20 18:02:19 +08:00

140 lines
3.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ArpResolver.swift
// sdlan
// 1. ipmac
// 2. ip
// Created by on 2025/7/14.
//
import Foundation
import Darwin
actor ArpResolver {
//
struct ArpEntry {
var mac: Data
var expireTime: TimeInterval
}
private var coolingDown: [UInt32: Date] = [:]
private var known_macs: [UInt32: ArpEntry] = [:]
private let arpTTL: TimeInterval
nonisolated private let snapshotPublisher: SnapshotPublisher<ArpSnapshot>
private var cleanupTask: Task<Void, Never>?
init(arpTTL: TimeInterval = 300) {
self.arpTTL = arpTTL
self.snapshotPublisher = SnapshotPublisher(initial: ArpSnapshot.empty())
}
func start() {
guard self.cleanupTask == nil else {
return
}
self.cleanupTask = Task { [weak self] in
while !Task.isCancelled {
try? await Task.sleep(for: .seconds(1))
await self?.cleanup()
}
}
}
func query(ip: UInt32) -> Data? {
guard let entry = known_macs[ip] else {
return nil
}
if entry.expireTime < Date().timeIntervalSince1970 {
known_macs.removeValue(forKey: ip)
self.publishSnapshot()
return nil
}
return entry.mac
}
func append(ip: UInt32, mac: Data) {
let expireAt = Date().timeIntervalSince1970 + arpTTL
self.known_macs[ip] = ArpEntry(mac: mac, expireTime: expireAt)
self.publishSnapshot()
}
func remove(ip: UInt32) {
self.known_macs.removeValue(forKey: ip)
self.publishSnapshot()
}
func dropMacs(macs: [Data]) {
self.known_macs = self.known_macs.filter { !macs.contains($0.value.mac) }
self.publishSnapshot()
}
func clear() {
self.known_macs = [:]
self.coolingDown = [:]
self.publishSnapshot()
}
func stop() {
self.cleanupTask?.cancel()
self.cleanupTask = nil
self.clear()
}
func makeArpRequest(targetIp: UInt32) throws -> Data? {
guard self.coolingDown[targetIp] == nil else {
return nil
}
self.coolingDown[targetIp] = Date().addingTimeInterval(3)
var arpRequest = SDLArpRequest()
arpRequest.targetIp = targetIp
return try arpRequest.serializedData()
}
func handleArpResponse(arpResponse: SDLArpResponse) {
let targetIp = arpResponse.targetIp
let targetMac = arpResponse.targetMac
if !targetMac.isEmpty {
let expireAt = Date().timeIntervalSince1970 + arpTTL
self.known_macs[targetIp] = ArpEntry(mac: targetMac, expireTime: expireAt)
self.publishSnapshot()
}
}
nonisolated func snapshot() -> ArpSnapshot {
return self.snapshotPublisher.current()
}
private func cleanup() {
let now = Date()
self.coolingDown = self.coolingDown.filter { $0.value > now }
let oldCount = self.known_macs.count
self.known_macs = self.known_macs.filter { $0.value.expireTime >= now.timeIntervalSince1970 }
if self.known_macs.count != oldCount {
self.publishSnapshot()
}
}
private func publishSnapshot() {
self.snapshotPublisher.publish(self.compileSnapshot())
}
private func compileSnapshot() -> ArpSnapshot {
let now = Date().timeIntervalSince1970
let entries = self.known_macs.compactMapValues { entry in
entry.expireTime >= now ? entry.mac : nil
}
return ArpSnapshot(entries: entries)
}
deinit {
self.cleanupTask?.cancel()
}
}