From f86dd4c273ff48cccfa945ea30cec6634546176c Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Mon, 25 Aug 2025 15:43:17 +0800 Subject: [PATCH] fix --- Tun/PacketTunnelProvider.swift | 5 +- Tun/Punchnet/AESCipher.swift | 13 + Tun/Punchnet/ARPPacket.swift | 125 ++ Tun/Punchnet/ArpServer.swift | 31 + Tun/Punchnet/DataExtension.swift | 39 + Tun/Punchnet/IPPacket.swift | 83 + Tun/Punchnet/LayerPacket.swift | 99 ++ Tun/Punchnet/RSACipher.swift | 13 + Tun/Punchnet/SDLConfiguration.swift | 65 + Tun/Punchnet/SDLContext.swift | 630 +++++++ Tun/Punchnet/SDLError.swift | 11 + Tun/Punchnet/SDLFlowTracerActor.swift | 43 + Tun/Punchnet/SDLLogger.swift | 46 + Tun/Punchnet/SDLMessage.pb.swift | 1482 +++++++++++++++++ Tun/Punchnet/SDLMessage.swift | 156 ++ Tun/Punchnet/SDLNatProber.swift | 77 + Tun/Punchnet/SDLNetAddress.swift | 49 + Tun/Punchnet/SDLNetworkMonitor.swift | 64 + Tun/Punchnet/SDLNoticeClient.swift | 88 + Tun/Punchnet/SDLProtoMessageExtension.swift | 16 + Tun/Punchnet/SDLQPSCounter.swift | 37 + Tun/Punchnet/SDLSuperClient.swift | 326 ++++ Tun/Punchnet/SDLThrottler.swift | 45 + Tun/Punchnet/SDLUDPHole.swift | 282 ++++ Tun/Punchnet/SDLUtil.swift | 52 + Tun/Punchnet/SessionManager.swift | 57 + Tun/Punchnet/UIntExtension.swift | 37 + punchnet.xcodeproj/project.pbxproj | 81 +- .../xcshareddata/swiftpm/Package.resolved | 11 +- punchnet/Core/NoticeMessage.swift | 110 +- punchnet/Core/UDPNoticeCenterServer.swift | 21 +- punchnet/Views/IndexView.swift | 8 +- 32 files changed, 4120 insertions(+), 82 deletions(-) create mode 100644 Tun/Punchnet/AESCipher.swift create mode 100644 Tun/Punchnet/ARPPacket.swift create mode 100644 Tun/Punchnet/ArpServer.swift create mode 100644 Tun/Punchnet/DataExtension.swift create mode 100644 Tun/Punchnet/IPPacket.swift create mode 100644 Tun/Punchnet/LayerPacket.swift create mode 100644 Tun/Punchnet/RSACipher.swift create mode 100644 Tun/Punchnet/SDLConfiguration.swift create mode 100644 Tun/Punchnet/SDLContext.swift create mode 100644 Tun/Punchnet/SDLError.swift create mode 100644 Tun/Punchnet/SDLFlowTracerActor.swift create mode 100644 Tun/Punchnet/SDLLogger.swift create mode 100644 Tun/Punchnet/SDLMessage.pb.swift create mode 100644 Tun/Punchnet/SDLMessage.swift create mode 100644 Tun/Punchnet/SDLNatProber.swift create mode 100644 Tun/Punchnet/SDLNetAddress.swift create mode 100644 Tun/Punchnet/SDLNetworkMonitor.swift create mode 100644 Tun/Punchnet/SDLNoticeClient.swift create mode 100644 Tun/Punchnet/SDLProtoMessageExtension.swift create mode 100644 Tun/Punchnet/SDLQPSCounter.swift create mode 100644 Tun/Punchnet/SDLSuperClient.swift create mode 100644 Tun/Punchnet/SDLThrottler.swift create mode 100644 Tun/Punchnet/SDLUDPHole.swift create mode 100644 Tun/Punchnet/SDLUtil.swift create mode 100644 Tun/Punchnet/SessionManager.swift create mode 100644 Tun/Punchnet/UIntExtension.swift diff --git a/Tun/PacketTunnelProvider.swift b/Tun/PacketTunnelProvider.swift index 8ea5ada..4a55140 100644 --- a/Tun/PacketTunnelProvider.swift +++ b/Tun/PacketTunnelProvider.swift @@ -14,7 +14,6 @@ // import NetworkExtension -import Punchnet class PacketTunnelProvider: NEPacketTunnelProvider { var context: SDLContext? @@ -36,7 +35,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let superIp = options["super_ip"] as! String let superPort = options["super_port"] as! Int let stunServersStr = options["stun_servers"] as! String - + let noticePort = options["notice_port"] as! Int + let stunServers = stunServersStr.split(separator: ";").compactMap { server -> SDLConfiguration.StunServer? in let parts = server.split(separator: ":", maxSplits: 2) guard parts.count == 2 else { @@ -62,6 +62,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { superPort: superPort, stunServers: stunServers, clientId: SDLContext.getUUID(), + noticePort: noticePort, token: "") // 加密算法 let rsaCipher = try! CCRSACipher(keySize: 1024) diff --git a/Tun/Punchnet/AESCipher.swift b/Tun/Punchnet/AESCipher.swift new file mode 100644 index 0000000..ae92578 --- /dev/null +++ b/Tun/Punchnet/AESCipher.swift @@ -0,0 +1,13 @@ +// +// AESCipher.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation + +public protocol AESCipher { + func decypt(aesKey: Data, data: Data) throws -> Data + + func encrypt(aesKey: Data, data: Data) throws -> Data +} diff --git a/Tun/Punchnet/ARPPacket.swift b/Tun/Punchnet/ARPPacket.swift new file mode 100644 index 0000000..fa618d2 --- /dev/null +++ b/Tun/Punchnet/ARPPacket.swift @@ -0,0 +1,125 @@ +// +// ARPPacket.swift +// Tun +// +// Created by 安礼成 on 2024/8/25. +// +import Foundation + +struct ARPPacket: CustomStringConvertible { + var description: String { + return """ +opcode: \(self.opcode), sender_ip: \(SDLUtil.int32ToIp(self.senderIP)), sender_mac: \(SDLUtil.formatMacAddress(mac: senderMAC)), +target_ip: \(SDLUtil.int32ToIp(self.targetIP)), target_mac: \(SDLUtil.formatMacAddress(mac: targetMAC)) +""" + } + + // ARP操作码 + enum Opcode: UInt16 { + case request = 0x01 + case response = 0x02 + + func isRequest() -> Bool { + return self == .request + } + + func isResponse() -> Bool { + return self == .response + } + } + + var hardwareType: UInt16 + var protocolType: UInt16 + var hardwareSize: UInt8 + var protocolSize: UInt8 + var opcode: Opcode + var senderMAC: Data + var senderIP: UInt32 + var targetMAC: Data + var targetIP: UInt32 + + init(hardwareType: UInt16, + protocolType: UInt16, + hardwareSize: UInt8, + protocolSize: UInt8, + opcode: Opcode, + senderMAC: Data, + senderIP: UInt32, + targetMAC: Data, + targetIP: UInt32) { + + self.hardwareType = hardwareType + self.protocolType = protocolType + self.hardwareSize = hardwareSize + self.protocolSize = protocolSize + self.opcode = opcode + self.senderMAC = senderMAC + self.senderIP = senderIP + self.targetMAC = targetMAC + self.targetIP = targetIP + } + + init?(data: Data) { + guard data.count >= 28 else { + return nil + } + + self.hardwareType = UInt16(data[0]) << 8 | UInt16(data[1]) + self.protocolType = UInt16(data[2]) << 8 | UInt16(data[3]) + self.hardwareSize = data[4] + self.protocolSize = data[5] + guard let opcode = Opcode(rawValue: UInt16(data[6]) << 8 | UInt16(data[7])) else { + return nil + } + + self.opcode = opcode + self.senderMAC = Data(data[8..<14]) + self.senderIP = UInt32(data: Data(data[14..<18])) + self.targetMAC = Data(data[18..<24]) + self.targetIP = UInt32(data: Data(data[24..<28])) + } + + func marshal() -> Data { + var data = Data() + data.append(self.hardwareType.data()) + data.append(self.protocolType.data()) + data.append(self.hardwareSize) + data.append(self.protocolSize) + data.append(self.opcode.rawValue.data()) + data.append(self.senderMAC) + data.append(Data(uint32: self.senderIP)) + data.append(self.targetMAC) + data.append(Data(uint32: self.targetIP)) + + return data + } + +} + +extension ARPPacket { + + static func arpRequest(senderIP: UInt32, senderMAC: Data, targetIP: UInt32) -> ARPPacket { + return ARPPacket(hardwareType: 0x01, + protocolType: 0x0800, + hardwareSize: 0x06, + protocolSize: 0x04, + opcode: .request, + senderMAC: senderMAC, + senderIP: senderIP, + targetMAC: Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + targetIP: targetIP) + } + + static func arpResponse(for arp: ARPPacket, mac: Data, ip: UInt32) -> ARPPacket { + return ARPPacket(hardwareType: arp.hardwareType, + protocolType: arp.protocolType, + hardwareSize: arp.hardwareSize, + protocolSize: arp.protocolSize, + opcode: .response, + senderMAC: mac, + senderIP: ip, + targetMAC: arp.senderMAC, + targetIP: arp.senderIP) + } + +} diff --git a/Tun/Punchnet/ArpServer.swift b/Tun/Punchnet/ArpServer.swift new file mode 100644 index 0000000..1963f97 --- /dev/null +++ b/Tun/Punchnet/ArpServer.swift @@ -0,0 +1,31 @@ +// +// ArpServer.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation + +actor ArpServer { + private var known_macs: [UInt32:Data] = [:] + + init(known_macs: [UInt32:Data]) { + self.known_macs = known_macs + } + + func query(ip: UInt32) -> Data? { + return self.known_macs[ip] + } + + func append(ip: UInt32, mac: Data) { + self.known_macs[ip] = mac + } + + func remove(ip: UInt32) { + self.known_macs.removeValue(forKey: ip) + } + + func clear() { + self.known_macs = [:] + } +} diff --git a/Tun/Punchnet/DataExtension.swift b/Tun/Punchnet/DataExtension.swift new file mode 100644 index 0000000..e52843b --- /dev/null +++ b/Tun/Punchnet/DataExtension.swift @@ -0,0 +1,39 @@ +// +// DataExtension.swift +// Tun +// +// Created by 安礼成 on 2024/2/29. +// + +import Foundation + +extension Data { + + init(uint32: UInt32) { + var bytes: [UInt8] = [UInt8](repeating: 0, count: 4) + bytes[0] = (UInt8)(uint32 >> 24 & 0xFF) + bytes[1] = (UInt8)(uint32 >> 16 & 0xFF) + bytes[2] = (UInt8)(uint32 >> 8 & 0xFF) + bytes[3] = (UInt8)(uint32 & 0xFF) + + self.init(bytes) + } + + init(uint16: UInt16) { + var bytes: [UInt8] = [UInt8](repeating: 0, count: 2) + bytes[0] = (UInt8)(uint16 >> 8 & 0xFF) + bytes[1] = (UInt8)(uint16 & 0xFF) + + self.init(bytes) + } + + init(components: Data...) { + var data = Data() + for component in components { + data.append(component) + } + self = data + } + +} + diff --git a/Tun/Punchnet/IPPacket.swift b/Tun/Punchnet/IPPacket.swift new file mode 100644 index 0000000..e136525 --- /dev/null +++ b/Tun/Punchnet/IPPacket.swift @@ -0,0 +1,83 @@ +// +// IPPacket.swift +// Tun +// +// Created by 安礼成 on 2024/1/18. +// + +import Foundation + +struct IPHeader { + let version: UInt8 + let headerLength: UInt8 + let typeOfService: UInt8 + let totalLength: UInt16 + let id: UInt16 + let offset: UInt16 + let timeToLive: UInt8 + let proto:UInt8 + let checksum: UInt16 + let source: UInt32 + let destination: UInt32 + + var source_ip: String { + return intToIp(source) + } + + var destination_ip: String { + return intToIp(destination) + } + + private func intToIp(_ num: UInt32) -> String { + let ip0 = (UInt8) (num >> 24 & 0xFF) + let ip1 = (UInt8) (num >> 16 & 0xFF) + let ip2 = (UInt8) (num >> 8 & 0xFF) + let ip3 = (UInt8) (num & 0xFF) + + return "\(ip0).\(ip1).\(ip2).\(ip3)" + } + + public var description: String { + """ + IPHeader version: \(version), header length: \(headerLength), type of service: \(typeOfService), total length: \(totalLength), + id: \(id), offset: \(offset), time ot live: \(timeToLive), proto: \(proto), checksum: \(checksum), source ip: \(source_ip), destination ip:\(destination_ip) + """ + } +} + +enum IPVersion: UInt8 { + case ipv4 = 4 + case ipv6 = 6 +} + +enum TransportProtocol: UInt8 { + case icmp = 1 + case tcp = 6 + case udp = 17 + +} + +struct IPPacket { + let header: IPHeader + let data: Data + + init?(_ data: Data) { + guard data.count >= 20 else { + return nil + } + + self.header = IPHeader(version: data[0] >> 4, + headerLength: (data[0] & 0b1111) * 4, + typeOfService: data[1], + totalLength: UInt16(bytes: (data[2], data[3])), + id: UInt16(bytes: (data[4], data[5])), + offset: 1, + timeToLive: data[8], + proto: data[9], + checksum: UInt16(bytes: (data[10], data[11])), + source: UInt32(bytes: (data[12], data[13], data[14], data[15])), + destination: UInt32(bytes: (data[16], data[17], data[18], data[19]))) + self.data = data + } + +} diff --git a/Tun/Punchnet/LayerPacket.swift b/Tun/Punchnet/LayerPacket.swift new file mode 100644 index 0000000..5657f52 --- /dev/null +++ b/Tun/Punchnet/LayerPacket.swift @@ -0,0 +1,99 @@ +// +// LayerPacket.swift +// Tun +// +// Created by 安礼成 on 2024/8/21. +// + +import Foundation +import zlib + +struct LayerPacket { + // 数据包类型 + enum PacketType: UInt16 { + case arp = 0x0806 + case ipv4 = 0x0800 + case ipv6 = 0x86DD + case taggedFrame = 0x8100 + } + + enum LayerPacketError: Error { + case invalidLength + case invaldPacketType + } + + struct MacAddress { + let data: Data + + init(data: Data) { + self.data = data + } + + func isBroadcast() -> Bool { + return data.count == 6 && self.data.allSatisfy { $0 == 0xFF} + } + + func isMulticast() -> Bool { + return data.count == 6 && (data[0] == 0x01 && data[1] == 0x00 && data[2] == 0x5E) + } + + func format() -> String { + // 将mac地址转换成字符串 + let bytes = [UInt8](data) + return bytes.map { String(format: "%02X", $0) }.joined(separator: ":").lowercased() + } + + static func description(data: Data) -> String { + // 将mac地址转换成字符串 + let bytes = [UInt8](data) + return bytes.map { String(format: "%02X", $0) }.joined(separator: ":").lowercased() + } + + } + + let dstMac: Data + let srcMac: Data + let type: PacketType + let data: Data + + init(dstMac: Data, srcMac: Data, type: PacketType, data: Data) { + self.dstMac = dstMac + self.srcMac = srcMac + self.type = type + self.data = data + } + + init(layerData playload: Data) throws { + guard playload.count >= 14 else { + throw LayerPacketError.invalidLength + } + + self.dstMac = Data(playload[0..<6]) + self.srcMac = Data(playload[6..<12]) + guard let type = PacketType(rawValue: UInt16(bytes: (playload[12], playload[13]))) else { + throw LayerPacketError.invaldPacketType + } + + self.type = type + self.data = Data(playload[14...]) + } + + func marshal() -> Data { + var packet = Data() + packet.append(dstMac) + packet.append(srcMac) + packet.append(self.type.rawValue.data()) + packet.append(self.data) + + return packet + } + + private static func crc32(data: Data) -> UInt32 { + let crc = data.withUnsafeBytes { buffer in + return zlib.crc32(0, buffer.bindMemory(to: UInt8.self).baseAddress, uInt(buffer.count)) + } + + return UInt32(crc) + } + +} diff --git a/Tun/Punchnet/RSACipher.swift b/Tun/Punchnet/RSACipher.swift new file mode 100644 index 0000000..8bd4235 --- /dev/null +++ b/Tun/Punchnet/RSACipher.swift @@ -0,0 +1,13 @@ +// +// RSACipher.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation + +public protocol RSACipher { + var pubKey: String {get set} + + func decode(data: Data) throws -> Data +} diff --git a/Tun/Punchnet/SDLConfiguration.swift b/Tun/Punchnet/SDLConfiguration.swift new file mode 100644 index 0000000..5c2df3c --- /dev/null +++ b/Tun/Punchnet/SDLConfiguration.swift @@ -0,0 +1,65 @@ +// +// SDLConfiguration.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation +import NIOCore + +// 配置项目 +public class SDLConfiguration { + + public struct StunServer { + public let host: String + public let ports: [Int] + + public init(host: String, ports: [Int]) { + self.host = host + self.ports = ports + } + } + + // 当前的客户端版本 + let version: UInt8 + + // 安装渠道 + let installedChannel: String + + let superHost: String + let superPort: Int + + let stunServers: [StunServer] + + let noticePort: Int + + lazy var stunSocketAddress: SocketAddress = { + let stunServer = stunServers[0] + return try! SocketAddress.makeAddressResolvingHost(stunServer.host, port: stunServer.ports[0]) + }() + + // 网络探测地址信息 + lazy var stunProbeSocketAddressArray: [[SocketAddress]] = { + return stunServers.map { stunServer in + [ + try! SocketAddress.makeAddressResolvingHost(stunServer.host, port: stunServer.ports[0]), + try! SocketAddress.makeAddressResolvingHost(stunServer.host, port: stunServer.ports[1]) + ] + } + }() + + let clientId: String + let token: String + + public init(version: UInt8, installedChannel: String, superHost: String, superPort: Int, stunServers: [StunServer], clientId: String, noticePort: Int, token: String) { + self.version = version + self.installedChannel = installedChannel + self.superHost = superHost + self.superPort = superPort + self.stunServers = stunServers + self.clientId = clientId + self.noticePort = noticePort + self.token = token + } + +} diff --git a/Tun/Punchnet/SDLContext.swift b/Tun/Punchnet/SDLContext.swift new file mode 100644 index 0000000..5a00c68 --- /dev/null +++ b/Tun/Punchnet/SDLContext.swift @@ -0,0 +1,630 @@ +// +// SDLContext.swift +// Tun +// +// Created by 安礼成 on 2024/2/29. +// + +import Foundation +import NetworkExtension +import NIOCore +import Combine + +// 上下文环境变量,全局共享 +/* +1. 处理rsa的加解密逻辑 + */ + +@available(macOS 14, *) +public class SDLContext: @unchecked Sendable { + + // 路由信息 + struct Route { + let dstAddress: String + let subnetMask: String + + var debugInfo: String { + return "\(dstAddress):\(subnetMask)" + } + } + + let config: SDLConfiguration + + // tun网络地址信息 + var devAddr: SDLDevAddr + + // nat映射的相关信息, 暂时没有用处 + //var natAddress: SDLNatAddress? + // nat的网络类型 + var natType: SDLNatProber.NatType = .blocked + + // AES加密,授权通过后,对象才会被创建 + var aesCipher: AESCipher + + // aes + var aesKey: Data = Data() + + // rsa的相关配置, public_key是本地生成的 + let rsaCipher: RSACipher + + // 依赖的变量 + var udpHole: SDLUDPHole? + var superClient: SDLSuperClient? + + // 数据包读取任务 + private var readTask: Task<(), Never>? + + let provider: NEPacketTunnelProvider + + private var sessionManager: SessionManager + private var arpServer: ArpServer + + // 记录最后发送的stunRequest的cookie + private var lastCookie: UInt32? = 0 + + // 网络状态变化的健康 + private var monitor: SDLNetworkMonitor? + + // 内部socket通讯 + private var noticeClient: SDLNoticeClient? + + // 流量统计 + private var flowTracer = SDLFlowTracerActor() + private var flowTracerCancel: AnyCancellable? + + // 处理holer + private var holerPublishers: [Data:PassthroughSubject] = [:] + private var bag = Set() + private var locker = NSLock() + + private let logger: SDLLogger + + private var rootTask: Task? + + struct RegisterRequest { + let srcMac: Data + let dstMac: Data + let networkId: UInt32 + } + + public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher, logger: SDLLogger) { + self.logger = logger + + self.config = config + self.rsaCipher = rsaCipher + self.aesCipher = aesCipher + + // 生成mac地址 + var devAddr = SDLDevAddr() + devAddr.mac = Self.getMacAddress() + self.devAddr = devAddr + + self.provider = provider + self.sessionManager = SessionManager() + self.arpServer = ArpServer(known_macs: [:]) + } + + public func start() async throws { + self.rootTask = Task { + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + while !Task.isCancelled { + do { + try await self.startUDPHole() + } catch let err { + self.logger.log("[SDLContext] UDPHole get err: \(err)", level: .warning) + try await Task.sleep(for: .seconds(2)) + } + } + } + + group.addTask { + while !Task.isCancelled { + do { + try await self.startSuperClient() + } catch let err { + self.logger.log("[SDLContext] SuperClient get error: \(err), will restart", level: .warning) + await self.arpServer.clear() + try await Task.sleep(for: .seconds(2)) + } + } + } + + group.addTask { + await self.startMonitor() + } + + group.addTask { + while !Task.isCancelled { + do { + try await self.startNoticeClient() + } catch let err { + self.logger.log("[SDLContext] noticeClient get err: \(err)", level: .warning) + try await Task.sleep(for: .seconds(2)) + } + } + } + + try await group.waitForAll() + } + } + + try await self.rootTask?.value + } + + public func stop() async { + self.rootTask?.cancel() + self.superClient = nil + self.udpHole = nil + self.noticeClient = nil + + self.readTask?.cancel() + } + + private func startNoticeClient() async throws { + self.noticeClient = try await SDLNoticeClient(noticePort: self.config.noticePort, logger: self.logger) + try await self.noticeClient?.start() + self.logger.log("[SDLContext] notice_client task cancel", level: .warning) + } + + private func startUDPHole() async throws { + self.udpHole = try await SDLUDPHole(logger: self.logger) + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + try await self.udpHole?.start() + } + + group.addTask { + while !Task.isCancelled { + try await Task.sleep(nanoseconds: 5 * 1_000_000_000) + self.lastCookie = await self.udpHole?.stunRequest(context: self) + } + } + + group.addTask { + if let eventFlow = self.udpHole?.eventFlow { + for try await event in eventFlow { + try await self.handleUDPEvent(event: event) + } + } + } + + if let _ = try await group.next() { + group.cancelAll() + } + } + + } + + private func startSuperClient() async throws { + self.superClient = try await SDLSuperClient(host: self.config.superHost, port: self.config.superPort, logger: self.logger) + try await withThrowingTaskGroup(of: Void.self) { group in + defer { + self.logger.log("[SDLContext] super client task cancel", level: .warning) + } + + group.addTask { + try await self.superClient?.start() + } + + group.addTask { + if let eventFlow = self.superClient?.eventFlow { + for try await event in eventFlow { + try await self.handleSuperEvent(event: event) + } + } + } + + if let _ = try await group.next() { + group.cancelAll() + } + } + } + + private func startMonitor() async { + self.monitor = SDLNetworkMonitor() + for await event in self.monitor!.eventStream { + switch event { + case .changed: + // 需要重新探测网络的nat类型 + self.natType = await SDLNatProber.getNatType(udpHole: self.udpHole, config: self.config, logger: self.logger) + self.logger.log("didNetworkPathChanged, nat type is: \(self.natType)", level: .info) + case .unreachable: + self.logger.log("didNetworkPathUnreachable", level: .warning) + } + } + } + + private func handleSuperEvent(event: SDLSuperClient.SuperEvent) async throws { + switch event { + case .ready: + self.logger.log("[SDLContext] get registerSuper, mac address: \(SDLUtil.formatMacAddress(mac: self.devAddr.mac))", level: .debug) + guard let message = try await self.superClient?.registerSuper(context: self).get() else { + return + } + + switch message.packet { + case .registerSuperAck(let registerSuperAck): + // 需要对数据通过rsa的私钥解码 + let aesKey = try! self.rsaCipher.decode(data: Data(registerSuperAck.aesKey)) + let upgradeType = SDLUpgradeType(rawValue: registerSuperAck.upgradeType) + + self.logger.log("[SDLContext] get registerSuperAck, aes_key len: \(aesKey.count)", level: .info) + self.devAddr = registerSuperAck.devAddr + + if upgradeType == .force { + let forceUpgrade = NoticeMessage.upgrade(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress) + await self.noticeClient?.send(data: forceUpgrade) + exit(-1) + } + + // 服务器分配的tun网卡信息 + await self.didNetworkConfigChanged(devAddr: self.devAddr) + self.aesKey = aesKey + + if upgradeType == .normal { + let normalUpgrade = NoticeMessage.upgrade(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress) + await self.noticeClient?.send(data: normalUpgrade) + } + + case .registerSuperNak(let nakPacket): + let errorMessage = nakPacket.errorMessage + guard let errorCode = SDLNAKErrorCode(rawValue: UInt8(nakPacket.errorCode)) else { + return + } + + switch errorCode { + case .invalidToken, .nodeDisabled: + let alertNotice = NoticeMessage.alert(alert: errorMessage) + await self.noticeClient?.send(data: alertNotice) + exit(-1) + case .noIpAddress, .networkFault, .internalFault: + let alertNotice = NoticeMessage.alert(alert: errorMessage) + await self.noticeClient?.send(data: alertNotice) + } + self.logger.log("[SDLContext] Get a SuperNak message exit", level: .warning) + default: + () + } + + case .event(let evt): + switch evt { + case .natChanged(let natChangedEvent): + let dstMac = natChangedEvent.mac + self.logger.log("[SDLContext] natChangedEvent, dstMac: \(dstMac)", level: .info) + await sessionManager.removeSession(dstMac: dstMac) + case .sendRegister(let sendRegisterEvent): + self.logger.log("[SDLContext] sendRegisterEvent, ip: \(sendRegisterEvent)", level: .debug) + let address = SDLUtil.int32ToIp(sendRegisterEvent.natIp) + if let remoteAddress = try? SocketAddress.makeAddressResolvingHost(address, port: Int(sendRegisterEvent.natPort)) { + // 发送register包 + await self.udpHole?.sendRegister(remoteAddress: remoteAddress, networkId: self.devAddr.networkID, srcMac: self.devAddr.mac, dst_mac: sendRegisterEvent.dstMac) + } + + case .networkShutdown(let shutdownEvent): + let alertNotice = NoticeMessage.alert(alert: shutdownEvent.message) + await self.noticeClient?.send(data: alertNotice) + exit(-1) + } + case .command(let packetId, let command): + switch command { + case .changeNetwork(let changeNetworkCommand): + // 需要对数据通过rsa的私钥解码 + let aesKey = try! self.rsaCipher.decode(data: Data(changeNetworkCommand.aesKey)) + self.logger.log("[SDLContext] change network command get aes_key len: \(aesKey.count)", level: .info) + self.devAddr = changeNetworkCommand.devAddr + + // 服务器分配的tun网卡信息 + await self.didNetworkConfigChanged(devAddr: self.devAddr) + self.aesKey = aesKey + + var commandAck = SDLCommandAck() + commandAck.status = true + + await self.superClient?.commandAck(packetId: packetId, ack: commandAck) + } + } + + } + + private func handleUDPEvent(event: SDLUDPHole.UDPEvent) async throws { + switch event { + case .ready: + // 获取当前网络的类型 + //self.natType = await SDLNatProber.getNatType(udpHole: self.udpHole, config: self.config) + self.logger.log("[SDLContext] nat type is: \(self.natType)", level: .debug) + + case .message(let remoteAddress, let message): + switch message { + case .register(let register): + self.logger.log("register packet: \(register), dev_addr: \(self.devAddr)", level: .debug) + // 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下 + if register.dstMac == self.devAddr.mac && register.networkID == self.devAddr.networkID { + // 回复ack包 + await self.udpHole?.sendRegisterAck(context: self, remoteAddress: remoteAddress, dst_mac: register.srcMac) + // 这里需要建立到来源的会话, 在复杂网络下,通过super-node查询到的nat地址不一定靠谱,需要通过udp包的来源地址作为nat地址 + let session = Session(dstMac: register.srcMac, natAddress: remoteAddress) + await self.sessionManager.addSession(session: session) + } else { + self.logger.log("SDLContext didReadRegister get a invalid packet, because dst_ip not matched: \(register.dstMac)", level: .warning) + } + case .registerAck(let registerAck): + // 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下 + if registerAck.dstMac == self.devAddr.mac && registerAck.networkID == self.devAddr.networkID { + let session = Session(dstMac: registerAck.srcMac, natAddress: remoteAddress) + await self.sessionManager.addSession(session: session) + } else { + self.logger.log("SDLContext didReadRegisterAck get a invalid packet, because dst_mac not matched: \(registerAck.dstMac)", level: .warning) + } + case .stunReply(let stunReply): + let cookie = stunReply.cookie + if cookie == self.lastCookie { + // 记录下当前在nat上的映射信息,暂时没有用;后续会用来判断网络类型 + //self.natAddress = stunReply.natAddress + self.logger.log("[SDLContext] get a stunReply: \(try! stunReply.jsonString())", level: .debug) + } + default: + () + } + + case .data(let data): + let mac = LayerPacket.MacAddress(data: data.dstMac) + guard (data.dstMac == self.devAddr.mac || mac.isBroadcast() || mac.isMulticast()) else { + return + } + + guard let decyptedData = try? self.aesCipher.decypt(aesKey: self.aesKey, data: Data(data.data)) else { + return + } + + do { + let layerPacket = try LayerPacket(layerData: decyptedData) + + await self.flowTracer.inc(num: decyptedData.count, type: .inbound) + // 处理arp请求 + switch layerPacket.type { + case .arp: + // 判断如果收到的是arp请求 + if let arpPacket = ARPPacket(data: layerPacket.data) { + if arpPacket.targetIP == self.devAddr.netAddr { + switch arpPacket.opcode { + case .request: + self.logger.log("[SDLContext] get arp request packet", level: .debug) + let response = ARPPacket.arpResponse(for: arpPacket, mac: self.devAddr.mac, ip: self.devAddr.netAddr) + await self.routeLayerPacket(dstMac: arpPacket.senderMAC, type: .arp, data: response.marshal()) + case .response: + self.logger.log("[SDLContext] get arp response packet", level: .debug) + await self.arpServer.append(ip: arpPacket.senderIP, mac: arpPacket.senderMAC) + } + } else { + self.logger.log("[SDLContext] get invalid arp packet: \(arpPacket), target_ip: \(SDLUtil.int32ToIp(arpPacket.targetIP)), net ip: \(SDLUtil.int32ToIp(self.devAddr.netAddr))", level: .debug) + } + } else { + self.logger.log("[SDLContext] get invalid arp packet", level: .debug) + } + case .ipv4: + guard let ipPacket = IPPacket(layerPacket.data), ipPacket.header.destination == self.devAddr.netAddr else { + return + } + let packet = NEPacket(data: ipPacket.data, protocolFamily: 2) + self.provider.packetFlow.writePacketObjects([packet]) + default: + self.logger.log("[SDLContext] get invalid packet", level: .debug) + } + } catch let err { + self.logger.log("[SDLContext] didReadData err: \(err)", level: .warning) + } + } + + } + + // 流量统计 +// public func flowReportTask() { +// Task { +// // 每分钟汇报一次 +// self.flowTracerCancel = Timer.publish(every: 60.0, on: .main, in: .common).autoconnect() +// .sink { _ in +// Task { +// let (forwardNum, p2pNum, inboundNum) = await self.flowTracer.reset() +// await self.superClient?.flowReport(forwardNum: forwardNum, p2pNum: p2pNum, inboundNum: inboundNum) +// } +// } +// } +// } + + // 网络改变时需要重新配置网络信息 + private func didNetworkConfigChanged(devAddr: SDLDevAddr, dnsServers: [String]? = nil) async { + let netAddress = SDLNetAddress(ip: devAddr.netAddr, maskLen: UInt8(devAddr.netBitLen)) + let routes = [Route(dstAddress: netAddress.networkAddress, subnetMask: netAddress.maskAddress)] + + // Add code here to start the process of connecting the tunnel. + let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "8.8.8.8") + networkSettings.mtu = 1460 + + // 设置网卡的DNS解析 + if let dnsServers { + networkSettings.dnsSettings = NEDNSSettings(servers: dnsServers) + } else { + networkSettings.dnsSettings = NEDNSSettings(servers: ["8.8.8.8", "114.114.114.114"]) + } + + self.logger.log("[SDLContext] Tun started at network ip: \(netAddress.ipAddress), mask: \(netAddress.maskAddress)", level: .info) + + let ipv4Settings = NEIPv4Settings(addresses: [netAddress.ipAddress], subnetMasks: [netAddress.maskAddress]) + // 设置路由表 + //NEIPv4Route.default() + ipv4Settings.includedRoutes = routes.map { route in + NEIPv4Route(destinationAddress: route.dstAddress, subnetMask: route.subnetMask) + } + networkSettings.ipv4Settings = ipv4Settings + // 网卡配置设置必须成功 + do { + try await self.provider.setTunnelNetworkSettings(networkSettings) + self.startReader() + + let ipMessage = NoticeMessage.ipAdress(ip: netAddress.ipAddress) + await self.noticeClient?.send(data: ipMessage) + + self.logger.log("[SDLContext] setTunnelNetworkSettings success, start read packet", level: .info) + } catch let err { + self.logger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error) + exit(-1) + } + } + + // 开始读取数据, 用单独的线程处理packetFlow + private func startReader() { + // 停止之前的任务 + self.readTask?.cancel() + + // 开启新的任务 + self.readTask = Task(priority: .high) { + repeat { + let (packets, numbers) = await self.provider.packetFlow.readPackets() + for (data, number) in zip(packets, numbers) where number == 2 { + if let packet = IPPacket(data) { + Task.detached { + let dstIp = packet.header.destination + // 本地通讯, 目标地址是本地服务器的ip地址 + if dstIp == self.devAddr.netAddr { + let nePacket = NEPacket(data: packet.data, protocolFamily: 2) + self.provider.packetFlow.writePacketObjects([nePacket]) + return + } + + // 查找arp缓存中是否有目标mac地址 + if let dstMac = await self.arpServer.query(ip: dstIp) { + await self.routeLayerPacket(dstMac: dstMac, type: .ipv4, data: packet.data) + } + else { + // 构造arp请求 + let broadcastMac = Data([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + let arpReqeust = ARPPacket.arpRequest(senderIP: self.devAddr.netAddr, senderMAC: self.devAddr.mac, targetIP: dstIp) + await self.routeLayerPacket(dstMac: broadcastMac, type: .arp, data: arpReqeust.marshal()) + + self.logger.log("[SDLContext] dstIp: \(dstIp) arp query not found", level: .debug) + } + } + } + } + + } while true + } + + } + + private func routeLayerPacket(dstMac: Data, type: LayerPacket.PacketType, data: Data) async { + // 将数据封装层2层的数据包 + let layerPacket = LayerPacket(dstMac: dstMac, srcMac: self.devAddr.mac, type: type, data: data) + guard let encodedPacket = try? self.aesCipher.encrypt(aesKey: self.aesKey, data: layerPacket.marshal()) else { + return + } + + // 通过session发送到对端 + if let session = await self.sessionManager.getSession(toAddress: dstMac) { + self.logger.log("[SDLContext] send packet by session: \(session)", level: .debug) + await self.udpHole?.sendPacket(context: self, session: session, data: encodedPacket) + + await self.flowTracer.inc(num: data.count, type: .p2p) + } + else { + // 通过super_node进行转发 + await self.udpHole?.forwardPacket(context: self, dst_mac: dstMac, data: encodedPacket) + // 流量统计 + await self.flowTracer.inc(num: data.count, type: .forward) + + // 尝试打洞 + let registerRequest = RegisterRequest(srcMac: self.devAddr.mac, dstMac: dstMac, networkId: self.devAddr.networkID) + self.submitRegisterRequest(request: registerRequest) + } + } + + private func submitRegisterRequest(request: RegisterRequest) { + self.locker.lock() + defer { + self.locker.unlock() + } + + let dstMac = request.dstMac + if let publisher = self.holerPublishers[dstMac] { + publisher.send(request) + } else { + let publisher = PassthroughSubject() + publisher.throttle(for: .seconds(5), scheduler: DispatchQueue.global(), latest: true) + .sink { request in + Task { + await self.tryHole(request: request) + } + } + .store(in: &self.bag) + + self.holerPublishers[dstMac] = publisher + } + } + + private func tryHole(request: RegisterRequest) async { + guard let message = try? await self.superClient?.queryInfo(dst_mac: request.dstMac).get() else { + return + } + + switch message.packet { + case .empty: + self.logger.log("[SDLContext] hole query_info get empty: \(message)", level: .debug) + case .peerInfo(let peerInfo): + if let remoteAddress = peerInfo.v4Info.socketAddress() { + self.logger.log("[SDLContext] hole sock address: \(remoteAddress)", level: .debug) + // 发送register包 + await self.udpHole?.sendRegister(remoteAddress: remoteAddress, networkId: request.networkId, srcMac: request.srcMac, dst_mac: request.dstMac) + } else { + self.logger.log("[SDLContext] hole sock address is invalid: \(peerInfo.v4Info)", level: .warning) + } + default: + self.logger.log("[SDLContext] hole query_info is packet: \(message)", level: .warning) + } + } + + deinit { + self.rootTask?.cancel() + self.udpHole = nil + self.superClient = nil + } + + public static func getUUID() -> String { + let userDefaults = UserDefaults.standard + if let uuid = userDefaults.value(forKey: "gClientId") as? String { + return uuid + } else { + let uuid = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased() + userDefaults.setValue(uuid, forKey: "gClientId") + + return uuid + } + } + + // 获取mac地址 + public static func getMacAddress() -> Data { + let key = "gMacAddress2" + + let userDefaults = UserDefaults.standard + if let mac = userDefaults.value(forKey: key) as? Data { + return mac + } + else { + let mac = generateMacAddress() + userDefaults.setValue(mac, forKey: key) + + return mac + } + } + + // 随机生成mac地址 + private static func generateMacAddress() -> Data { + var macAddress = [UInt8](repeating: 0, count: 6) + for i in 0..<6 { + macAddress[i] = UInt8.random(in: 0...255) + } + + return Data(macAddress) + } + +} diff --git a/Tun/Punchnet/SDLError.swift b/Tun/Punchnet/SDLError.swift new file mode 100644 index 0000000..3c95aac --- /dev/null +++ b/Tun/Punchnet/SDLError.swift @@ -0,0 +1,11 @@ +// +// SDLError.swift +// sdlan +// +// Created by 安礼成 on 2025/8/2. +// + +enum SDLError: Error { + case socketClosed + case socketError +} diff --git a/Tun/Punchnet/SDLFlowTracerActor.swift b/Tun/Punchnet/SDLFlowTracerActor.swift new file mode 100644 index 0000000..0b0afbe --- /dev/null +++ b/Tun/Punchnet/SDLFlowTracerActor.swift @@ -0,0 +1,43 @@ +// +// SDLFlowTracer.swift +// Tun +// +// Created by 安礼成 on 2024/5/27. +// + +import Foundation + +// 流量统计器 +actor SDLFlowTracerActor { + enum FlowType { + case forward + case p2p + case inbound + } + + private var forwardFlowBytes: UInt32 = 0 + private var p2pFlowBytes: UInt32 = 0 + private var inFlowBytes: UInt32 = 0 + + func inc(num: Int, type: FlowType) { + switch type { + case .inbound: + self.inFlowBytes += UInt32(num) + case .forward: + self.forwardFlowBytes += UInt32(num) + case .p2p: + self.p2pFlowBytes += UInt32(num) + } + } + + func reset() -> (UInt32, UInt32, UInt32) { + defer { + self.forwardFlowBytes = 0 + self.inFlowBytes = 0 + self.p2pFlowBytes = 0 + } + + return (forwardFlowBytes, p2pFlowBytes, inFlowBytes) + } + +} diff --git a/Tun/Punchnet/SDLLogger.swift b/Tun/Punchnet/SDLLogger.swift new file mode 100644 index 0000000..7e21bf3 --- /dev/null +++ b/Tun/Punchnet/SDLLogger.swift @@ -0,0 +1,46 @@ +// +// SDLLogger.swift +// Tun +// +// Created by 安礼成 on 2024/3/13. +// +import Foundation +import os.log + +public class SDLLogger: @unchecked Sendable { + public enum Level: Int8, CustomStringConvertible { + case debug = 0 + case info = 1 + case warning = 2 + case error = 3 + + public var description: String { + switch self { + case .debug: + return "Debug" + case .info: + return "Info" + case .warning: + return "Warning" + case .error: + return "Error" + } + } + } + + private let level: Level + private let log: OSLog + + public init(level: Level) { + self.level = level + self.log = OSLog(subsystem: "com.jihe.punchnet", category: "punchnet") + } + + public func log(_ message: String, level: Level = .debug) { + if self.level.rawValue <= level.rawValue { + //os_log("%{public}@: %{public}@", log: self.log, type: .debug, level.description, message) + NSLog("\(level.description): \(message)") + } + } + +} diff --git a/Tun/Punchnet/SDLMessage.pb.swift b/Tun/Punchnet/SDLMessage.pb.swift new file mode 100644 index 0000000..60cfbd7 --- /dev/null +++ b/Tun/Punchnet/SDLMessage.pb.swift @@ -0,0 +1,1482 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: sdlan_pb.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct SDLV4Info { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var port: UInt32 = 0 + + var v4: Data = Data() + + var natType: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLV6Info { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var port: UInt32 = 0 + + var v6: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// 设备网络地址信息 +struct SDLDevAddr { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var networkID: UInt32 = 0 + + var mac: Data = Data() + + var netAddr: UInt32 = 0 + + var netBitLen: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// tcp通讯消息 +struct SDLEmpty { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLRegisterSuper { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var version: UInt32 = 0 + + var installedChannel: String = String() + + var clientID: String = String() + + var devAddr: SDLDevAddr { + get {return _devAddr ?? SDLDevAddr()} + set {_devAddr = newValue} + } + /// Returns true if `devAddr` has been explicitly set. + var hasDevAddr: Bool {return self._devAddr != nil} + /// Clears the value of `devAddr`. Subsequent reads from it will return its default value. + mutating func clearDevAddr() {self._devAddr = nil} + + var pubKey: String = String() + + var token: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _devAddr: SDLDevAddr? = nil +} + +struct SDLRegisterSuperAck { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var devAddr: SDLDevAddr { + get {return _devAddr ?? SDLDevAddr()} + set {_devAddr = newValue} + } + /// Returns true if `devAddr` has been explicitly set. + var hasDevAddr: Bool {return self._devAddr != nil} + /// Clears the value of `devAddr`. Subsequent reads from it will return its default value. + mutating func clearDevAddr() {self._devAddr = nil} + + var aesKey: Data = Data() + + var upgradeType: UInt32 = 0 + + var upgradePrompt: String { + get {return _upgradePrompt ?? String()} + set {_upgradePrompt = newValue} + } + /// Returns true if `upgradePrompt` has been explicitly set. + var hasUpgradePrompt: Bool {return self._upgradePrompt != nil} + /// Clears the value of `upgradePrompt`. Subsequent reads from it will return its default value. + mutating func clearUpgradePrompt() {self._upgradePrompt = nil} + + var upgradeAddress: String { + get {return _upgradeAddress ?? String()} + set {_upgradeAddress = newValue} + } + /// Returns true if `upgradeAddress` has been explicitly set. + var hasUpgradeAddress: Bool {return self._upgradeAddress != nil} + /// Clears the value of `upgradeAddress`. Subsequent reads from it will return its default value. + mutating func clearUpgradeAddress() {self._upgradeAddress = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _devAddr: SDLDevAddr? = nil + fileprivate var _upgradePrompt: String? = nil + fileprivate var _upgradeAddress: String? = nil +} + +struct SDLRegisterSuperNak { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var errorCode: UInt32 = 0 + + var errorMessage: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLQueryInfo { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var dstMac: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLPeerInfo { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var dstMac: Data = Data() + + var v4Info: SDLV4Info { + get {return _v4Info ?? SDLV4Info()} + set {_v4Info = newValue} + } + /// Returns true if `v4Info` has been explicitly set. + var hasV4Info: Bool {return self._v4Info != nil} + /// Clears the value of `v4Info`. Subsequent reads from it will return its default value. + mutating func clearV4Info() {self._v4Info = nil} + + var natType: UInt32 = 0 + + var v6Info: SDLV6Info { + get {return _v6Info ?? SDLV6Info()} + set {_v6Info = newValue} + } + /// Returns true if `v6Info` has been explicitly set. + var hasV6Info: Bool {return self._v6Info != nil} + /// Clears the value of `v6Info`. Subsequent reads from it will return its default value. + mutating func clearV6Info() {self._v6Info = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _v4Info: SDLV4Info? = nil + fileprivate var _v6Info: SDLV6Info? = nil +} + +struct SDLNatChangedEvent { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var mac: Data = Data() + + var ip: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLSendRegisterEvent { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var dstMac: Data = Data() + + var natIp: UInt32 = 0 + + var natPort: UInt32 = 0 + + var v6Info: SDLV6Info { + get {return _v6Info ?? SDLV6Info()} + set {_v6Info = newValue} + } + /// Returns true if `v6Info` has been explicitly set. + var hasV6Info: Bool {return self._v6Info != nil} + /// Clears the value of `v6Info`. Subsequent reads from it will return its default value. + mutating func clearV6Info() {self._v6Info = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _v6Info: SDLV6Info? = nil +} + +struct SDLNetworkShutdownEvent { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var message: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLChangeNetworkCommand { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var devAddr: SDLDevAddr { + get {return _devAddr ?? SDLDevAddr()} + set {_devAddr = newValue} + } + /// Returns true if `devAddr` has been explicitly set. + var hasDevAddr: Bool {return self._devAddr != nil} + /// Clears the value of `devAddr`. Subsequent reads from it will return its default value. + mutating func clearDevAddr() {self._devAddr = nil} + + var aesKey: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _devAddr: SDLDevAddr? = nil +} + +struct SDLCommandAck { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// status = true, 表示成功;status = false 表示失败,message是失败原因描述 + var status: Bool = false + + var message: String { + get {return _message ?? String()} + set {_message = newValue} + } + /// Returns true if `message` has been explicitly set. + var hasMessage: Bool {return self._message != nil} + /// Clears the value of `message`. Subsequent reads from it will return its default value. + mutating func clearMessage() {self._message = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _message: String? = nil +} + +struct SDLFlows { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// 服务器转发流量 + var forwardNum: UInt32 = 0 + + /// p2p直接流量 + var p2PNum: UInt32 = 0 + + /// 接收的流量 + var inboundNum: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLStunRequest { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var cookie: UInt32 = 0 + + var clientID: String = String() + + var networkID: UInt32 = 0 + + var mac: Data = Data() + + var ip: UInt32 = 0 + + var natType: UInt32 = 0 + + var v6Info: SDLV6Info { + get {return _v6Info ?? SDLV6Info()} + set {_v6Info = newValue} + } + /// Returns true if `v6Info` has been explicitly set. + var hasV6Info: Bool {return self._v6Info != nil} + /// Clears the value of `v6Info`. Subsequent reads from it will return its default value. + mutating func clearV6Info() {self._v6Info = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _v6Info: SDLV6Info? = nil +} + +struct SDLStunReply { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var cookie: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLData { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var networkID: UInt32 = 0 + + var srcMac: Data = Data() + + var dstMac: Data = Data() + + var isP2P: Bool = false + + var ttl: UInt32 = 0 + + var data: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLRegister { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var networkID: UInt32 = 0 + + var srcMac: Data = Data() + + var dstMac: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLRegisterAck { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var networkID: UInt32 = 0 + + var srcMac: Data = Data() + + var dstMac: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLStunProbe { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var cookie: UInt32 = 0 + + var attr: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLStunProbeReply { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var cookie: UInt32 = 0 + + var port: UInt32 = 0 + + var ip: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +#if swift(>=5.5) && canImport(_Concurrency) +extension SDLV4Info: @unchecked Sendable {} +extension SDLV6Info: @unchecked Sendable {} +extension SDLDevAddr: @unchecked Sendable {} +extension SDLEmpty: @unchecked Sendable {} +extension SDLRegisterSuper: @unchecked Sendable {} +extension SDLRegisterSuperAck: @unchecked Sendable {} +extension SDLRegisterSuperNak: @unchecked Sendable {} +extension SDLQueryInfo: @unchecked Sendable {} +extension SDLPeerInfo: @unchecked Sendable {} +extension SDLNatChangedEvent: @unchecked Sendable {} +extension SDLSendRegisterEvent: @unchecked Sendable {} +extension SDLNetworkShutdownEvent: @unchecked Sendable {} +extension SDLChangeNetworkCommand: @unchecked Sendable {} +extension SDLCommandAck: @unchecked Sendable {} +extension SDLFlows: @unchecked Sendable {} +extension SDLStunRequest: @unchecked Sendable {} +extension SDLStunReply: @unchecked Sendable {} +extension SDLData: @unchecked Sendable {} +extension SDLRegister: @unchecked Sendable {} +extension SDLRegisterAck: @unchecked Sendable {} +extension SDLStunProbe: @unchecked Sendable {} +extension SDLStunProbeReply: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension SDLV4Info: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLV4Info" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "port"), + 2: .same(proto: "v4"), + 3: .standard(proto: "nat_type"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.port) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.v4) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.natType) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.port != 0 { + try visitor.visitSingularUInt32Field(value: self.port, fieldNumber: 1) + } + if !self.v4.isEmpty { + try visitor.visitSingularBytesField(value: self.v4, fieldNumber: 2) + } + if self.natType != 0 { + try visitor.visitSingularUInt32Field(value: self.natType, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLV4Info, rhs: SDLV4Info) -> Bool { + if lhs.port != rhs.port {return false} + if lhs.v4 != rhs.v4 {return false} + if lhs.natType != rhs.natType {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLV6Info: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLV6Info" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "port"), + 2: .same(proto: "v6"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.port) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.v6) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.port != 0 { + try visitor.visitSingularUInt32Field(value: self.port, fieldNumber: 1) + } + if !self.v6.isEmpty { + try visitor.visitSingularBytesField(value: self.v6, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLV6Info, rhs: SDLV6Info) -> Bool { + if lhs.port != rhs.port {return false} + if lhs.v6 != rhs.v6 {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLDevAddr: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLDevAddr" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "network_id"), + 2: .same(proto: "mac"), + 3: .standard(proto: "net_addr"), + 4: .standard(proto: "net_bit_len"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.mac) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.netAddr) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.netBitLen) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) + } + if !self.mac.isEmpty { + try visitor.visitSingularBytesField(value: self.mac, fieldNumber: 2) + } + if self.netAddr != 0 { + try visitor.visitSingularUInt32Field(value: self.netAddr, fieldNumber: 3) + } + if self.netBitLen != 0 { + try visitor.visitSingularUInt32Field(value: self.netBitLen, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLDevAddr, rhs: SDLDevAddr) -> Bool { + if lhs.networkID != rhs.networkID {return false} + if lhs.mac != rhs.mac {return false} + if lhs.netAddr != rhs.netAddr {return false} + if lhs.netBitLen != rhs.netBitLen {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLEmpty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLEmpty" + static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + mutating func decodeMessage(decoder: inout D) throws { + while let _ = try decoder.nextFieldNumber() { + } + } + + func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLEmpty, rhs: SDLEmpty) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegisterSuper: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegisterSuper" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "version"), + 2: .standard(proto: "installed_channel"), + 3: .standard(proto: "client_id"), + 4: .standard(proto: "dev_addr"), + 5: .standard(proto: "pub_key"), + 6: .same(proto: "token"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.version) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.installedChannel) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.clientID) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._devAddr) }() + case 5: try { try decoder.decodeSingularStringField(value: &self.pubKey) }() + case 6: try { try decoder.decodeSingularStringField(value: &self.token) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.version != 0 { + try visitor.visitSingularUInt32Field(value: self.version, fieldNumber: 1) + } + if !self.installedChannel.isEmpty { + try visitor.visitSingularStringField(value: self.installedChannel, fieldNumber: 2) + } + if !self.clientID.isEmpty { + try visitor.visitSingularStringField(value: self.clientID, fieldNumber: 3) + } + try { if let v = self._devAddr { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + if !self.pubKey.isEmpty { + try visitor.visitSingularStringField(value: self.pubKey, fieldNumber: 5) + } + if !self.token.isEmpty { + try visitor.visitSingularStringField(value: self.token, fieldNumber: 6) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegisterSuper, rhs: SDLRegisterSuper) -> Bool { + if lhs.version != rhs.version {return false} + if lhs.installedChannel != rhs.installedChannel {return false} + if lhs.clientID != rhs.clientID {return false} + if lhs._devAddr != rhs._devAddr {return false} + if lhs.pubKey != rhs.pubKey {return false} + if lhs.token != rhs.token {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegisterSuperAck" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dev_addr"), + 2: .standard(proto: "aes_key"), + 3: .standard(proto: "upgrade_type"), + 4: .standard(proto: "upgrade_prompt"), + 5: .standard(proto: "upgrade_address"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._devAddr) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.aesKey) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.upgradeType) }() + case 4: try { try decoder.decodeSingularStringField(value: &self._upgradePrompt) }() + case 5: try { try decoder.decodeSingularStringField(value: &self._upgradeAddress) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._devAddr { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.aesKey.isEmpty { + try visitor.visitSingularBytesField(value: self.aesKey, fieldNumber: 2) + } + if self.upgradeType != 0 { + try visitor.visitSingularUInt32Field(value: self.upgradeType, fieldNumber: 3) + } + try { if let v = self._upgradePrompt { + try visitor.visitSingularStringField(value: v, fieldNumber: 4) + } }() + try { if let v = self._upgradeAddress { + try visitor.visitSingularStringField(value: v, fieldNumber: 5) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegisterSuperAck, rhs: SDLRegisterSuperAck) -> Bool { + if lhs._devAddr != rhs._devAddr {return false} + if lhs.aesKey != rhs.aesKey {return false} + if lhs.upgradeType != rhs.upgradeType {return false} + if lhs._upgradePrompt != rhs._upgradePrompt {return false} + if lhs._upgradeAddress != rhs._upgradeAddress {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegisterSuperNak: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegisterSuperNak" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "error_code"), + 2: .standard(proto: "error_message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.errorCode) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.errorMessage) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.errorCode != 0 { + try visitor.visitSingularUInt32Field(value: self.errorCode, fieldNumber: 1) + } + if !self.errorMessage.isEmpty { + try visitor.visitSingularStringField(value: self.errorMessage, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegisterSuperNak, rhs: SDLRegisterSuperNak) -> Bool { + if lhs.errorCode != rhs.errorCode {return false} + if lhs.errorMessage != rhs.errorMessage {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLQueryInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLQueryInfo" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dst_mac"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLQueryInfo, rhs: SDLQueryInfo) -> Bool { + if lhs.dstMac != rhs.dstMac {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLPeerInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLPeerInfo" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dst_mac"), + 2: .standard(proto: "v4_info"), + 3: .standard(proto: "nat_type"), + 4: .standard(proto: "v6_info"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._v4Info) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.natType) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._v6Info) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 1) + } + try { if let v = self._v4Info { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if self.natType != 0 { + try visitor.visitSingularUInt32Field(value: self.natType, fieldNumber: 3) + } + try { if let v = self._v6Info { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLPeerInfo, rhs: SDLPeerInfo) -> Bool { + if lhs.dstMac != rhs.dstMac {return false} + if lhs._v4Info != rhs._v4Info {return false} + if lhs.natType != rhs.natType {return false} + if lhs._v6Info != rhs._v6Info {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLNatChangedEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLNatChangedEvent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "mac"), + 2: .same(proto: "ip"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.mac) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.ip) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.mac.isEmpty { + try visitor.visitSingularBytesField(value: self.mac, fieldNumber: 1) + } + if self.ip != 0 { + try visitor.visitSingularUInt32Field(value: self.ip, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLNatChangedEvent, rhs: SDLNatChangedEvent) -> Bool { + if lhs.mac != rhs.mac {return false} + if lhs.ip != rhs.ip {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLSendRegisterEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLSendRegisterEvent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dst_mac"), + 2: .standard(proto: "nat_ip"), + 3: .standard(proto: "nat_port"), + 4: .standard(proto: "v6_info"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.natIp) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.natPort) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._v6Info) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 1) + } + if self.natIp != 0 { + try visitor.visitSingularUInt32Field(value: self.natIp, fieldNumber: 2) + } + if self.natPort != 0 { + try visitor.visitSingularUInt32Field(value: self.natPort, fieldNumber: 3) + } + try { if let v = self._v6Info { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLSendRegisterEvent, rhs: SDLSendRegisterEvent) -> Bool { + if lhs.dstMac != rhs.dstMac {return false} + if lhs.natIp != rhs.natIp {return false} + if lhs.natPort != rhs.natPort {return false} + if lhs._v6Info != rhs._v6Info {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLNetworkShutdownEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLNetworkShutdownEvent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.message) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.message.isEmpty { + try visitor.visitSingularStringField(value: self.message, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLNetworkShutdownEvent, rhs: SDLNetworkShutdownEvent) -> Bool { + if lhs.message != rhs.message {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLChangeNetworkCommand: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLChangeNetworkCommand" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dev_addr"), + 2: .standard(proto: "aes_key"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._devAddr) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.aesKey) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._devAddr { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.aesKey.isEmpty { + try visitor.visitSingularBytesField(value: self.aesKey, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLChangeNetworkCommand, rhs: SDLChangeNetworkCommand) -> Bool { + if lhs._devAddr != rhs._devAddr {return false} + if lhs.aesKey != rhs.aesKey {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLCommandAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLCommandAck" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "status"), + 2: .same(proto: "message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self.status) }() + case 2: try { try decoder.decodeSingularStringField(value: &self._message) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.status != false { + try visitor.visitSingularBoolField(value: self.status, fieldNumber: 1) + } + try { if let v = self._message { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLCommandAck, rhs: SDLCommandAck) -> Bool { + if lhs.status != rhs.status {return false} + if lhs._message != rhs._message {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLFlows: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLFlows" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "forward_num"), + 2: .standard(proto: "p2p_num"), + 3: .standard(proto: "inbound_num"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.forwardNum) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.p2PNum) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.inboundNum) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.forwardNum != 0 { + try visitor.visitSingularUInt32Field(value: self.forwardNum, fieldNumber: 1) + } + if self.p2PNum != 0 { + try visitor.visitSingularUInt32Field(value: self.p2PNum, fieldNumber: 2) + } + if self.inboundNum != 0 { + try visitor.visitSingularUInt32Field(value: self.inboundNum, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLFlows, rhs: SDLFlows) -> Bool { + if lhs.forwardNum != rhs.forwardNum {return false} + if lhs.p2PNum != rhs.p2PNum {return false} + if lhs.inboundNum != rhs.inboundNum {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLStunRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLStunRequest" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "cookie"), + 2: .standard(proto: "client_id"), + 3: .standard(proto: "network_id"), + 4: .same(proto: "mac"), + 5: .same(proto: "ip"), + 6: .standard(proto: "nat_type"), + 7: .standard(proto: "v6_info"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.cookie) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.clientID) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 4: try { try decoder.decodeSingularBytesField(value: &self.mac) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.ip) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self.natType) }() + case 7: try { try decoder.decodeSingularMessageField(value: &self._v6Info) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.cookie != 0 { + try visitor.visitSingularUInt32Field(value: self.cookie, fieldNumber: 1) + } + if !self.clientID.isEmpty { + try visitor.visitSingularStringField(value: self.clientID, fieldNumber: 2) + } + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 3) + } + if !self.mac.isEmpty { + try visitor.visitSingularBytesField(value: self.mac, fieldNumber: 4) + } + if self.ip != 0 { + try visitor.visitSingularUInt32Field(value: self.ip, fieldNumber: 5) + } + if self.natType != 0 { + try visitor.visitSingularUInt32Field(value: self.natType, fieldNumber: 6) + } + try { if let v = self._v6Info { + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLStunRequest, rhs: SDLStunRequest) -> Bool { + if lhs.cookie != rhs.cookie {return false} + if lhs.clientID != rhs.clientID {return false} + if lhs.networkID != rhs.networkID {return false} + if lhs.mac != rhs.mac {return false} + if lhs.ip != rhs.ip {return false} + if lhs.natType != rhs.natType {return false} + if lhs._v6Info != rhs._v6Info {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLStunReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLStunReply" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "cookie"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.cookie) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.cookie != 0 { + try visitor.visitSingularUInt32Field(value: self.cookie, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLStunReply, rhs: SDLStunReply) -> Bool { + if lhs.cookie != rhs.cookie {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLData: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLData" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "network_id"), + 2: .standard(proto: "src_mac"), + 3: .standard(proto: "dst_mac"), + 4: .standard(proto: "is_p2p"), + 5: .same(proto: "ttl"), + 6: .same(proto: "data"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.srcMac) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.isP2P) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.ttl) }() + case 6: try { try decoder.decodeSingularBytesField(value: &self.data) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) + } + if !self.srcMac.isEmpty { + try visitor.visitSingularBytesField(value: self.srcMac, fieldNumber: 2) + } + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 3) + } + if self.isP2P != false { + try visitor.visitSingularBoolField(value: self.isP2P, fieldNumber: 4) + } + if self.ttl != 0 { + try visitor.visitSingularUInt32Field(value: self.ttl, fieldNumber: 5) + } + if !self.data.isEmpty { + try visitor.visitSingularBytesField(value: self.data, fieldNumber: 6) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLData, rhs: SDLData) -> Bool { + if lhs.networkID != rhs.networkID {return false} + if lhs.srcMac != rhs.srcMac {return false} + if lhs.dstMac != rhs.dstMac {return false} + if lhs.isP2P != rhs.isP2P {return false} + if lhs.ttl != rhs.ttl {return false} + if lhs.data != rhs.data {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegister: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegister" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "network_id"), + 2: .standard(proto: "src_mac"), + 3: .standard(proto: "dst_mac"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.srcMac) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) + } + if !self.srcMac.isEmpty { + try visitor.visitSingularBytesField(value: self.srcMac, fieldNumber: 2) + } + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegister, rhs: SDLRegister) -> Bool { + if lhs.networkID != rhs.networkID {return false} + if lhs.srcMac != rhs.srcMac {return false} + if lhs.dstMac != rhs.dstMac {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegisterAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegisterAck" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "network_id"), + 2: .standard(proto: "src_mac"), + 3: .standard(proto: "dst_mac"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.srcMac) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) + } + if !self.srcMac.isEmpty { + try visitor.visitSingularBytesField(value: self.srcMac, fieldNumber: 2) + } + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegisterAck, rhs: SDLRegisterAck) -> Bool { + if lhs.networkID != rhs.networkID {return false} + if lhs.srcMac != rhs.srcMac {return false} + if lhs.dstMac != rhs.dstMac {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLStunProbe: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLStunProbe" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "cookie"), + 2: .same(proto: "attr"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.cookie) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.attr) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.cookie != 0 { + try visitor.visitSingularUInt32Field(value: self.cookie, fieldNumber: 1) + } + if self.attr != 0 { + try visitor.visitSingularUInt32Field(value: self.attr, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLStunProbe, rhs: SDLStunProbe) -> Bool { + if lhs.cookie != rhs.cookie {return false} + if lhs.attr != rhs.attr {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLStunProbeReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLStunProbeReply" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "cookie"), + 2: .same(proto: "port"), + 3: .same(proto: "ip"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.cookie) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.port) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.ip) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.cookie != 0 { + try visitor.visitSingularUInt32Field(value: self.cookie, fieldNumber: 1) + } + if self.port != 0 { + try visitor.visitSingularUInt32Field(value: self.port, fieldNumber: 2) + } + if self.ip != 0 { + try visitor.visitSingularUInt32Field(value: self.ip, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLStunProbeReply, rhs: SDLStunProbeReply) -> Bool { + if lhs.cookie != rhs.cookie {return false} + if lhs.port != rhs.port {return false} + if lhs.ip != rhs.ip {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Tun/Punchnet/SDLMessage.swift b/Tun/Punchnet/SDLMessage.swift new file mode 100644 index 0000000..9293a29 --- /dev/null +++ b/Tun/Punchnet/SDLMessage.swift @@ -0,0 +1,156 @@ +// +// SDLPacketType.swift +// Tun +// +// Created by 安礼成 on 2024/4/10. +// + +import Foundation +import NIOCore + +// 消息类型定义 +enum SDLPacketType: UInt8 { + case empty = 0x00 + + case registerSuper = 0x01 + case registerSuperAck = 0x02 + case registerSuperNak = 0x04 + + case unregisterSuper = 0x05 + + case queryInfo = 0x06 + case peerInfo = 0x07 + + case ping = 0x08 + case pong = 0x09 + + // 事件类型 + case event = 0x10 + + // 推送命令消息, 需要返回值 + case command = 0x11 + case commandAck = 0x12 + + // 流量统计 + case flowTracer = 0x15 + + case register = 0x20 + case registerAck = 0x21 + + case stunRequest = 0x30 + case stunReply = 0x31 + + case stunProbe = 0x32 + case stunProbeReply = 0x33 + + case data = 0xFF +} + +// 升级策略 +enum SDLUpgradeType: UInt32 { + case none = 0 + case normal = 1 + case force = 2 +} + +// Id生成器 +struct SDLIdGenerator: Sendable { + // 消息体id + private var packetId: UInt32 + + init(seed packetId: UInt32) { + self.packetId = packetId + } + + mutating func nextId() -> UInt32 { + let packetId = self.packetId + self.packetId = packetId + 1 + return packetId + } +} + +// 定义事件类型 + +// 命令类型 +enum SDLEventType: UInt8 { + case natChanged = 0x03 + case sendRegister = 0x04 + case networkShutdown = 0xFF +} + +enum SDLEvent { + case natChanged(SDLNatChangedEvent) + case sendRegister(SDLSendRegisterEvent) + case networkShutdown(SDLNetworkShutdownEvent) +} + +// --MARK: 定义命令类型 + +enum SDLCommandType: UInt8 { + case changeNetwork = 0x01 +} + +enum SDLCommand { + case changeNetwork(SDLChangeNetworkCommand) +} + +// --MARK: 网络类型探测 +// 探测的Attr属性 +enum SDLProbeAttr: UInt8 { + case none = 0 + case port = 1 + case peer = 2 +} + +// Nak的错误类型,不同的错误客户端的处理逻辑不一样 +enum SDLNAKErrorCode: UInt8 { + case invalidToken = 1 + case nodeDisabled = 2 + case noIpAddress = 3 + case networkFault = 4 + case internalFault = 5 +} + +extension SDLV4Info { + func socketAddress() -> SocketAddress? { + let address = "\(v4[0]).\(v4[1]).\(v4[2]).\(v4[3])" + + return try? SocketAddress.makeAddressResolvingHost(address, port: Int(port)) + } +} + +extension SDLStunProbeReply { + func socketAddress() -> SocketAddress? { + let address = SDLUtil.int32ToIp(self.ip) + + return try? SocketAddress.makeAddressResolvingHost(address, port: Int(port)) + } +} + +// --MARK: 进来的消息, 这里需要采用代数类型来表示 + +enum SDLHoleInboundMessage { + case stunReply(SDLStunReply) + case stunProbeReply(SDLStunProbeReply) + + case data(SDLData) + case register(SDLRegister) + case registerAck(SDLRegisterAck) +} + +// --MARK: 定义消息类型 + +struct SDLSuperInboundMessage { + let msgId: UInt32 + let packet: InboundPacket + + enum InboundPacket { + case empty + case registerSuperAck(SDLRegisterSuperAck) + case registerSuperNak(SDLRegisterSuperNak) + case peerInfo(SDLPeerInfo) + case pong + case event(SDLEvent) + case command(SDLCommand) + } +} diff --git a/Tun/Punchnet/SDLNatProber.swift b/Tun/Punchnet/SDLNatProber.swift new file mode 100644 index 0000000..b61fe93 --- /dev/null +++ b/Tun/Punchnet/SDLNatProber.swift @@ -0,0 +1,77 @@ +// +// File.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// + +import Foundation +import NIOCore + +// 网络类型探测器 +@available(macOS 14, *) +struct SDLNatProber { + + // 定义nat类型 + enum NatType: UInt8, Encodable { + case blocked = 0 + case noNat = 1 + case fullCone = 2 + case portRestricted = 3 + case coneRestricted = 4 + case symmetric = 5 + } + + // 获取当前所处的网络的nat类型 + static func getNatType(udpHole: SDLUDPHole?, config: SDLConfiguration, logger: SDLLogger) async -> NatType { + guard let udpHole else { + return .blocked + } + + let addressArray = config.stunProbeSocketAddressArray + // step1: ip1:port1 <---- ip1:port1 + guard let natAddress1 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .none) else { + return .blocked + } + + // 网络没有在nat下 + if await natAddress1 == udpHole.localAddress { + return .noNat + } + + // step2: ip2:port2 <---- ip2:port2 + guard let natAddress2 = await getNatAddress(udpHole, remoteAddress: addressArray[1][1], attr: .none) else { + return .blocked + } + + // 如果natAddress2 的IP地址与上次回来的IP是不一样的,它就是对称型NAT; 这次的包也一定能发成功并收到 + // 如果ip地址变了,这说明{dstIp, dstPort, srcIp, srcPort}, 其中有一个变了;则用新的ip地址 + logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2)", level: .debug) + if let ipAddress1 = natAddress1.ipAddress, let ipAddress2 = natAddress2.ipAddress, ipAddress1 != ipAddress2 { + return .symmetric + } + + // step3: ip1:port1 <---- ip2:port2 (ip地址和port都变的情况) + // 如果能收到的,说明是完全锥形 说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。 + if let natAddress3 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .peer) { + logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address3: \(natAddress3)", level: .debug) + return .fullCone + } + + // step3: ip1:port1 <---- ip1:port2 (port改变情况) + // 如果能收到的说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。 + if let natAddress4 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .port) { + logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address4: \(natAddress4)", level: .debug) + return .coneRestricted + } else { + return .portRestricted + } + } + + private static func getNatAddress(_ udpHole: SDLUDPHole, remoteAddress: SocketAddress, attr: SDLProbeAttr) async -> SocketAddress? { + let stunProbeReply = try? await udpHole.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: 5) + + return stunProbeReply?.socketAddress() + } + +} diff --git a/Tun/Punchnet/SDLNetAddress.swift b/Tun/Punchnet/SDLNetAddress.swift new file mode 100644 index 0000000..5ab8a56 --- /dev/null +++ b/Tun/Punchnet/SDLNetAddress.swift @@ -0,0 +1,49 @@ +// +// SDLIPAddress.swift +// Tun +// +// Created by 安礼成 on 2024/3/4. +// + +import Foundation + +struct SDLNetAddress { + let ip: UInt32 + let maskLen: UInt8 + + // ip地址 + var ipAddress: String { + return intToIpAddress(self.ip) + } + + // 掩码 + var maskAddress: String { + let len0 = 32 - maskLen + let num: UInt32 = (0xFFFFFFFF >> len0) << len0 + + return intToIpAddress(num) + } + + // 网络地址 + var networkAddress: String { + let len0 = 32 - maskLen + let mask: UInt32 = (0xFFFFFFFF >> len0) << len0 + + return intToIpAddress(self.ip & mask) + } + + init(ip: UInt32, maskLen: UInt8) { + self.ip = ip + self.maskLen = maskLen + } + + private func intToIpAddress(_ num: UInt32) -> String { + let ip0 = (UInt8) (num >> 24 & 0xFF) + let ip1 = (UInt8) (num >> 16 & 0xFF) + let ip2 = (UInt8) (num >> 8 & 0xFF) + let ip3 = (UInt8) (num & 0xFF) + + return "\(ip0).\(ip1).\(ip2).\(ip3)" + } + +} diff --git a/Tun/Punchnet/SDLNetworkMonitor.swift b/Tun/Punchnet/SDLNetworkMonitor.swift new file mode 100644 index 0000000..5a76bee --- /dev/null +++ b/Tun/Punchnet/SDLNetworkMonitor.swift @@ -0,0 +1,64 @@ +// +// SDLNetworkMonitor.swift +// Tun +// +// Created by 安礼成 on 2024/5/16. +// + +import Foundation +import Network +import Combine + +// 监控网络的变化 +class SDLNetworkMonitor: @unchecked Sendable { + private var monitor: NWPathMonitor + private var interfaceType: NWInterface.InterfaceType? + private let publisher = PassthroughSubject() + private var cancel: AnyCancellable? + + public let eventStream: AsyncStream + private let eventContinuation: AsyncStream.Continuation + + enum MonitorEvent { + case changed + case unreachable + } + + init() { + self.monitor = NWPathMonitor() + (self.eventStream , self.eventContinuation) = AsyncStream.makeStream(of: MonitorEvent.self, bufferingPolicy: .unbounded) + } + + func start() { + self.monitor.pathUpdateHandler = {path in + if path.status == .satisfied { + if path.usesInterfaceType(.wifi) { + self.publisher.send(.wifi) + } else if path.usesInterfaceType(.cellular) { + self.publisher.send(.cellular) + } else if path.usesInterfaceType(.wiredEthernet) { + self.publisher.send(.wiredEthernet) + } + } else { + self.eventContinuation.yield(.unreachable) + self.interfaceType = nil + } + } + self.monitor.start(queue: DispatchQueue.global()) + + self.cancel = publisher.throttle(for: 5.0, scheduler: DispatchQueue.global(), latest: true) + .sink { type in + if self.interfaceType != nil && self.interfaceType != type { + self.eventContinuation.yield(.changed) + } + self.interfaceType = type + } + } + + deinit { + self.monitor.cancel() + self.cancel?.cancel() + self.eventContinuation.finish() + } + +} diff --git a/Tun/Punchnet/SDLNoticeClient.swift b/Tun/Punchnet/SDLNoticeClient.swift new file mode 100644 index 0000000..7155538 --- /dev/null +++ b/Tun/Punchnet/SDLNoticeClient.swift @@ -0,0 +1,88 @@ +// +// SDLNoticeClient.swift +// Tun +// +// Created by 安礼成 on 2024/5/20. +// + +import Foundation + +// +// SDLanServer.swift +// Tun +// +// Created by 安礼成 on 2024/1/31. +// + +import Foundation +import NIOCore +import NIOPosix + +// 处理和sn-server服务器之间的通讯 +actor SDLNoticeClient { + private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private let asyncChannel: NIOAsyncChannel, AddressedEnvelope> + private let remoteAddress: SocketAddress + private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: Data.self, bufferingPolicy: .unbounded) + + private let logger: SDLLogger + + // 启动函数 + init(noticePort: Int, logger: SDLLogger) async throws { + self.logger = logger + + self.remoteAddress = try! SocketAddress(ipAddress: "127.0.0.1", port: noticePort) + + let bootstrap = DatagramBootstrap(group: self.group) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + + self.asyncChannel = try await bootstrap.bind(host: "0.0.0.0", port: 0) + .flatMapThrowing {channel in + return try NIOAsyncChannel(wrappingChannelSynchronously: channel, configuration: .init( + inboundType: AddressedEnvelope.self, + outboundType: AddressedEnvelope.self + )) + } + .get() + + self.logger.log("[SDLNoticeClient] started and listening on: \(self.asyncChannel.channel.localAddress!)", level: .debug) + } + + func start() async throws { + try await self.asyncChannel.executeThenClose { inbound, outbound in + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + try await self.asyncChannel.channel.closeFuture.get() + throw SDLError.socketClosed + } + + group.addTask { + defer { + self.writeContinuation.finish() + } + + for try await message in self.writeStream { + let buf = self.asyncChannel.channel.allocator.buffer(bytes: message) + let envelope = AddressedEnvelope(remoteAddress: self.remoteAddress, data: buf) + + try await outbound.write(envelope) + } + } + + for try await _ in group { + + } + } + } + } + + // 处理写入逻辑 + func send(data: Data) { + self.writeContinuation.yield(data) + } + + deinit { + try? self.group.syncShutdownGracefully() + self.writeContinuation.finish() + } +} diff --git a/Tun/Punchnet/SDLProtoMessageExtension.swift b/Tun/Punchnet/SDLProtoMessageExtension.swift new file mode 100644 index 0000000..89ca77e --- /dev/null +++ b/Tun/Punchnet/SDLProtoMessageExtension.swift @@ -0,0 +1,16 @@ +// +// SDLProtoMessageExtension.swift +// Tun +// +// Created by 安礼成 on 2024/10/24. +// + +import Foundation + +extension SDLData { + + func format() -> String { + return "network_id: \(self.networkID), src_mac: \(LayerPacket.MacAddress.description(data: self.srcMac)), dst_mac: \(LayerPacket.MacAddress.description(data: self.dstMac)), data: \([UInt8](self.data))" + } + +} diff --git a/Tun/Punchnet/SDLQPSCounter.swift b/Tun/Punchnet/SDLQPSCounter.swift new file mode 100644 index 0000000..9c2859e --- /dev/null +++ b/Tun/Punchnet/SDLQPSCounter.swift @@ -0,0 +1,37 @@ +// +// SDLQPSCounter.swift +// Tun +// +// Created by 安礼成 on 2024/4/16. +// + +import Foundation + +// 计数器,用来统计qps +class SDLQPSCounter: @unchecked Sendable { + private var count = 0 + private let timer: DispatchSourceTimer + private let label: String + private let queue = DispatchQueue(label: "com.punchnet.qps") + + init(label: String) { + self.label = label + timer = DispatchSource.makeTimerSource(queue: queue) + timer.schedule(deadline: .now(), repeating: .seconds(1), leeway: .milliseconds(100)) + timer.setEventHandler { [weak self] in + guard let self = self else { return } + self.count = 0 + } + timer.resume() + } + + func increment(num: Int = 1) { + queue.async { + self.count += num + } + } + + deinit { + timer.cancel() + } +} diff --git a/Tun/Punchnet/SDLSuperClient.swift b/Tun/Punchnet/SDLSuperClient.swift new file mode 100644 index 0000000..dd4a98e --- /dev/null +++ b/Tun/Punchnet/SDLSuperClient.swift @@ -0,0 +1,326 @@ +// +// SDLWebsocketClient.swift +// Tun +// +// Created by 安礼成 on 2024/3/28. +// + +import Foundation +import NIOCore +import NIOPosix + +// --MARK: 和SuperNode的客户端 +@available(macOS 14, *) +actor SDLSuperClient { + private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private let asyncChannel: NIOAsyncChannel + private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: TcpMessage.self, bufferingPolicy: .unbounded) + private var callbackPromises: [UInt32:EventLoopPromise] = [:] + + public let eventFlow: AsyncStream + private let inboundContinuation: AsyncStream.Continuation + + // id生成器 + var idGenerator = SDLIdGenerator(seed: 1) + + private let logger: SDLLogger + // 发送的消息格式 + struct TcpMessage { + let packetId: UInt32 + let type: SDLPacketType + let data: Data + } + + // 定义事件类型 + enum SuperEvent { + case ready + case event(SDLEvent) + case command(UInt32, SDLCommand) + } + + init(host: String, port: Int, logger: SDLLogger) async throws { + self.logger = logger + + (self.eventFlow, self.inboundContinuation) = AsyncStream.makeStream(of: SuperEvent.self, bufferingPolicy: .unbounded) + let bootstrap = ClientBootstrap(group: self.group) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .channelInitializer { channel in + return channel.pipeline.addHandlers([ + ByteToMessageHandler(FixedHeaderDecoder()), + MessageToByteHandler(FixedHeaderEncoder()) + ]) + } + + self.asyncChannel = try await bootstrap.connect(host: host, port: port) + .flatMapThrowing { channel in + return try NIOAsyncChannel(wrappingChannelSynchronously: channel, configuration: .init( + inboundType: ByteBuffer.self, + outboundType: ByteBuffer.self + )) + } + .get() + } + + func start() async throws { + try await withTaskCancellationHandler { + try await self.asyncChannel.executeThenClose { inbound, outbound in + self.inboundContinuation.yield(.ready) + + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + defer { + self.logger.log("[SDLSuperClient] inbound closed", level: .warning) + } + + for try await var packet in inbound { + try Task.checkCancellation() + + if let message = SDLSuperClientDecoder.decode(buffer: &packet) { + self.logger.log("[SDLSuperTransport] read message: \(message)", level: .debug) + switch message.packet { + case .event(let event): + self.inboundContinuation.yield(.event(event)) + case .command(let command): + self.inboundContinuation.yield(.command(message.msgId, command)) + default: + await self.fireCallback(message: message) + } + } + } + } + + group.addTask { + defer { + self.logger.log("[SDLSuperClient] outbound closed", level: .warning) + } + + for await message in self.writeStream { + try Task.checkCancellation() + + var buffer = self.asyncChannel.channel.allocator.buffer(capacity: message.data.count + 5) + buffer.writeInteger(message.packetId, as: UInt32.self) + buffer.writeBytes([message.type.rawValue]) + buffer.writeBytes(message.data) + try await outbound.write(buffer) + } + } + + // --MARK: 心跳机制 + group.addTask { + defer { + self.logger.log("[SDLSuperClient] ping task closed", level: .warning) + } + + while true { + try Task.checkCancellation() + await self.ping() + try await Task.sleep(nanoseconds: 5 * 1_000_000_000) + } + } + + // 迭代等待所有任务的退出, 第一个异常会被抛出 + if let _ = try await group.next() { + group.cancelAll() + } + } + } + } onCancel: { + self.inboundContinuation.finish() + self.writeContinuation.finish() + self.logger.log("[SDLSuperClient] withTaskCancellationHandler cancel") + } + } + + // -- MARK: apis + + func commandAck(packetId: UInt32, ack: SDLCommandAck) { + guard let data = try? ack.serializedData() else { + return + } + + self.send(type: .commandAck, packetId: packetId, data: data) + } + + func registerSuper(context ctx: SDLContext) throws -> EventLoopFuture { + var registerSuper = SDLRegisterSuper() + registerSuper.version = UInt32(ctx.config.version) + registerSuper.clientID = ctx.config.clientId + registerSuper.devAddr = ctx.devAddr + registerSuper.pubKey = ctx.rsaCipher.pubKey + registerSuper.token = ctx.config.token + + let data = try! registerSuper.serializedData() + + return self.write(type: .registerSuper, data: data) + } + + // 查询目标服务器的相关信息 + func queryInfo(dst_mac: Data) async throws -> EventLoopFuture { + var queryInfo = SDLQueryInfo() + queryInfo.dstMac = dst_mac + + return self.write(type: .queryInfo, data: try! queryInfo.serializedData()) + } + + func unregister(context ctx: SDLContext) throws { + self.send(type: .unregisterSuper, packetId: 0, data: Data()) + } + + func ping() { + self.send(type: .ping, packetId: 0, data: Data()) + } + + func flowReport(forwardNum: UInt32, p2pNum: UInt32, inboundNum: UInt32) { + var flow = SDLFlows() + flow.forwardNum = forwardNum + flow.p2PNum = p2pNum + flow.inboundNum = inboundNum + + self.send(type: .flowTracer, packetId: 0, data: try! flow.serializedData()) + } + + private func write(type: SDLPacketType, data: Data) -> EventLoopFuture { + let packetId = idGenerator.nextId() + let promise = self.asyncChannel.channel.eventLoop.makePromise(of: SDLSuperInboundMessage.self) + self.callbackPromises[packetId] = promise + + self.writeContinuation.yield(TcpMessage(packetId: packetId, type: type, data: data)) + + return promise.futureResult + } + + private func send(type: SDLPacketType, packetId: UInt32, data: Data) { + self.writeContinuation.yield(TcpMessage(packetId: packetId, type: type, data: data)) + } + + // 处理回调函数 + private func fireCallback(message: SDLSuperInboundMessage) { + if let promise = self.callbackPromises[message.msgId] { + self.asyncChannel.channel.eventLoop.execute { + promise.succeed(message) + } + self.callbackPromises.removeValue(forKey: message.msgId) + } + } + + deinit { + try! group.syncShutdownGracefully() + } + +} + +// --MARK: 编解码器 +private struct SDLSuperClientDecoder { + // 消息格式为: <> + static func decode(buffer: inout ByteBuffer) -> SDLSuperInboundMessage? { + guard let msgId = buffer.readInteger(as: UInt32.self), + let type = buffer.readInteger(as: UInt8.self), + let messageType = SDLPacketType(rawValue: type) else { + return nil + } + + switch messageType { + case .empty: + return .init(msgId: msgId, packet: .empty) + case .registerSuperAck: + guard let bytes = buffer.readBytes(length: buffer.readableBytes), + let registerSuperAck = try? SDLRegisterSuperAck(serializedBytes: bytes) else { + return nil + } + return .init(msgId: msgId, packet: .registerSuperAck(registerSuperAck)) + + case .registerSuperNak: + guard let bytes = buffer.readBytes(length: buffer.readableBytes), + let registerSuperNak = try? SDLRegisterSuperNak(serializedBytes: bytes) else { + return nil + } + return .init(msgId: msgId, packet: .registerSuperNak(registerSuperNak)) + + case .peerInfo: + guard let bytes = buffer.readBytes(length: buffer.readableBytes), + let peerInfo = try? SDLPeerInfo(serializedBytes: bytes) else { + return nil + } + + return .init(msgId: msgId, packet: .peerInfo(peerInfo)) + case .pong: + return .init(msgId: msgId, packet: .pong) + + case .command: + guard let commandVal = buffer.readInteger(as: UInt8.self), + let command = SDLCommandType(rawValue: commandVal), + let bytes = buffer.readBytes(length: buffer.readableBytes) else { + return nil + } + + switch command { + case .changeNetwork: + guard let changeNetworkCommand = try? SDLChangeNetworkCommand(serializedBytes: bytes) else { + return nil + } + + return .init(msgId: msgId, packet: .command(.changeNetwork(changeNetworkCommand))) + } + + case .event: + guard let eventVal = buffer.readInteger(as: UInt8.self), + let event = SDLEventType(rawValue: eventVal), + let bytes = buffer.readBytes(length: buffer.readableBytes) else { + return nil + } + + switch event { + case .natChanged: + guard let natChangedEvent = try? SDLNatChangedEvent(serializedBytes: bytes) else { + return nil + } + return .init(msgId: msgId, packet: .event(.natChanged(natChangedEvent))) + case .sendRegister: + guard let sendRegisterEvent = try? SDLSendRegisterEvent(serializedBytes: bytes) else { + return nil + } + return .init(msgId: msgId, packet: .event(.sendRegister(sendRegisterEvent))) + case .networkShutdown: + guard let networkShutdownEvent = try? SDLNetworkShutdownEvent(serializedBytes: bytes) else { + return nil + } + return .init(msgId: msgId, packet: .event(.networkShutdown(networkShutdownEvent))) + } + + default: + return nil + } + } +} + +private final class FixedHeaderEncoder: MessageToByteEncoder, @unchecked Sendable { + typealias InboundIn = ByteBuffer + typealias InboundOut = ByteBuffer + + func encode(data: ByteBuffer, out: inout ByteBuffer) throws { + let len = data.readableBytes + out.writeInteger(UInt16(len)) + out.writeBytes(data.readableBytesView) + } +} + +private final class FixedHeaderDecoder: ByteToMessageDecoder, @unchecked Sendable { + typealias InboundIn = ByteBuffer + typealias InboundOut = ByteBuffer + + func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState { + guard let len = buffer.getInteger(at: buffer.readerIndex, endianness: .big, as: UInt16.self) else { + return .needMoreData + } + + if buffer.readableBytes >= len + 2 { + buffer.moveReaderIndex(forwardBy: 2) + if let bytes = buffer.readBytes(length: Int(len)) { + context.fireChannelRead(self.wrapInboundOut(ByteBuffer(bytes: bytes))) + } + return .continue + } else { + return .needMoreData + } + } +} diff --git a/Tun/Punchnet/SDLThrottler.swift b/Tun/Punchnet/SDLThrottler.swift new file mode 100644 index 0000000..4e32217 --- /dev/null +++ b/Tun/Punchnet/SDLThrottler.swift @@ -0,0 +1,45 @@ +// +// SDLThrottler.swift +// Tun +// +// Created by 安礼成 on 2024/6/3. +// + +import Foundation +import Combine + +// 限流器 +actor SDLThrottler { + private var limit: Int + private var token: Int + private var cancel: AnyCancellable? + + init(limit: Int) { + self.limit = limit + self.token = limit + } + + func start() { + self.cancel?.cancel() + self.cancel = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect() + .sink { _ in + Task { + self.token = self.limit + } + } + } + + func setRateLimit(limit: Int) { + self.limit = limit + } + + func getToken(num: Int) -> Bool { + if token > 0 { + self.token = self.token - num + return true + } else { + return false + } + } + +} diff --git a/Tun/Punchnet/SDLUDPHole.swift b/Tun/Punchnet/SDLUDPHole.swift new file mode 100644 index 0000000..2e0209a --- /dev/null +++ b/Tun/Punchnet/SDLUDPHole.swift @@ -0,0 +1,282 @@ +// +// SDLanServer.swift +// Tun +// +// Created by 安礼成 on 2024/1/31. +// + +import Foundation +import NIOCore +import NIOPosix + +// 处理和sn-server服务器之间的通讯 +@available(macOS 14, *) +actor SDLUDPHole { + private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private let asyncChannel: NIOAsyncChannel, AddressedEnvelope> + private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: UDPMessage.self, bufferingPolicy: .unbounded) + + private var cookieGenerator = SDLIdGenerator(seed: 1) + private var promises: [UInt32:EventLoopPromise] = [:] + public var localAddress: SocketAddress? + + public let eventFlow: AsyncStream + private let eventContinuation: AsyncStream.Continuation + + private let logger: SDLLogger + + struct UDPMessage { + let remoteAddress: SocketAddress + let type: SDLPacketType + let data: Data + } + + // 定义事件类型 + enum UDPEvent { + case ready + case message(SocketAddress, SDLHoleInboundMessage) + case data(SDLData) + } + + // 启动函数 + init(logger: SDLLogger) async throws { + self.logger = logger + + (self.eventFlow, self.eventContinuation) = AsyncStream.makeStream(of: UDPEvent.self, bufferingPolicy: .unbounded) + + let bootstrap = DatagramBootstrap(group: group) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + + self.asyncChannel = try await bootstrap.bind(host: "0.0.0.0", port: 0) + .flatMapThrowing { channel in + return try NIOAsyncChannel(wrappingChannelSynchronously: channel, configuration: .init( + inboundType: AddressedEnvelope.self, + outboundType: AddressedEnvelope.self + )) + } + .get() + + self.localAddress = self.asyncChannel.channel.localAddress + self.logger.log("[UDPHole] started and listening on: \(self.localAddress!)", level: .debug) + } + + func start() async throws { + try await withTaskCancellationHandler { + try await self.asyncChannel.executeThenClose {inbound, outbound in + self.eventContinuation.yield(.ready) + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + defer { + self.logger.log("[SDLUDPHole] inbound closed", level: .warning) + } + + for try await envelope in inbound { + try Task.checkCancellation() + + var buffer = envelope.data + let remoteAddress = envelope.remoteAddress + do { + if let message = try Self.decode(buffer: &buffer) { + switch message { + case .data(let data): + self.logger.log("[SDLUDPHole] read data: \(data.format()), from: \(remoteAddress)", level: .debug) + self.eventContinuation.yield(.data(data)) + case .stunProbeReply(let probeReply): + // 执行并移除回调 + await self.trigger(probeReply: probeReply) + default: + self.eventContinuation.yield(.message(remoteAddress, message)) + } + } else { + self.logger.log("[SDLUDPHole] decode message, get null", level: .warning) + } + } catch let err { + self.logger.log("[SDLUDPHole] decode message, get error: \(err)", level: .warning) + throw err + } + } + } + + group.addTask { + defer { + self.logger.log("[SDLUDPHole] outbound closed", level: .warning) + } + + for await message in self.writeStream { + try Task.checkCancellation() + + var buffer = self.asyncChannel.channel.allocator.buffer(capacity: message.data.count + 1) + buffer.writeBytes([message.type.rawValue]) + buffer.writeBytes(message.data) + + let envelope = AddressedEnvelope(remoteAddress: message.remoteAddress, data: buffer) + try await outbound.write(envelope) + } + } + + if let _ = try await group.next() { + group.cancelAll() + } + } + } + } onCancel: { + self.writeContinuation.finish() + self.eventContinuation.finish() + self.logger.log("[SDLUDPHole] withTaskCancellationHandler cancel") + } + } + + // MARK: super_node apis + + func stunRequest(context ctx: SDLContext) -> UInt32 { + let cookie = self.cookieGenerator.nextId() + let remoteAddress = ctx.config.stunSocketAddress + + var stunRequest = SDLStunRequest() + stunRequest.cookie = cookie + stunRequest.clientID = ctx.config.clientId + stunRequest.networkID = ctx.devAddr.networkID + stunRequest.ip = ctx.devAddr.netAddr + stunRequest.mac = ctx.devAddr.mac + stunRequest.natType = UInt32(ctx.natType.rawValue) + + self.logger.log("[SDLUDPHole] stunRequest: \(remoteAddress), host: \(ctx.config.stunServers[0].host):\(ctx.config.stunServers[0].ports[0])", level: .debug) + + self.send(remoteAddress: remoteAddress, type: .stunRequest, data: try! stunRequest.serializedData()) + + return cookie + } + + // 探测tun信息 + func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int = 5) async throws -> SDLStunProbeReply { + return try await self._stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: timeout).get() + } + + private func _stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int) -> EventLoopFuture { + let cookie = self.cookieGenerator.nextId() + var stunProbe = SDLStunProbe() + stunProbe.cookie = cookie + stunProbe.attr = UInt32(attr.rawValue) + self.send(remoteAddress: remoteAddress, type: .stunProbe, data: try! stunProbe.serializedData()) + self.logger.log("[SDLUDPHole] stunProbe: \(remoteAddress)", level: .debug) + + let promise = self.asyncChannel.channel.eventLoop.makePromise(of: SDLStunProbeReply.self) + self.promises[cookie] = promise + + return promise.futureResult + } + + private func trigger(probeReply: SDLStunProbeReply) { + let id = probeReply.cookie + // 执行并移除回调 + if let promise = self.promises[id] { + self.asyncChannel.channel.eventLoop.execute { + promise.succeed(probeReply) + } + self.promises.removeValue(forKey: id) + } + } + + // MARK: client-client apis + + // 发送数据包到其他session + func sendPacket(context ctx: SDLContext, session: Session, data: Data) { + let remoteAddress = session.natAddress + + var dataPacket = SDLData() + dataPacket.networkID = ctx.devAddr.networkID + dataPacket.srcMac = ctx.devAddr.mac + dataPacket.dstMac = session.dstMac + dataPacket.ttl = 255 + dataPacket.data = data + if let packet = try? dataPacket.serializedData() { + self.logger.log("[SDLUDPHole] sendPacket: \(remoteAddress), count: \(packet.count)", level: .debug) + self.send(remoteAddress: remoteAddress, type: .data, data: packet) + } + } + + // 通过sn服务器转发数据包, data已经是加密过后的数据 + func forwardPacket(context ctx: SDLContext, dst_mac: Data, data: Data) { + let remoteAddress = ctx.config.stunSocketAddress + + var dataPacket = SDLData() + dataPacket.networkID = ctx.devAddr.networkID + dataPacket.srcMac = ctx.devAddr.mac + dataPacket.dstMac = dst_mac + dataPacket.ttl = 255 + dataPacket.data = data + + if let packet = try? dataPacket.serializedData() { + self.logger.log("[SDLContext] forward packet, remoteAddress: \(remoteAddress), data size: \(packet.count)", level: .debug) + self.send(remoteAddress: remoteAddress, type: .data, data: packet) + } + } + + // 发送register包 + func sendRegister(remoteAddress: SocketAddress, networkId: UInt32, srcMac: Data, dst_mac: Data) { + var register = SDLRegister() + register.networkID = networkId + register.srcMac = srcMac + register.dstMac = dst_mac + + if let packet = try? register.serializedData() { + self.logger.log("[SDLUDPHole] SendRegister: \(remoteAddress), src_mac: \(LayerPacket.MacAddress.description(data: srcMac)), dst_mac: \(LayerPacket.MacAddress.description(data: dst_mac))", level: .debug) + self.send(remoteAddress: remoteAddress, type: .register, data: packet) + } + } + + // 回复registerAck + func sendRegisterAck(context ctx: SDLContext, remoteAddress: SocketAddress, dst_mac: Data) { + var registerAck = SDLRegisterAck() + registerAck.networkID = ctx.devAddr.networkID + registerAck.srcMac = ctx.devAddr.mac + registerAck.dstMac = dst_mac + + if let packet = try? registerAck.serializedData() { + self.logger.log("[SDLUDPHole] SendRegisterAck: \(remoteAddress), \(registerAck)", level: .debug) + self.send(remoteAddress: remoteAddress, type: .registerAck, data: packet) + } + } + + // 处理写入逻辑 + private func send(remoteAddress: SocketAddress, type: SDLPacketType, data: Data) { + let message = UDPMessage(remoteAddress: remoteAddress, type: type, data: data) + self.writeContinuation.yield(message) + } + + //--MARK: 编解码器 + private static func decode(buffer: inout ByteBuffer) throws -> SDLHoleInboundMessage? { + guard let type = buffer.readInteger(as: UInt8.self), + let packetType = SDLPacketType(rawValue: type), + let bytes = buffer.readBytes(length: buffer.readableBytes) else { + return nil + } + + switch packetType { + case .data: + let dataPacket = try SDLData(serializedBytes: bytes) + return .data(dataPacket) + case .register: + let registerPacket = try SDLRegister(serializedBytes: bytes) + return .register(registerPacket) + case .registerAck: + let registerAck = try SDLRegisterAck(serializedBytes: bytes) + return .registerAck(registerAck) + case .stunReply: + let stunReply = try SDLStunReply(serializedBytes: bytes) + return .stunReply(stunReply) + case .stunProbeReply: + let stunProbeReply = try SDLStunProbeReply(serializedBytes: bytes) + return .stunProbeReply(stunProbeReply) + default: + return nil + } + } + + deinit { + try? self.group.syncShutdownGracefully() + self.writeContinuation.finish() + self.eventContinuation.finish() + } + +} diff --git a/Tun/Punchnet/SDLUtil.swift b/Tun/Punchnet/SDLUtil.swift new file mode 100644 index 0000000..69f46b9 --- /dev/null +++ b/Tun/Punchnet/SDLUtil.swift @@ -0,0 +1,52 @@ +// +// Util.swift +// Tun +// +// Created by 安礼成 on 2024/1/19. +// + +import Foundation + +struct SDLUtil { + + public static func int32ToIp(_ num: UInt32) -> String { + let ip0 = (UInt8) (num >> 24 & 0xFF) + let ip1 = (UInt8) (num >> 16 & 0xFF) + let ip2 = (UInt8) (num >> 8 & 0xFF) + let ip3 = (UInt8) (num & 0xFF) + + return "\(ip0).\(ip1).\(ip2).\(ip3)" + } + + public static func netMaskIp(maskLen: UInt8) -> String { + let len0 = 32 - maskLen + let num: UInt32 = (0xFFFFFFFF >> len0) << len0 + + let ip0 = (UInt8) (num >> 24 & 0xFF) + let ip1 = (UInt8) (num >> 16 & 0xFF) + let ip2 = (UInt8) (num >> 8 & 0xFF) + let ip3 = (UInt8) (num & 0xFF) + + return "\(ip0).\(ip1).\(ip2).\(ip3)" + } + + // 判断ip地址是否在同一个网络 + public static func inSameNetwork(ip: UInt32, compareIp: UInt32, maskLen: UInt8) -> Bool { + if ip == compareIp { + return true + } + + let len0 = 32 - maskLen + // 掩码值 + let mask: UInt32 = (0xFFFFFFFF >> len0) << len0 + + return ip & mask == compareIp & mask + } + + public static func formatMacAddress(mac: Data) -> String { + let bytes = [UInt8](mac) + + return bytes.map { String(format: "%02X", $0) }.joined(separator: ":").lowercased() + } + +} diff --git a/Tun/Punchnet/SessionManager.swift b/Tun/Punchnet/SessionManager.swift new file mode 100644 index 0000000..8fe1505 --- /dev/null +++ b/Tun/Punchnet/SessionManager.swift @@ -0,0 +1,57 @@ +// +// Session.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation +import NIOCore + +struct Session { + // 在内部的通讯的ip地址, 整数格式 + let dstMac: Data + // 对端的主机在nat上映射的端口信息 + let natAddress: SocketAddress + + // 最后使用时间 + var lastTimestamp: Int32 + + init(dstMac: Data, natAddress: SocketAddress) { + self.dstMac = dstMac + self.natAddress = natAddress + self.lastTimestamp = Int32(Date().timeIntervalSince1970) + } + + mutating func updateLastTimestamp(_ lastTimestamp: Int32) { + self.lastTimestamp = lastTimestamp + } +} + +actor SessionManager { + private var sessions: [Data:Session] = [:] + + // session的有效时间 + private let ttl: Int32 = 10 + + func getSession(toAddress: Data) -> Session? { + let timestamp = Int32(Date().timeIntervalSince1970) + if let session = self.sessions[toAddress] { + if session.lastTimestamp >= timestamp + ttl { + self.sessions[toAddress]?.updateLastTimestamp(timestamp) + return session + } else { + self.sessions.removeValue(forKey: toAddress) + } + } + return nil + } + + func addSession(session: Session) { + self.sessions[session.dstMac] = session + } + + func removeSession(dstMac: Data) { + self.sessions.removeValue(forKey: dstMac) + } + +} diff --git a/Tun/Punchnet/UIntExtension.swift b/Tun/Punchnet/UIntExtension.swift new file mode 100644 index 0000000..15056c9 --- /dev/null +++ b/Tun/Punchnet/UIntExtension.swift @@ -0,0 +1,37 @@ +// +// UIntExtension.swift +// Tun +// +// Created by 安礼成 on 2024/5/30. +// + +import Foundation + +extension UInt16 { + init(bytes: (UInt8, UInt8)) { + self = UInt16(bytes.0) << 8 + UInt16(bytes.1) + } + + init(data: Data) { + self = UInt16(data[0]) << 8 + UInt16(data[1]) + } + + func data() -> Data { + var data = Data() + + data.append(contentsOf: [UInt8(self >> 8), UInt8(self & 0x00FF)]) + + return data + } +} + +extension UInt32 { + init(bytes: (UInt8, UInt8, UInt8, UInt8)) { + self = UInt32(bytes.0) << 24 + UInt32(bytes.1) << 16 + UInt32(bytes.2) << 8 + UInt32(bytes.3) + } + + init(data: Data) { + self = UInt32(data[0]) << 24 | UInt32(data[1]) << 16 | UInt32(data[2]) << 8 | UInt32(data[3]) + } + +} diff --git a/punchnet.xcodeproj/project.pbxproj b/punchnet.xcodeproj/project.pbxproj index 94162eb..a60892e 100644 --- a/punchnet.xcodeproj/project.pbxproj +++ b/punchnet.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - C82EA7472E3FAA5A00DA5B3C /* Punchnet in Frameworks */ = {isa = PBXBuildFile; productRef = C82EA7462E3FAA5A00DA5B3C /* Punchnet */; }; C8A77F2A2DD1E77B00195617 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8A77F292DD1E77B00195617 /* NetworkExtension.framework */; }; C8A77F322DD1E77B00195617 /* Tun.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C8A77F272DD1E77B00195617 /* Tun.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; C8A77F792DD1E93900195617 /* NIO in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F782DD1E93900195617 /* NIO */; }; @@ -17,6 +16,13 @@ C8A77F812DD1E93900195617 /* NIOFoundationCompat in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F802DD1E93900195617 /* NIOFoundationCompat */; }; C8A77F882DD1EA0200195617 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F872DD1EA0200195617 /* SwiftProtobuf */; }; C8A77F8A2DD1EA0200195617 /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = C8A77F892DD1EA0200195617 /* SwiftProtobufPluginLibrary */; }; + C8AA72BB2E5C49E000E4C4E9 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72BA2E5C49E000E4C4E9 /* SwiftProtobuf */; }; + C8AA72BD2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72BC2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary */; }; + C8AA72C02E5C4A3100E4C4E9 /* NIO in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72BF2E5C4A3100E4C4E9 /* NIO */; }; + C8AA72C22E5C4A3100E4C4E9 /* NIOConcurrencyHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72C12E5C4A3100E4C4E9 /* NIOConcurrencyHelpers */; }; + C8AA72C42E5C4A3100E4C4E9 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72C32E5C4A3100E4C4E9 /* NIOCore */; }; + C8AA72C62E5C4A3100E4C4E9 /* NIOEmbedded in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72C52E5C4A3100E4C4E9 /* NIOEmbedded */; }; + C8AA72C82E5C4A3100E4C4E9 /* NIOFoundationCompat in Frameworks */ = {isa = PBXBuildFile; productRef = C8AA72C72E5C4A3100E4C4E9 /* NIOFoundationCompat */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -144,8 +150,14 @@ files = ( C8A77F2A2DD1E77B00195617 /* NetworkExtension.framework in Frameworks */, C8A77F8A2DD1EA0200195617 /* SwiftProtobufPluginLibrary in Frameworks */, - C82EA7472E3FAA5A00DA5B3C /* Punchnet in Frameworks */, + C8AA72BD2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary in Frameworks */, + C8AA72C62E5C4A3100E4C4E9 /* NIOEmbedded in Frameworks */, + C8AA72C02E5C4A3100E4C4E9 /* NIO in Frameworks */, + C8AA72C22E5C4A3100E4C4E9 /* NIOConcurrencyHelpers in Frameworks */, + C8AA72C82E5C4A3100E4C4E9 /* NIOFoundationCompat in Frameworks */, C8A77F882DD1EA0200195617 /* SwiftProtobuf in Frameworks */, + C8AA72C42E5C4A3100E4C4E9 /* NIOCore in Frameworks */, + C8AA72BB2E5C49E000E4C4E9 /* SwiftProtobuf in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -280,7 +292,13 @@ packageProductDependencies = ( C8A77F872DD1EA0200195617 /* SwiftProtobuf */, C8A77F892DD1EA0200195617 /* SwiftProtobufPluginLibrary */, - C82EA7462E3FAA5A00DA5B3C /* Punchnet */, + C8AA72BA2E5C49E000E4C4E9 /* SwiftProtobuf */, + C8AA72BC2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary */, + C8AA72BF2E5C4A3100E4C4E9 /* NIO */, + C8AA72C12E5C4A3100E4C4E9 /* NIOConcurrencyHelpers */, + C8AA72C32E5C4A3100E4C4E9 /* NIOCore */, + C8AA72C52E5C4A3100E4C4E9 /* NIOEmbedded */, + C8AA72C72E5C4A3100E4C4E9 /* NIOFoundationCompat */, ); productName = Tun; productReference = C8A77F272DD1E77B00195617 /* Tun.appex */; @@ -322,7 +340,8 @@ mainGroup = C8A77EEA2DD1E6D000195617; minimizedProjectReferenceProxies = 1; packageReferences = ( - C82EA7452E3FAA5A00DA5B3C /* XCRemoteSwiftPackageReference "swiftlib_sdlan" */, + C8AA72B92E5C49E000E4C4E9 /* XCRemoteSwiftPackageReference "swift-protobuf" */, + C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */, ); preferredProjectObjectVersion = 77; productRefGroup = C8A77EF42DD1E6D000195617 /* Products */; @@ -785,22 +804,25 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - C82EA7452E3FAA5A00DA5B3C /* XCRemoteSwiftPackageReference "swiftlib_sdlan" */ = { + C8AA72B92E5C49E000E4C4E9 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://gitea.s5s8.com/anlicheng/swiftlib_sdlan.git"; + repositoryURL = "https://github.com/apple/swift-protobuf.git"; requirement = { kind = exactVersion; - version = 2.2.2; + version = 1.30.0; + }; + }; + C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-nio.git"; + requirement = { + kind = exactVersion; + version = 2.85.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - C82EA7462E3FAA5A00DA5B3C /* Punchnet */ = { - isa = XCSwiftPackageProductDependency; - package = C82EA7452E3FAA5A00DA5B3C /* XCRemoteSwiftPackageReference "swiftlib_sdlan" */; - productName = Punchnet; - }; C8A77F782DD1E93900195617 /* NIO */ = { isa = XCSwiftPackageProductDependency; productName = NIO; @@ -829,6 +851,41 @@ isa = XCSwiftPackageProductDependency; productName = SwiftProtobufPluginLibrary; }; + C8AA72BA2E5C49E000E4C4E9 /* SwiftProtobuf */ = { + isa = XCSwiftPackageProductDependency; + package = C8AA72B92E5C49E000E4C4E9 /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobuf; + }; + C8AA72BC2E5C49E000E4C4E9 /* SwiftProtobufPluginLibrary */ = { + isa = XCSwiftPackageProductDependency; + package = C8AA72B92E5C49E000E4C4E9 /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobufPluginLibrary; + }; + C8AA72BF2E5C4A3100E4C4E9 /* NIO */ = { + isa = XCSwiftPackageProductDependency; + package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIO; + }; + C8AA72C12E5C4A3100E4C4E9 /* NIOConcurrencyHelpers */ = { + isa = XCSwiftPackageProductDependency; + package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIOConcurrencyHelpers; + }; + C8AA72C32E5C4A3100E4C4E9 /* NIOCore */ = { + isa = XCSwiftPackageProductDependency; + package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIOCore; + }; + C8AA72C52E5C4A3100E4C4E9 /* NIOEmbedded */ = { + isa = XCSwiftPackageProductDependency; + package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIOEmbedded; + }; + C8AA72C72E5C4A3100E4C4E9 /* NIOFoundationCompat */ = { + isa = XCSwiftPackageProductDependency; + package = C8AA72BE2E5C4A3100E4C4E9 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIOFoundationCompat; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = C8A77EEB2DD1E6D000195617 /* Project object */; diff --git a/punchnet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/punchnet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 13c6958..4ea8f17 100644 --- a/punchnet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/punchnet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "f895c313fc3b8bbb6ccda8f228cf023bf2259c0a044d992827796f28685daa8c", + "originHash" : "03bf3695750ad5eb9d4372b7d6478fa6d8494d0400ef6f58755e25a26a9f4d8d", "pins" : [ { "identity" : "swift-atomics", @@ -45,15 +45,6 @@ "revision" : "b63d24d465e237966c3f59f47dcac6c70fb0bca3", "version" : "1.6.1" } - }, - { - "identity" : "swiftlib_sdlan", - "kind" : "remoteSourceControl", - "location" : "https://gitea.s5s8.com/anlicheng/swiftlib_sdlan.git", - "state" : { - "revision" : "90939a68c10e52f327d2ad1e55c1a62f56f7928b", - "version" : "2.2.2" - } } ], "version" : 3 diff --git a/punchnet/Core/NoticeMessage.swift b/punchnet/Core/NoticeMessage.swift index 297d7b5..59f0350 100644 --- a/punchnet/Core/NoticeMessage.swift +++ b/punchnet/Core/NoticeMessage.swift @@ -6,45 +6,85 @@ // import Foundation +import NIOCore struct NoticeMessage { - // 消息类型 - enum NoticeType: UInt8 { - case upgrade = 1 - case alert = 2 - } - - struct UpgradeMessage: Codable { - let prompt: String - let address: String - - var binaryData: Data { - let json = try! JSONEncoder().encode(self) - var data = Data() - data.append(contentsOf: [NoticeType.upgrade.rawValue]) - data.append(json) - - return data - } - } - - struct AlertMessage: Codable { - let alert: String - - var binaryData: Data { - let json = try! JSONEncoder().encode(self) - var data = Data() - data.append(contentsOf: [NoticeType.alert.rawValue]) - data.append(json) - - return data - } - } - enum InboundMessage { case none - case upgradeMessage(UpgradeMessage) - case alertMessage(AlertMessage) + case upgradeMessage(prompt: String, address: String) + case alertMessage(alert: String) + case ip(ip: String) + } + + static func decodeMessage(buffer: inout ByteBuffer) -> InboundMessage { + guard let type = buffer.readInteger(as: UInt8.self) else { + return .none + } + + switch type { + case 0x01: + if let len0 = buffer.readInteger(as: UInt16.self), + let prompt = buffer.readString(length: Int(len0)), + let len1 = buffer.readInteger(as: UInt16.self), + let address = buffer.readString(length: Int(len1)) { + return .upgradeMessage(prompt: prompt, address: address) + } + case 0x02: + if let len0 = buffer.readInteger(as: UInt16.self), + let alert = buffer.readString(length: Int(len0)) { + return .alertMessage(alert: alert) + } + + case 0x03: + if let len0 = buffer.readInteger(as: UInt16.self), + let ipAddress = buffer.readString(length: Int(len0)) { + return .ip(ip: ipAddress) + } + default: + return .none + } + + return .none + } + + static func upgrade(prompt: String, address: String) -> Data { + var data = Data() + data.append(contentsOf: [0x01]) + + data.append(contentsOf: lenBytes(UInt16(prompt.count))) + data.append(prompt.data(using: .utf8)!) + + data.append(contentsOf: lenBytes(UInt16(address.count))) + data.append(address.data(using: .utf8)!) + + return data + } + + static func alert(alert: String) -> Data { + var data = Data() + data.append(contentsOf: [0x02]) + + data.append(contentsOf: lenBytes(UInt16(alert.count))) + data.append(alert.data(using: .utf8)!) + + return data + } + + static func ipAdress(ip: String) -> Data { + var data = Data() + data.append(contentsOf: [0x03]) + + data.append(contentsOf: lenBytes(UInt16(ip.count))) + data.append(ip.data(using: .utf8)!) + + return data + } + + private static func lenBytes(_ value: UInt16) -> [UInt8] { + let byte1 = UInt8((value >> 8) & 0xFF) + let bytes2 = UInt8(value & 0xFF) + + return [byte1, bytes2] } } diff --git a/punchnet/Core/UDPNoticeCenterServer.swift b/punchnet/Core/UDPNoticeCenterServer.swift index 3b843bd..0f5592d 100644 --- a/punchnet/Core/UDPNoticeCenterServer.swift +++ b/punchnet/Core/UDPNoticeCenterServer.swift @@ -43,26 +43,9 @@ final class UDPNoticeCenterServer: ChannelInboundHandler { public func channelRead(context: ChannelHandlerContext, data: NIOAny) { let envelope = self.unwrapInboundIn(data) var buffer = envelope.data - guard let type = buffer.readInteger(as: UInt8.self), - let noticeType = NoticeMessage.NoticeType(rawValue: type), - let bytes = buffer.readBytes(length: buffer.readableBytes) else { - return - } - switch noticeType { - case .upgrade: - if let upgradeMessage = try? JSONDecoder().decode(NoticeMessage.UpgradeMessage.self, from: Data(bytes)) { - DispatchQueue.main.async { - self.messageFlow.send(.upgradeMessage(upgradeMessage)) - } - } - case .alert: - if let alertMessage = try? JSONDecoder().decode(NoticeMessage.AlertMessage.self, from: Data(bytes)) { - DispatchQueue.main.async { - self.messageFlow.send(.alertMessage(alertMessage)) - } - } - } + let notice = NoticeMessage.decodeMessage(buffer: &buffer) + self.messageFlow.send(notice) } public func channelReadComplete(context: ChannelHandlerContext) { diff --git a/punchnet/Views/IndexView.swift b/punchnet/Views/IndexView.swift index ba7dd03..6d88fdc 100644 --- a/punchnet/Views/IndexView.swift +++ b/punchnet/Views/IndexView.swift @@ -167,10 +167,10 @@ struct IndexView: View { } .alert(isPresented: $showStunAlert) { switch self.message { - case .upgradeMessage(let upgradeMessage): - Alert(title: Text(upgradeMessage.prompt)) - case .alertMessage(let alertMessage): - Alert(title: Text(alertMessage.alert)) + case .upgradeMessage(let prompt, _): + Alert(title: Text(prompt)) + case .alertMessage(let alert): + Alert(title: Text(alert)) default: Alert(title: Text("")) }