// // PacketOutboundActor.swift // Tun // // Created by Codex on 2026/5/20. // import Foundation import NetworkExtension import NIOCore actor PacketOutboundActor { private typealias PacketReadResult = (packets: [Data], protocols: [NSNumber])? private let provider: NEPacketTunnelProvider private let sessionManager: SessionManager private let arpResolver: ArpResolver private let puncherActor: SDLPuncherActor private let policyService: PolicyService private let superServiceProxy: SDLSuperServiceProxy private let udpHoleServiceProxy: SDLUDPHoleServiceProxy private let flowTracer: SDLFlowTracer private var packetReaderTask: Task? private var packetReaderGeneration: UInt64 = 0 private var networkAddress: SDLConfiguration.NetworkAddress private var identityId: UInt32 private var exitNode: SDLConfiguration.ExitNode? private var stunSocketAddress: SocketAddress private var dataCipher: CCDataCipher? private var dnsService: SDLDNSService? init(provider: NEPacketTunnelProvider, config: SDLConfiguration, dataCipher: CCDataCipher?, sessionManager: SessionManager, arpResolver: ArpResolver, puncherActor: SDLPuncherActor, policyService: PolicyService, superServiceProxy: SDLSuperServiceProxy, udpHoleServiceProxy: SDLUDPHoleServiceProxy, flowTracer: SDLFlowTracer) { self.provider = provider self.networkAddress = config.networkAddress self.identityId = config.identityId self.exitNode = config.exitNode self.stunSocketAddress = config.stunSocketAddress self.dataCipher = dataCipher self.sessionManager = sessionManager self.arpResolver = arpResolver self.puncherActor = puncherActor self.policyService = policyService self.superServiceProxy = superServiceProxy self.udpHoleServiceProxy = udpHoleServiceProxy self.flowTracer = flowTracer } func updateRuntime(config: SDLConfiguration, dataCipher: CCDataCipher?) { self.networkAddress = config.networkAddress self.identityId = config.identityId self.exitNode = config.exitNode self.stunSocketAddress = config.stunSocketAddress self.dataCipher = dataCipher } func updateDNSService(_ dnsService: SDLDNSService?) { self.dnsService = dnsService } func startPacketReader() async { guard self.packetReaderTask == nil else { return } self.packetReaderGeneration &+= 1 let provider = self.provider let generation = self.packetReaderGeneration self.packetReaderTask = Task(priority: .high) { [weak self] in while !Task.isCancelled { guard let batch = await Self.readPackets(from: provider) else { break } if Task.isCancelled { break } for (data, number) in zip(batch.packets, batch.protocols) where number.int32Value == 2 { if Task.isCancelled { break } if let packet = IPPacket(data) { await self?.handleTunPacket(packet) } } } await self?.finishPacketReader(generation: generation) SDLLogger.log("[PacketOutboundActor] packet reader task finished") } } func stop() async { self.packetReaderGeneration &+= 1 let packetReaderTask = self.packetReaderTask self.packetReaderTask = nil packetReaderTask?.cancel() } func handleTunPacket(_ packet: IPPacket) async { let router = SDLTunPacketRouter(networkAddress: self.networkAddress, exitNode: self.exitNode) let decision = router.route(packet: packet) if decision.shouldTrackFlow, let flowSession = packet.flowSession() { await self.policyService.flowSessionManager.updateSession(flowSession) } await self.handleTunRouteDecision(decision) } private func finishPacketReader(generation: UInt64) { guard generation == self.packetReaderGeneration else { return } self.packetReaderTask = nil } private static func readPackets(from provider: NEPacketTunnelProvider) async -> PacketReadResult { let readContinuation = OnceContinuation() return await withTaskCancellationHandler { await withCheckedContinuation { continuation in readContinuation.set(continuation) provider.packetFlow.readPackets { packets, protocols in readContinuation.resume(returning: (packets: packets, protocols: protocols)) } } } onCancel: { readContinuation.resume(returning: nil) } } func routeLayerPacket(dstMac: Data, type: LayerPacket.PacketType, data: Data) async { let forwarder = self.makeLayerPacketForwarder() guard let plan = try? await forwarder.makeDeliveryPlan(dstMac: dstMac, type: type, data: data) else { return } switch plan { case .superNode(let payload): await self.sendSuperPacket(type: .data, data: payload) case .peer(let payload, let session): SDLLogger.log("[PacketOutboundActor] step 5 send packet by session: \(session)", for: .trace) await self.sendPeerPacket(type: .data, data: payload, remoteAddress: session.natAddress) self.flowTracer.inc(num: payload.count, type: .p2p) case .superNodeAndPunch(let payload, let request): await self.sendSuperPacket(type: .data, data: payload) SDLLogger.log("[PacketOutboundActor] step 5 send packet by super: \(self.stunSocketAddress)", for: .trace) self.flowTracer.inc(num: payload.count, type: .forward) if let queryData = await self.puncherActor.makeQueryInfoRequest(request: request) { await self.superServiceProxy.send(type: .queryInfo, data: queryData) } } } private func handleTunRouteDecision(_ decision: SDLTunPacketRouter.RouteDecision) async { switch decision { case .loopback(let ipPacketData): let nePacket = NEPacket(data: ipPacketData, protocolFamily: 2) self.provider.packetFlow.writePacketObjects([nePacket]) case .cloudDNS(let name, let ipPacketData): SDLLogger.log("[PacketOutboundActor] get cloud dns request: \(name)") await self.dnsService?.forward(ipPacketData: ipPacketData) case .localDNS(let name, let payload, let tracker): SDLLogger.log("[PacketOutboundActor] get local dns request: \(name)") await self.dnsService?.queryLocal(tracker: tracker, dnsPayload: payload) case .forwardToNextHop(let ip, let type, let data, let kind): await self.forwardPacketToNextHop(ip: ip, type: type, data: data, kind: kind) case .drop(let reason): SDLLogger.log("[PacketOutboundActor] drop tun packet, reason: \(reason.rawValue)", for: .trace) } } private func forwardPacketToNextHop(ip: UInt32, type: LayerPacket.PacketType, data: Data, kind: SDLTunPacketRouter.ForwardKind) async { switch kind { case .sameNetwork: SDLLogger.log("[PacketOutboundActor] dstIp: \(SDLUtil.int32ToIp(ip)) same network", for: .trace) case .exitNode, .dnsExitNode: SDLLogger.log("[PacketOutboundActor] use exit_node: \(SDLUtil.int32ToIp(ip))", for: .trace) } if let dstMac = self.arpResolver.snapshot().lookup(ip) { SDLLogger.log("[PacketOutboundActor] dstIp: \(SDLUtil.int32ToIp(ip)), dst_mac is: \(SDLUtil.formatMacAddress(mac: dstMac))", for: .trace) await self.routeLayerPacket(dstMac: dstMac, type: type, data: data) } else { SDLLogger.log("[PacketOutboundActor] dstIp: \(SDLUtil.int32ToIp(ip)) arp query not found, broadcast", for: .trace) if let arpRequest = try? await self.arpResolver.makeArpRequest(targetIp: ip) { await self.superServiceProxy.send(type: .arpRequest, data: arpRequest) } } } private func makeLayerPacketForwarder() -> SDLLayerPacketForwarder { return .init( networkAddress: self.networkAddress, identityID: self.identityId, dataCipher: self.dataCipher, sessionManager: self.sessionManager ) } private func sendSuperPacket(type: SDLPacketType, data: Data) async { await self.sendPacket(type: type, data: data, remoteAddress: self.stunSocketAddress) } private func sendPeerPacket(type: SDLPacketType, data: Data, remoteAddress: SocketAddress) async { await self.sendPacket(type: type, data: data, remoteAddress: remoteAddress) } private func sendPacket(type: SDLPacketType, data: Data, remoteAddress: SocketAddress) async { await self.udpHoleServiceProxy.send(type: type, data: data, remoteAddress: remoteAddress) } }