108 lines
3.8 KiB
Swift
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))", 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"
|
|
}
|
|
}
|
|
|
|
}
|