punchnet-macos/Tun/Policy/PolicyRuleStore.swift
2026-05-21 17:41:56 +08:00

159 lines
5.7 KiB
Swift

//
// PolicyRuleStore.swift
// punchnet
// 1.
// Created by on 2026/2/5.
//
import Foundation
import NIO
actor PolicyRuleStore {
private struct PolicyEntry {
let ruleMap: PolicyRuleMap
let expiresAt: TimeInterval
}
private struct PendingRequest {
let version: UInt32
let retryCount: Int
let nextRetryAt: TimeInterval
}
private let policyTTL: TimeInterval = 120
private let refreshLeadTime: TimeInterval = 15
private let baseRetryDelay: TimeInterval = 2
private let maxRetryDelay: TimeInterval = 60
// , map[identityId] = version
private var versions: [UInt32: UInt32] = [:]
private var pendingByIdentity: [UInt32: PendingRequest] = [:]
nonisolated private let alloctor = ByteBufferAllocator()
private let publisher: SnapshotPublisher<PolicyRuleSnapshot>
private var policyByIdentity: [UInt32: PolicyEntry] = [:]
init(publisher: SnapshotPublisher<PolicyRuleSnapshot>) {
self.publisher = publisher
}
func makeBatchPolicyRequests(dstIdentityID: UInt32) -> [Data] {
let now = Date().timeIntervalSince1970
let identities = Set(self.policyByIdentity.keys).union(self.pendingByIdentity.keys)
return identities.compactMap { identityId in
self.makePolicyRequestIfDue(srcIdentityId: identityId, dstIdentityId: dstIdentityID, now: now, forceMissing: false)
}
}
func makePolicyRequest(srcIdentityId: UInt32, dstIdentityId: UInt32) -> Data? {
guard self.policyByIdentity[srcIdentityId] == nil else {
return nil
}
return self.makePolicyRequestIfDue(
srcIdentityId: srcIdentityId,
dstIdentityId: dstIdentityId,
now: Date().timeIntervalSince1970,
forceMissing: true
)
}
//
func applyPolicyResponse(_ policyResponse: SDLPolicyResponse) {
let id = policyResponse.srcIdentityID
let version = policyResponse.version
if let pending = self.pendingByIdentity[id], pending.version <= version {
self.pendingByIdentity.removeValue(forKey: id)
}
guard self.policyByIdentity[id] == nil || ((self.policyByIdentity[id]?.ruleMap.version ?? 0) < version) else {
return
}
//
var buffer = alloctor.buffer(bytes: policyResponse.rules)
var ruleMap: [UInt8: [UInt16: Bool]] = [:]
while true {
guard let proto = buffer.readInteger(endianness: .big, as: UInt8.self),
let port = buffer.readInteger(endianness: .big, as: UInt16.self) else {
break
}
ruleMap[proto, default: [:]][port] = true
}
let now = Date().timeIntervalSince1970
self.policyByIdentity[id] = PolicyEntry(
ruleMap: PolicyRuleMap(version: version, ruleMap: ruleMap),
expiresAt: now + self.policyTTL
)
SDLLogger.log("[PolicyRuleStore] apply policy response, srcIdentityID: \(id), version: \(version), rulesCount: \(ruleMap.reduce(0) { $0 + $1.value.count })", for: .debug)
//
let snapshot = compileSnapshot()
publisher.publish(snapshot)
}
func clear() {
self.versions.removeAll()
self.pendingByIdentity.removeAll()
self.policyByIdentity.removeAll()
self.publisher.publish(PolicyRuleSnapshot.empty())
}
private func compileSnapshot() -> PolicyRuleSnapshot {
return PolicyRuleSnapshot(ruleMapByIdentity: self.policyByIdentity.mapValues(\.ruleMap))
}
private func nextVersion(identityId: UInt32) -> UInt32 {
let version = self.versions[identityId, default: 1]
//
self.versions[identityId] = version + 1
return version
}
private func makePolicyRequestIfDue(srcIdentityId: UInt32, dstIdentityId: UInt32, now: TimeInterval, forceMissing: Bool) -> Data? {
if let pending = self.pendingByIdentity[srcIdentityId], pending.nextRetryAt > now {
return nil
}
if !forceMissing,
let entry = self.policyByIdentity[srcIdentityId],
entry.expiresAt - self.refreshLeadTime > now {
return nil
}
let version = self.nextVersion(identityId: srcIdentityId)
guard let data = self.makePolicyRequestData(srcIdentityId: srcIdentityId, dstIdentityId: dstIdentityId, version: version) else {
return nil
}
let retryCount = self.pendingByIdentity[srcIdentityId].map { $0.retryCount + 1 } ?? 0
let retryDelay = self.retryDelay(for: retryCount, identityId: srcIdentityId)
self.pendingByIdentity[srcIdentityId] = PendingRequest(
version: version,
retryCount: retryCount,
nextRetryAt: now + retryDelay
)
return data
}
private func makePolicyRequestData(srcIdentityId: UInt32, dstIdentityId: UInt32, version: UInt32) -> Data? {
var policyRequest = SDLPolicyRequest()
policyRequest.srcIdentityID = srcIdentityId
policyRequest.dstIdentityID = dstIdentityId
policyRequest.version = version
return try? policyRequest.serializedData()
}
private func retryDelay(for retryCount: Int, identityId: UInt32) -> TimeInterval {
let cappedRetryCount = min(retryCount, 5)
let multiplier = Double(1 << cappedRetryCount)
let jitter = Double(identityId % 1_000) / 1_000
return min(self.baseRetryDelay * multiplier + jitter, self.maxRetryDelay)
}
}