127 lines
3.5 KiB
Swift
127 lines
3.5 KiB
Swift
//
|
||
// ArpResolver.swift
|
||
// sdlan
|
||
// 1. 通过ip地址查找mac地址
|
||
// 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)
|
||
}
|
||
|
||
}
|