punchnet-macos/Tun/Outbound/PacketOutboundRouter.swift
2026-05-21 23:15:33 +08:00

110 lines
3.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// PacketOutboundRouter.swift
// Tun
//
// Created by on 2026/4/14.
//
import Foundation
struct PacketOutboundRouter {
enum DropReason: String {
case invalidDNSRequest
case noRoute
}
enum ForwardKind {
case sameNetwork
case exitNode
case dnsExitNode
}
enum RouteDecision {
case loopback(ipPacketData: Data)
case cloudDNS(name: String, ipPacketData: Data)
case localDNS(name: String, payload: Data, tracker: DNSLocalClient.DNSTracker)
case forwardToNextHop(ip: UInt32, type: LayerPacket.PacketType, data: Data, kind: ForwardKind)
case drop(reason: DropReason)
}
let networkAddress: SDLConfiguration.NetworkAddress
let exitNode: SDLConfiguration.ExitNode?
func route(packet: IPPacketView, now: Date = Date()) -> RouteDecision {
let dstIp = packet.header.destination
// , ip
if dstIp == self.networkAddress.ip {
return .loopback(ipPacketData: packet.data)
}
// dns
if let dnsDecision = self.routeDNS(packet: packet, now: now) {
return dnsDecision
}
//
// ip
if SDLUtil.inSameNetwork(ip: dstIp, compareIp: self.networkAddress.ip, maskLen: self.networkAddress.maskLen) {
return .forwardToNextHop(ip: dstIp, type: .ipv4, data: packet.data, kind: .sameNetwork)
}
// ,
if let exitNode = self.exitNode {
return .forwardToNextHop(ip: exitNode.exitNodeIp, type: .ipv4, data: packet.data, kind: .exitNode)
}
return .drop(reason: .noRoute)
}
private func routeDNS(packet: IPPacketView, now: Date) -> RouteDecision? {
guard DNSHelper.isDnsRequestPacket(ipPacket: packet) else {
return nil
}
guard case .udp(let srcPort, _, let payloadOffset) = packet.transportPacket else {
return .drop(reason: .invalidDNSRequest)
}
guard let query = DNSParser.parseFirstQuestion(data: packet.data, offset: payloadOffset) else {
return .drop(reason: .invalidDNSRequest)
}
// ip
if query.name.contains(self.networkAddress.networkDomain) {
return .cloudDNS(name: query.name, ipPacketData: packet.data)
}
//
if let exitNode = self.exitNode {
return .forwardToNextHop(ip: exitNode.exitNodeIp, type: .ipv4, data: packet.data, kind: .dnsExitNode)
}
// dnsudppayload
let dnsPayload = Data(packet.data[payloadOffset..<packet.data.count])
let tracker = DNSLocalClient.DNSTracker(
transactionID: query.transactionID,
clientIP: packet.header.source,
clientPort: srcPort,
createdAt: now
)
return .localDNS(name: query.name, payload: dnsPayload, tracker: tracker)
}
}
extension PacketOutboundRouter.RouteDecision {
var shouldTrackFlow: Bool {
switch self {
case .forwardToNextHop(_, _, _, let kind):
switch kind {
case .sameNetwork, .exitNode, .dnsExitNode:
return true
}
default:
return false
}
}
}