// // 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 init(policyRuleSnapshot: PolicyRuleSnapshot, flowSessionTable: FlowSessionTable) { self.policyRuleSnapshot = policyRuleSnapshot self.flowSessionTable = flowSessionTable } func evaluateInbound(srcIdentityID: UInt32, ipPacket: IPPacketView) -> InboundDecision { 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 } 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 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" } } }