// // 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))", category: .policy) 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))", category: .policy) return .allow } if case .icmp = ipPacket.transportPacket { SDLLogger.log("[PolicyRuntime] icmp hit, src_identify_id: \(srcIdentityID), allow: \(debugInfo(ipPacket: ipPacket))", category: .policy) 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))", category: .policy) 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" } } }