// // SDLHoleControlPlane.swift // Tun // // Created by Codex on 2026/5/28. // import Foundation import NIOCore enum HoleDecision { case updateNatType(SDLNATProberActor.NatType) case sendPacket(type: SDLPacketType, data: Data, remoteAddress: SocketAddress) case addSession(Session) } struct SDLHoleControlPlane { private let networkAddress: SDLConfiguration.NetworkAddress init(networkAddress: SDLConfiguration.NetworkAddress) { self.networkAddress = networkAddress } func handle(_ event: SDLUDPHoleService.Event) -> [HoleDecision] { switch event { case .ready(let localAddress): SDLLogger.log("[SDLContext] udpHole ready: \(localAddress)") return [] case .natType(let natType): SDLLogger.log("[SDLContext] nat_type is: \(natType)") return [ .updateNatType(natType) ] case .packet(let remoteAddress, let message): return self.handlePacket(remoteAddress: remoteAddress, message: message) case .closed(let error): SDLLogger.log("[SDLContext] udpHole closed: \(error)", for: .debug) return [] } } private func handlePacket(remoteAddress: SocketAddress, message: SDLHoleControlMessage) -> [HoleDecision] { switch message { case .stunReply, .stunProbeReply: SDLLogger.log("[SDLContext] get a stun reply", for: .debug) return [] case .register(let register): return self.handleRegister(remoteAddress: remoteAddress, register: register) case .registerAck(let registerAck): return self.handleRegisterAck(remoteAddress: remoteAddress, registerAck: registerAck) } } private func handleRegister(remoteAddress: SocketAddress, register: SDLRegister) -> [HoleDecision] { SDLLogger.log("[SDLContext] register packet: \(register), network_address: \(self.networkAddress)") var decisions: [HoleDecision] = [] guard register.dstMac == self.networkAddress.mac && register.networkID == self.networkAddress.networkId else { SDLLogger.log("[SDLContext] didReadRegister get a invalid packet, because dst_ip not matched: \(register.dstMac)") return decisions } var registerAck = SDLRegisterAck() registerAck.networkID = self.networkAddress.networkId registerAck.srcMac = self.networkAddress.mac registerAck.dstMac = register.srcMac if let data = try? registerAck.serializedData() { decisions.append(.sendPacket(type: .registerAck, data: data, remoteAddress: remoteAddress)) } if let session = self.makeSession(dstMac: register.srcMac, remoteAddress: remoteAddress) { decisions.append(.addSession(session)) } else { SDLLogger.log("[SDLContext] didReadRegister get unsupported remoteAddress: \(remoteAddress)", for: .debug) } return decisions } private func handleRegisterAck(remoteAddress: SocketAddress, registerAck: SDLRegisterAck) -> [HoleDecision] { guard registerAck.dstMac == self.networkAddress.mac && registerAck.networkID == self.networkAddress.networkId else { SDLLogger.log("[SDLContext] didReadRegisterAck get a invalid packet, because dst_mac not matched: \(registerAck.dstMac)", for: .debug) return [] } guard let session = self.makeSession(dstMac: registerAck.srcMac, remoteAddress: remoteAddress) else { SDLLogger.log("[SDLContext] didReadRegisterAck get unsupported remoteAddress: \(remoteAddress)", for: .debug) return [] } return [.addSession(session)] } private func makeSession(dstMac: Data, remoteAddress: SocketAddress) -> Session? { guard let addressType = Self.addressType(from: remoteAddress) else { return nil } return Session(dstMac: dstMac, natAddress: remoteAddress, addressType: addressType) } private static func addressType(from remoteAddress: SocketAddress) -> Session.AddressType? { switch remoteAddress { case .v4: return .v4 case .v6: return .v6 default: return nil } } }