punchnet-macos/Tun/Policy/PolicyService.swift
2026-05-26 00:47:03 +08:00

115 lines
4.2 KiB
Swift

//
// PolicyService.swift
// punchnet
//
// Created by on 2026/5/19.
//
import Foundation
final class ExposedServiceSnapshot: Snapshot {
let acl: SDLConfiguration.ACL
init(acl: SDLConfiguration.ACL) {
self.acl = acl
}
}
actor PolicyService {
//
private let policyRuleStore: PolicyRuleStore
nonisolated private let snapshotPublisher: SnapshotPublisher<PolicyRuleSnapshot>
nonisolated private let aclPublisher: SnapshotPublisher<ExposedServiceSnapshot>
nonisolated private let flowSessionTable = FlowSessionTable()
// identityId
let identityId: UInt32
private var latestExposedServiceRequestVersion: UInt32 = 0
init(identityId: UInt32, acl: SDLConfiguration.ACL) {
self.identityId = identityId
//
let snapshotPublisher = SnapshotPublisher(initial: PolicyRuleSnapshot.empty())
self.policyRuleStore = PolicyRuleStore(publisher: snapshotPublisher)
self.snapshotPublisher = snapshotPublisher
self.aclPublisher = SnapshotPublisher(initial: ExposedServiceSnapshot(acl: acl))
}
nonisolated func policyRuntime() -> PolicyRuntime {
return PolicyRuntime(policyRuleSnapshot: self.snapshotPublisher.current(), flowSessionTable: self.flowSessionTable, acl: self.aclPublisher.current().acl)
}
nonisolated func recordOutboundFlow(ipPacket: IPPacketView) {
guard let flowSession = ipPacket.flowSession() else {
return
}
switch ipPacket.transportPacket {
case .tcp(_, _, let flags):
self.flowSessionTable.recordOutboundTCP(flowSession, flags: flags)
case .udp(_, let dstPort, _):
self.flowSessionTable.recordOutboundUDP(flowSession, isDNS: dstPort == 53)
default:
return
}
}
func makePolicyRequest(srcIdentityID: UInt32) async -> Data? {
return await self.policyRuleStore.makePolicyRequest(srcIdentityId: srcIdentityID, dstIdentityId: self.identityId)
}
func updatePolicy(superServiceProxy: SDLSuperServiceProxy) async {
let requests = await self.policyRuleStore.makeBatchPolicyRequests(dstIdentityID: self.identityId)
for request in requests {
await superServiceProxy.send(type: .policyRequest, data: request)
}
}
func applyPolicyResponse(_ policyResponse: SDLPolicyResponse) async {
guard policyResponse.dstIdentityID == self.identityId else {
SDLLogger.log("[PolicyService] ignore policy response, dstIdentityID mismatch: \(policyResponse.dstIdentityID), expected: \(self.identityId)", for: .debug)
return
}
await self.policyRuleStore.applyPolicyResponse(policyResponse)
}
func makeExposedServiceRequest() -> Data? {
var request = SDLExposedServiceRequest()
self.latestExposedServiceRequestVersion = Self.nextVersion(after: self.latestExposedServiceRequestVersion)
request.version = self.latestExposedServiceRequestVersion
SDLLogger.log("[PolicyService] make exposed service request, version: \(request.version)", for: .debug)
return try? request.serializedData()
}
func applyExposedServiceResponse(_ response: SDLExposedServiceResponse) -> SDLConfiguration.ACL? {
guard response.version == self.latestExposedServiceRequestVersion else {
SDLLogger.log("[PolicyService] ignore exposed service response, version: \(response.version), latest request version: \(self.latestExposedServiceRequestVersion)", for: .debug)
return nil
}
let acl = SDLConfiguration.ACL(response: response)
self.aclPublisher.publish(ExposedServiceSnapshot(acl: acl))
SDLLogger.log("[PolicyService] apply exposed service response, version: \(response.version), tcp: \(acl.tcpPorts.count), udp: \(acl.udpPorts.count)", for: .debug)
return acl
}
func clear() async {
self.flowSessionTable.clear()
await self.policyRuleStore.clear()
}
deinit {
SDLLogger.log("[PolicyService] deinit")
}
private static func nextVersion(after version: UInt32) -> UInt32 {
if version == UInt32.max {
return 1
}
return version + 1
}
}