punchnet-macos/Tun/Arp/ArpResolver.swift
2026-05-27 15:47:51 +08:00

127 lines
3.5 KiB
Swift
Raw Permalink 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>
init(arpTTL: TimeInterval = 300) {
self.arpTTL = arpTTL
self.snapshotPublisher = SnapshotPublisher(initial: ArpSnapshot.empty())
}
func runCleanup() async throws {
while !Task.isCancelled {
try await Task.sleep(for: .seconds(1))
try Task.checkCancellation()
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.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)
}
}