punchnet-macos/Tun/Policy/PolicyRuntime.swift
2026-05-26 00:24:13 +08:00

108 lines
3.8 KiB
Swift

//
// PolicyRuntime.swift
// Tun
//
// Created by Codex on 2026/5/21.
//
import Foundation
struct PolicyRuntime: @unchecked Sendable {
enum InboundDecision {
case allow
case deny
case missingPolicy
}
private let policyRuleSnapshot: PolicyRuleSnapshot
private let flowSessionTable: FlowSessionTable
private let acl: SDLConfiguration.ACL
init(policyRuleSnapshot: PolicyRuleSnapshot, flowSessionTable: FlowSessionTable, acl: SDLConfiguration.ACL) {
self.policyRuleSnapshot = policyRuleSnapshot
self.flowSessionTable = flowSessionTable
self.acl = acl
}
func evaluateInbound(srcIdentityID: UInt32, ipPacket: IPPacketView) -> InboundDecision {
if self.isExposedService(ipPacket: ipPacket) {
SDLLogger.log("[PolicyRuntime] acl hit, src_identify_id: \(srcIdentityID), check rule: \(debugInfo(ipPacket: ipPacket))")
return self.evaluateByRule(srcIdentityID: srcIdentityID, ipPacket: ipPacket)
}
if self.isAllowedBySession(ipPacket: ipPacket) {
SDLLogger.log("[PolicyRuntime] session hit, src_identify_id: \(srcIdentityID), allow: \(debugInfo(ipPacket: ipPacket))")
return .allow
}
if case .icmp = ipPacket.transportPacket {
SDLLogger.log("[PolicyRuntime] icmp hit, src_identify_id: \(srcIdentityID), allow: \(debugInfo(ipPacket: ipPacket))")
return .allow
}
return self.evaluateByRule(srcIdentityID: srcIdentityID, ipPacket: ipPacket)
}
private func evaluateByRule(srcIdentityID: UInt32, ipPacket: IPPacketView) -> InboundDecision {
guard let ruleMap = self.policyRuleSnapshot.lookup(srcIdentityID) else {
return .missingPolicy
}
let isAllowed = self.isAllowedByRule(ruleMap: ruleMap, ipPacket: ipPacket)
SDLLogger.log("[PolicyRuntime] rule hit: \(isAllowed), src_identify_id: \(srcIdentityID), allow: \(debugInfo(ipPacket: ipPacket))")
return isAllowed ? .allow : .deny
}
private func isExposedService(ipPacket: IPPacketView) -> Bool {
switch ipPacket.transportPacket {
case .tcp(_, let dstPort, _):
return ipPacket.header.proto == TransportProtocol.tcp.rawValue && self.acl.tcpPorts.contains(dstPort)
case .udp(_, let dstPort, _):
return ipPacket.header.proto == TransportProtocol.udp.rawValue && self.acl.udpPorts.contains(dstPort)
default:
return false
}
}
private func isAllowedByRule(ruleMap: PolicyRuleMap, ipPacket: IPPacketView) -> Bool {
let proto = ipPacket.header.proto
switch ipPacket.transportPacket {
case .tcp(_, let dstPort, _):
return ruleMap.isAllow(proto: proto, port: dstPort)
case .udp(_, let dstPort, _):
return ruleMap.isAllow(proto: proto, port: dstPort)
default:
return false
}
}
private func isAllowedBySession(ipPacket: IPPacketView) -> Bool {
guard let reverseFlowSession = ipPacket.flowSession()?.reverse() else {
return false
}
switch ipPacket.transportPacket {
case .tcp(_, _, let flags):
return self.flowSessionTable.allowInboundTCP(reverseFlowSession, flags: flags)
case .udp(let srcPort, _, _):
return self.flowSessionTable.allowInboundUDP(reverseFlowSession, isDNS: srcPort == 53)
default:
return false
}
}
private func debugInfo(ipPacket: IPPacketView) -> String {
switch ipPacket.transportPacket {
case .tcp(_, let dstPort, _):
return "tcp: \(dstPort)"
case .udp(_, let dstPort, _):
return "udp: \(dstPort)"
default:
return "unknown"
}
}
}