fix Snapshot

This commit is contained in:
anlicheng 2026-05-20 17:58:31 +08:00
parent 153f172daf
commit c5c06cbb1f
6 changed files with 71 additions and 7 deletions

View File

@ -19,11 +19,13 @@ actor ArpServer {
private var known_macs: [UInt32: ArpEntry] = [:] private var known_macs: [UInt32: ArpEntry] = [:]
private let arpTTL: TimeInterval private let arpTTL: TimeInterval
nonisolated private let snapshotPublisher: SnapshotPublisher<ArpSnapshot>
private var cleanupTask: Task<Void, Never>? private var cleanupTask: Task<Void, Never>?
init(arpTTL: TimeInterval = 300) { init(arpTTL: TimeInterval = 300) {
self.arpTTL = arpTTL self.arpTTL = arpTTL
self.snapshotPublisher = SnapshotPublisher(initial: ArpSnapshot.empty())
} }
func start() { func start() {
@ -46,6 +48,7 @@ actor ArpServer {
if entry.expireTime < Date().timeIntervalSince1970 { if entry.expireTime < Date().timeIntervalSince1970 {
known_macs.removeValue(forKey: ip) known_macs.removeValue(forKey: ip)
self.publishSnapshot()
return nil return nil
} }
@ -55,19 +58,23 @@ actor ArpServer {
func append(ip: UInt32, mac: Data) { func append(ip: UInt32, mac: Data) {
let expireAt = Date().timeIntervalSince1970 + arpTTL let expireAt = Date().timeIntervalSince1970 + arpTTL
self.known_macs[ip] = ArpEntry(mac: mac, expireTime: expireAt) self.known_macs[ip] = ArpEntry(mac: mac, expireTime: expireAt)
self.publishSnapshot()
} }
func remove(ip: UInt32) { func remove(ip: UInt32) {
self.known_macs.removeValue(forKey: ip) self.known_macs.removeValue(forKey: ip)
self.publishSnapshot()
} }
func dropMacs(macs: [Data]) { func dropMacs(macs: [Data]) {
self.known_macs = self.known_macs.filter { !macs.contains($0.value.mac) } self.known_macs = self.known_macs.filter { !macs.contains($0.value.mac) }
self.publishSnapshot()
} }
func clear() { func clear() {
self.known_macs = [:] self.known_macs = [:]
self.coolingDown = [:] self.coolingDown = [:]
self.publishSnapshot()
} }
func stop() { func stop() {
@ -95,12 +102,34 @@ actor ArpServer {
if !targetMac.isEmpty { if !targetMac.isEmpty {
let expireAt = Date().timeIntervalSince1970 + arpTTL let expireAt = Date().timeIntervalSince1970 + arpTTL
self.known_macs[targetIp] = ArpEntry(mac: targetMac, expireTime: expireAt) self.known_macs[targetIp] = ArpEntry(mac: targetMac, expireTime: expireAt)
self.publishSnapshot()
} }
} }
nonisolated func snapshot() -> ArpSnapshot {
return self.snapshotPublisher.current()
}
private func cleanup() { private func cleanup() {
let now = Date() let now = Date()
self.coolingDown = self.coolingDown.filter { $0.value > now } 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 { deinit {

View File

@ -0,0 +1,24 @@
//
// ArpSnapshot.swift
// Tun
//
// Created by Codex on 2026/5/20.
//
import Foundation
final class ArpSnapshot: Snapshot {
private let entries: [UInt32: Data]
init(entries: [UInt32: Data]) {
self.entries = entries
}
func lookup(_ ip: UInt32) -> Data? {
return self.entries[ip]
}
static func empty() -> ArpSnapshot {
return ArpSnapshot(entries: [:])
}
}

View File

@ -195,7 +195,7 @@ actor PacketOutboundActor {
SDLLogger.log("[PacketOutboundActor] use exit_node: \(SDLUtil.int32ToIp(ip))", for: .trace) SDLLogger.log("[PacketOutboundActor] use exit_node: \(SDLUtil.int32ToIp(ip))", for: .trace)
} }
if let dstMac = await self.arpServer.query(ip: ip) { if let dstMac = self.arpServer.snapshot().lookup(ip) {
SDLLogger.log("[PacketOutboundActor] dstIp: \(SDLUtil.int32ToIp(ip)), dst_mac is: \(SDLUtil.formatMacAddress(mac: dstMac))", for: .trace) SDLLogger.log("[PacketOutboundActor] dstIp: \(SDLUtil.int32ToIp(ip)), dst_mac is: \(SDLUtil.formatMacAddress(mac: dstMac))", for: .trace)
await self.routeLayerPacket(dstMac: dstMac, type: type, data: data) await self.routeLayerPacket(dstMac: dstMac, type: type, data: data)
} else { } else {

View File

@ -4,8 +4,9 @@
// //
// Created by on 2026/2/5. // Created by on 2026/2/5.
// //
import Foundation
final class IdentitySnapshot { final class IdentitySnapshot: Snapshot {
typealias IdentityID = UInt32 typealias IdentityID = UInt32
private let identityMap: [IdentityID: IdentityRuleMap] private let identityMap: [IdentityID: IdentityRuleMap]

View File

@ -0,0 +1,10 @@
//
// Snapshot.swift
// punchnet
//
// Created by on 2026/5/20.
//
protocol Snapshot: AnyObject {
}

View File

@ -6,21 +6,21 @@
// //
import Atomics import Atomics
final class SnapshotPublisher<IdentitySnapshot: AnyObject> { final class SnapshotPublisher<S: Snapshot> {
private let atomic: ManagedAtomic<Unmanaged<IdentitySnapshot>> private let atomic: ManagedAtomic<Unmanaged<S>>
init(initial snapshot: IdentitySnapshot) { init(initial snapshot: S) {
self.atomic = ManagedAtomic(.passRetained(snapshot)) self.atomic = ManagedAtomic(.passRetained(snapshot))
} }
func publish(_ snapshot: IdentitySnapshot) { func publish(_ snapshot: S) {
let newRef = Unmanaged.passRetained(snapshot) let newRef = Unmanaged.passRetained(snapshot)
let oldRef = atomic.exchange(newRef, ordering: .acquiring) let oldRef = atomic.exchange(newRef, ordering: .acquiring)
oldRef.release() oldRef.release()
} }
@inline(__always) @inline(__always)
func current() -> IdentitySnapshot { func current() -> S {
atomic.load(ordering: .relaxed).takeUnretainedValue() atomic.load(ordering: .relaxed).takeUnretainedValue()
} }