diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6234f05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +punchnet.xcodeproj/* diff --git a/Tun/NetworkInterface.swift b/Tun/NetworkInterface.swift index b6ac7fa..8455287 100644 --- a/Tun/NetworkInterface.swift +++ b/Tun/NetworkInterface.swift @@ -1,3 +1,11 @@ +// +// NetworkInterface.swift +// punchnet +// +// Created by 安礼成 on 2025/8/3. +// + + // // NetworkInterface.swift // Tun @@ -7,13 +15,13 @@ import Foundation -struct NetworkInterface { - let name: String - let ip: String - let netmask: String +public struct NetworkInterface { + public let name: String + public let ip: String + public let netmask: String } -struct NetworkInterfaceManager { +public struct NetworkInterfaceManager { /** 获取网卡信息, (let name: String let ip: String let netmask: String) */ @@ -44,6 +52,7 @@ struct NetworkInterfaceManager { if (getnameinfo(&addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) == 0) { let address = String(cString: hostname) + let name = ptr!.pointee.ifa_name! let ifname = String(cString: name) diff --git a/Tun/PacketTunnelProvider.swift b/Tun/PacketTunnelProvider.swift index dd82fde..296e7da 100644 --- a/Tun/PacketTunnelProvider.swift +++ b/Tun/PacketTunnelProvider.swift @@ -1,3 +1,11 @@ +// +// PacketTunnelProvider.swift +// punchnet +// +// Created by 安礼成 on 2025/8/3. +// + + // // PacketTunnelProvider.swift // Tun @@ -9,6 +17,7 @@ import NetworkExtension class PacketTunnelProvider: NEPacketTunnelProvider { var context: SDLContext? + private var rootTask: Task? override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { // host: "192.168.0.101", port: 1265 @@ -16,38 +25,80 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return } - let token = options["token"] as! String - //let version = options["version"] as! Int - let installed_channel = options["installed_channel"] as! String + // 如果当前在运行状态,不允许重复请求 + guard self.context == nil else { + return + } + // let token = options["token"] as! String + let installed_channel = options["installed_channel"] as! String let superIp = options["super_ip"] as! String - Task { - SDLLogger.logLevel = .debug + let superPort = options["super_port"] as! Int + let stunServersStr = options["stun_servers"] as! String + let noticePort = options["notice_port"] as! Int + let token = options["token"] as! String + let networkCode = options["network_code"] as! String + let clientId = options["client_id"] as! String + let remoteDnsServer = options["remote_dns_server"] as! String + let hostname = options["hostname"] as! String + + let stunServers = stunServersStr.split(separator: ";").compactMap { server -> SDLConfiguration.StunServer? in + let parts = server.split(separator: ":", maxSplits: 2) + guard parts.count == 2 else { + return nil + } + + let ports = parts[1].split(separator: ",", maxSplits: 2) + guard ports.count == 2, let port1 = Int(String(ports[0])), let port2 = Int(String(ports[1])) else { + return nil + } + + return .init(host: String(parts[0]), ports: [port1, port2]) + } + + guard stunServers.count >= 2 else { + NSLog("stunServers配置错误") + return + } + + NSLog("[PacketTunnelProvider] client_id: \(clientId), token: \(token), network_code: \(networkCode)") + + let config = SDLConfiguration(version: 1, + installedChannel: installed_channel, + superHost: superIp, + superPort: superPort, + stunServers: stunServers, + clientId: clientId, + noticePort: noticePort, + token: token, + networkCode: networkCode, + remoteDnsServer: remoteDnsServer, + hostname: hostname) + // 加密算法 + let rsaCipher = try! CCRSACipher(keySize: 1024) + let aesChiper = CCAESChiper() + + self.rootTask = Task { do { - self.context = try SDLContext(provider: self, config: .init( - version: 1, - installedChannel: installed_channel, - //superHost: "118.178.229.213", - superHost: superIp, - superPort: 18083, - stunServers: [.init(host: superIp, ports: [1265, 1266]), .init(host: "118.178.229.213", ports: [1265, 1266])], - clientId: SDLContext.getUUID(), - token: "" - //token: token - )) - + self.context = SDLContext(provider: self, config: config, rsaCipher: rsaCipher, aesCipher: aesChiper, logger: SDLLogger(level: .debug)) try await self.context?.start() - completionHandler(nil) } catch let err { - NSLog("SDLContext start get error: \(err)") - - completionHandler(err) + NSLog("[PacketTunnelProvider] exit with error: \(err)") + exit(-1) } } + completionHandler(nil) } override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { // Add code here to start the process of stopping the tunnel. + self.rootTask?.cancel() + Task { + await self.context?.stop() + } + self.context = nil + self.rootTask = nil + completionHandler() } @@ -78,4 +129,51 @@ extension PacketTunnelProvider { return interfaces.first {$0.name == "en0"} }() + struct CCRSACipher: RSACipher { + var pubKey: String + let privateKeyDER: Data + + init(keySize: Int) throws { + let (privateKey, publicKey) = try Self.loadKeys(keySize: keySize) + let privKeyStr = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKey) + + self.pubKey = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey) + self.privateKeyDER = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privKeyStr) + } + + public func decode(data: Data) throws -> Data { + let tag = Data() + let (decryptedData, _) = try CC.RSA.decrypt(data, derKey: self.privateKeyDER, tag: tag, padding: .pkcs1, digest: .none) + + return decryptedData + } + + private static func loadKeys(keySize: Int) throws -> (Data, Data) { + if let privateKey = UserDefaults.standard.data(forKey: "privateKey"), + let publicKey = UserDefaults.standard.data(forKey: "publicKey") { + + return (privateKey, publicKey) + } else { + let (privateKey, publicKey) = try CC.RSA.generateKeyPair(keySize) + UserDefaults.standard.setValue(privateKey, forKey: "privateKey") + UserDefaults.standard.setValue(publicKey, forKey: "publicKey") + + return (privateKey, publicKey) + } + } + } + + struct CCAESChiper: AESCipher { + func decypt(aesKey: Data, data: Data) throws -> Data { + let ivData = Data(aesKey.prefix(16)) + return try CC.crypt(.decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData) + } + + func encrypt(aesKey: Data, data: Data) throws -> Data { + let ivData = Data(aesKey.prefix(16)) + + return try CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData) + } + } + } 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/ARPPacket.swift b/Tun/Punchnet/ARPPacket.swift similarity index 80% rename from Tun/ARPPacket.swift rename to Tun/Punchnet/ARPPacket.swift index ac3e9f6..4694f15 100644 --- a/Tun/ARPPacket.swift +++ b/Tun/Punchnet/ARPPacket.swift @@ -6,7 +6,16 @@ // import Foundation -struct ARPPacket { +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)) +""" + } + + static let broadcastMac = Data([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + // ARP操作码 enum Opcode: UInt16 { case request = 0x01 @@ -31,8 +40,15 @@ struct ARPPacket { 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) { + 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 @@ -47,7 +63,6 @@ struct ARPPacket { init?(data: Data) { guard data.count >= 28 else { - NSLog("length < 28: len: \(data.count)") return nil } @@ -56,7 +71,6 @@ struct ARPPacket { self.hardwareSize = data[4] self.protocolSize = data[5] guard let opcode = Opcode(rawValue: UInt16(data[6]) << 8 | UInt16(data[7])) else { - NSLog("opcode error") return nil } @@ -82,6 +96,10 @@ struct ARPPacket { return data } + static func isBroadcastMac(_ macAddress: Data) -> Bool { + return macAddress == broadcastMac + } + } extension ARPPacket { diff --git a/Tun/Punchnet/Actors/SDLDNSClientActor.swift b/Tun/Punchnet/Actors/SDLDNSClientActor.swift new file mode 100644 index 0000000..aec9151 --- /dev/null +++ b/Tun/Punchnet/Actors/SDLDNSClientActor.swift @@ -0,0 +1,117 @@ +// +// DNSClient.swift +// Tun +// +// Created by 安礼成 on 2025/12/10. +// + +import Foundation +import NIOCore +import NIOPosix + +// 处理和sn-server服务器之间的通讯 +@available(macOS 14, *) +actor SDLDNSClientActor { + private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private let asyncChannel: NIOAsyncChannel, AddressedEnvelope> + private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: Data.self, bufferingPolicy: .unbounded) + + private let logger: SDLLogger + private let dnsServerAddress: SocketAddress + + public let packetFlow: AsyncStream + private let packetContinuation: AsyncStream.Continuation + + // 启动函数 + init(dnsServerAddress: SocketAddress, logger: SDLLogger) async throws { + self.dnsServerAddress = dnsServerAddress + self.logger = logger + + (self.packetFlow, self.packetContinuation) = AsyncStream.makeStream(of: Data.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() + } + + func start() async throws { + try await withTaskCancellationHandler { + try await self.asyncChannel.executeThenClose {inbound, outbound in + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + defer { + self.logger.log("[DNSClient] inbound closed", level: .warning) + } + + for try await envelope in inbound { + try Task.checkCancellation() + var buffer = envelope.data + let remoteAddress = envelope.remoteAddress + self.logger.log("[DNSClient] read data: \(buffer), from: \(remoteAddress)", level: .debug) + + let len = buffer.readableBytes + if let bytes = buffer.readBytes(length: len) { + self.packetContinuation.yield(Data(bytes)) + } + } + } + + group.addTask { + defer { + self.logger.log("[DNSClient] outbound closed", level: .warning) + } + + for await message in self.writeStream { + try Task.checkCancellation() + + let buffer = self.asyncChannel.channel.allocator.buffer(bytes: message) + let envelope = AddressedEnvelope(remoteAddress: self.dnsServerAddress, data: buffer) + try await outbound.write(envelope) + } + } + + if let _ = try await group.next() { + group.cancelAll() + } + } + } + } onCancel: { + self.writeContinuation.finish() + self.packetContinuation.finish() + self.logger.log("[DNSClient] withTaskCancellationHandler cancel") + } + } + + func forward(ipPacket: IPPacket) { + self.writeContinuation.yield(ipPacket.data) + } + + deinit { + try? self.group.syncShutdownGracefully() + self.writeContinuation.finish() + } + +} + +extension SDLDNSClientActor { + + struct Helper { + static let dnsServer: String = "100.100.100.100" + // dns请求包的目标地址 + static let dnsDestIpAddr: UInt32 = 1684300900 + + // 判断是否是dns请求的数据包 + static func isDnsRequestPacket(ipPacket: IPPacket) -> Bool { + return ipPacket.header.destination == dnsDestIpAddr + } + } + +} diff --git a/Tun/Punchnet/Actors/SDLPuncherActor.swift b/Tun/Punchnet/Actors/SDLPuncherActor.swift new file mode 100644 index 0000000..7141f5d --- /dev/null +++ b/Tun/Punchnet/Actors/SDLPuncherActor.swift @@ -0,0 +1,89 @@ +// +// SDLPuncherActor.swift +// Tun +// +// Created by 安礼成 on 2026/1/7. +// + +import Foundation + +actor SDLPuncherActor { + // dstMac + private var coolingDown: Set = [] + private let cooldown: Duration = .seconds(5) + + private var superClientActor: SDLSuperClientActor? + private var udpHoleActor: SDLUDPHoleActor? + + // 处理holer + private var logger: SDLLogger + + struct RegisterRequest { + let srcMac: Data + let dstMac: Data + let networkId: UInt32 + } + + init(logger: SDLLogger) { + self.logger = logger + } + + func setSuperClientActor(superClientActor: SDLSuperClientActor?) { + self.superClientActor = superClientActor + } + + func setUDPHoleActor(udpHoleActor: SDLUDPHoleActor?) { + self.udpHoleActor = udpHoleActor + } + + func submitRegisterRequest(request: RegisterRequest) { + let dstMac = request.dstMac + + guard !coolingDown.contains(dstMac) else { + return + } + + // 触发一次打洞 + coolingDown.insert(dstMac) + + Task { + await self.tryHole(request: request) + // 启动冷却期 + try? await Task.sleep(for: .seconds(5)) + self.endCooldown(for: dstMac) + } + } + + private func endCooldown(for key: Data) { + self.coolingDown.remove(key) + } + + private func tryHole(request: RegisterRequest) async { + var queryInfo = SDLQueryInfo() + queryInfo.dstMac = request.dstMac + guard let message = try? await self.superClientActor?.request(type: .queryInfo, data: try queryInfo.serializedData()) 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包 + var register = SDLRegister() + register.networkID = request.networkId + register.srcMac = request.srcMac + register.dstMac = request.dstMac + + await self.udpHoleActor?.send(type: .register, data: try! register.serializedData(), remoteAddress: remoteAddress) + } 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) + } + } + +} diff --git a/Tun/Punchnet/Actors/SDLSuperClientActor.swift b/Tun/Punchnet/Actors/SDLSuperClientActor.swift new file mode 100644 index 0000000..f8ef759 --- /dev/null +++ b/Tun/Punchnet/Actors/SDLSuperClientActor.swift @@ -0,0 +1,312 @@ +// +// SDLWebsocketClient.swift +// Tun +// +// Created by 安礼成 on 2024/3/28. +// + +import Foundation +import NIOCore +import NIOPosix + +// --MARK: 和SuperNode的客户端 +actor SDLSuperClientActor { + // 发送的消息格式 + private typealias TcpMessage = (packetId: UInt32, type: SDLPacketType, data: Data) + + private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private let asyncChannel: NIOAsyncChannel + private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: TcpMessage.self, bufferingPolicy: .unbounded) + private var continuations: [UInt32:CheckedContinuation] = [:] + + public let eventFlow: AsyncStream + private let inboundContinuation: AsyncStream.Continuation + + // id生成器 + var idGenerator = SDLIdGenerator(seed: 1) + + private let logger: SDLLogger + + // 定义事件类型 + enum SuperEvent { + case ready + case event(SDLEvent) + case command(UInt32, SDLCommand) + } + + enum SuperClientError: Error { + case timeout + case connectionClosed + case cancelled + } + + 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) { + if !message.isPong() { + self.logger.log("[SDLSuperClient] 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 (packetId, type, data) in self.writeStream { + try Task.checkCancellation() + + var buffer = self.asyncChannel.channel.allocator.buffer(capacity: data.count + 5) + buffer.writeInteger(packetId, as: UInt32.self) + buffer.writeBytes([type.rawValue]) + buffer.writeBytes(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") + Task { + await self.failAllContinuations(SuperClientError.cancelled) + } + } + } + + // -- MARK: apis + func unregister() throws { + self.send(type: .unregisterSuper, packetId: 0, data: Data()) + } + + private func ping() { + self.send(type: .ping, packetId: 0, data: Data()) + } + + func request(type: SDLPacketType, data: Data, timeout: Duration = .seconds(5)) async throws -> SDLSuperInboundMessage { + let packetId = idGenerator.nextId() + + return try await withCheckedThrowingContinuation { cont in + self.continuations[packetId] = cont + self.writeContinuation.yield(TcpMessage(packetId: packetId, type: type, data: data)) + Task { + try? await Task.sleep(for: timeout) + self.timeout(packetId: packetId) + } + } + } + + func send(type: SDLPacketType, packetId: UInt32, data: Data) { + self.writeContinuation.yield(TcpMessage(packetId: packetId, type: type, data: data)) + } + + // 处理回调函数 + private func fireCallback(message: SDLSuperInboundMessage) { + guard let cont = self.continuations.removeValue(forKey: message.msgId) else { + return + } + cont.resume(returning: message) + } + + private func failAllContinuations(_ error: Error) { + let all = continuations + continuations.removeAll() + + for (_, cont) in all { + cont.resume(throwing: error) + } + } + + private func timeout(packetId: UInt32) { + guard let cont = self.continuations.removeValue(forKey: packetId) else { + return + } + cont.resume(throwing: SuperClientError.timeout) + } + + 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/Actors/SDLTunnelProviderActor.swift b/Tun/Punchnet/Actors/SDLTunnelProviderActor.swift new file mode 100644 index 0000000..b5ffbad --- /dev/null +++ b/Tun/Punchnet/Actors/SDLTunnelProviderActor.swift @@ -0,0 +1,89 @@ +// +// SDLContext.swift +// Tun +// +// Created by 安礼成 on 2024/2/29. +// + +import Foundation +import NetworkExtension +import NIOCore +import Combine + +// 上下文环境变量,全局共享 +/* +1. 处理rsa的加解密逻辑 + */ + +actor SDLTunnelProviderActor { + + // 路由信息 + struct Route { + let dstAddress: String + let subnetMask: String + + var debugInfo: String { + return "\(dstAddress):\(subnetMask)" + } + } + + // 数据包读取任务 + private var readTask: Task<(), Never>? + + let provider: NEPacketTunnelProvider + let logger: SDLLogger + + public init(provider: NEPacketTunnelProvider, logger: SDLLogger) { + self.logger = logger + self.provider = provider + } + + func writePackets(packets: [NEPacket]) { + //let packet = NEPacket(data: ipPacket.data, protocolFamily: 2) + self.provider.packetFlow.writePacketObjects(packets) + } + + // 网络改变时需要重新配置网络信息 + func setNetworkSettings(devAddr: SDLDevAddr, dnsServer: String) async throws -> String { + let netAddress = SDLNetAddress(ip: devAddr.netAddr, maskLen: UInt8(devAddr.netBitLen)) + let routes = [ + Route(dstAddress: netAddress.networkAddress, subnetMask: netAddress.maskAddress), + Route(dstAddress: dnsServer, subnetMask: "255.255.255.255") + ] + + // Add code here to start the process of connecting the tunnel. + let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "8.8.8.8") + networkSettings.mtu = 1460 + + // 设置网卡的DNS解析 + + let networkDomain = devAddr.networkDomain + let dnsSettings = NEDNSSettings(servers: [dnsServer]) + dnsSettings.searchDomains = [networkDomain] + dnsSettings.matchDomains = [networkDomain] + dnsSettings.matchDomainsNoSearch = false + networkSettings.dnsSettings = dnsSettings + 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 + // 网卡配置设置必须成功 + try await self.provider.setTunnelNetworkSettings(networkSettings) + + return netAddress.ipAddress + } + + // 开始读取数据, 用单独的线程处理packetFlow + func readPackets() async -> [Data] { + let (packets, numbers) = await self.provider.packetFlow.readPackets() + return zip(packets, numbers).compactMap { (data, number) in + return number == 2 ? data : nil + } + } + +} diff --git a/Tun/Punchnet/Actors/SDLUDPHoleActor.swift b/Tun/Punchnet/Actors/SDLUDPHoleActor.swift new file mode 100644 index 0000000..497d8ad --- /dev/null +++ b/Tun/Punchnet/Actors/SDLUDPHoleActor.swift @@ -0,0 +1,210 @@ +// +// SDLanServer.swift +// Tun +// +// Created by 安礼成 on 2024/1/31. +// + +import Foundation +import NIOCore +import NIOPosix + +// 处理和sn-server服务器之间的通讯 +actor SDLUDPHoleActor { + 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 Capabilities { + let logger: @Sendable (String) async -> Void + + } + + 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") + } + } + + func getCookieId() -> UInt32 { + return self.cookieGenerator.nextId() + } + + // 探测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( type: .stunProbe, data: try! stunProbe.serializedData(), remoteAddress: remoteAddress) + 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 + // 处理写入逻辑 + func send(type: SDLPacketType, data: Data, remoteAddress: SocketAddress) { + 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/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/DataExtension.swift b/Tun/Punchnet/DataExtension.swift similarity index 100% rename from Tun/DataExtension.swift rename to Tun/Punchnet/DataExtension.swift diff --git a/Tun/IPPacket.swift b/Tun/Punchnet/IPPacket.swift similarity index 95% rename from Tun/IPPacket.swift rename to Tun/Punchnet/IPPacket.swift index e136525..89543ab 100644 --- a/Tun/IPPacket.swift +++ b/Tun/Punchnet/IPPacket.swift @@ -54,7 +54,6 @@ enum TransportProtocol: UInt8 { case icmp = 1 case tcp = 6 case udp = 17 - } struct IPPacket { @@ -80,4 +79,8 @@ struct IPPacket { self.data = data } + // 获取负载部分 + func getPayload() -> Data { + return data.subdata(in: 20.. Data +} diff --git a/Tun/Punchnet/SDLConfiguration.swift b/Tun/Punchnet/SDLConfiguration.swift new file mode 100644 index 0000000..4c8ee52 --- /dev/null +++ b/Tun/Punchnet/SDLConfiguration.swift @@ -0,0 +1,72 @@ +// +// 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 remoteDnsServer: String + let hostname: String + + 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 + let networkCode: String + + public init(version: UInt8, installedChannel: String, superHost: String, superPort: Int, stunServers: [StunServer], clientId: String, noticePort: Int, token: String, networkCode: String, remoteDnsServer: String, hostname: 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 + self.networkCode = networkCode + self.remoteDnsServer = remoteDnsServer + self.hostname = hostname + } + +} diff --git a/Tun/Punchnet/SDLContext.swift b/Tun/Punchnet/SDLContext.swift new file mode 100644 index 0000000..398378c --- /dev/null +++ b/Tun/Punchnet/SDLContext.swift @@ -0,0 +1,715 @@ +// +// 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 { + + // 路由信息 + 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: NatType = .blocked + + // AES加密,授权通过后,对象才会被创建 + var aesCipher: AESCipher + + // aes + var aesKey: Data = Data() + + // rsa的相关配置, public_key是本地生成的 + let rsaCipher: RSACipher + + // 依赖的变量 + var udpHoleActor: SDLUDPHoleActor? + var superClientActor: SDLSuperClientActor? + var providerActor: SDLTunnelProviderActor + var puncherActor: SDLPuncherActor + // dns的client对象 + var dnsClientActor: SDLDNSClientActor? + + // 数据包读取任务 + private var readTask: Task<(), Never>? + + 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? + + private let logger: SDLLogger + private var rootTask: Task? + + 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.sessionManager = SessionManager() + self.arpServer = ArpServer(known_macs: [:]) + self.providerActor = SDLTunnelProviderActor(provider: provider, logger: logger) + self.puncherActor = SDLPuncherActor(logger: logger) + } + + 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.startDnsClient() + } 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.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.superClientActor = nil + self.udpHoleActor = 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.udpHoleActor = try await SDLUDPHoleActor(logger: self.logger) + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + try await self.udpHoleActor?.start() + } + + group.addTask { + while !Task.isCancelled { + try Task.checkCancellation() + try await Task.sleep(nanoseconds: 5 * 1_000_000_000) + try Task.checkCancellation() + + if let udpHoleActor = self.udpHoleActor { + let cookie = await udpHoleActor.getCookieId() + var stunRequest = SDLStunRequest() + stunRequest.cookie = cookie + stunRequest.clientID = self.config.clientId + stunRequest.networkID = self.devAddr.networkID + stunRequest.ip = self.devAddr.netAddr + stunRequest.mac = self.devAddr.mac + stunRequest.natType = UInt32(self.natType.rawValue) + + let remoteAddress = self.config.stunSocketAddress + await udpHoleActor.send(type: .stunRequest, data: try stunRequest.serializedData(), remoteAddress: remoteAddress) + self.lastCookie = cookie + } + } + } + + group.addTask { + if let eventFlow = self.udpHoleActor?.eventFlow { + for try await event in eventFlow { + try Task.checkCancellation() + try await self.handleUDPEvent(event: event) + } + } + } + + if let _ = try await group.next() { + group.cancelAll() + } + } + + } + + private func startSuperClient() async throws { + self.superClientActor = try await SDLSuperClientActor(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.superClientActor?.start() + } + + group.addTask { + if let eventFlow = self.superClientActor?.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 self.getNatType() + self.logger.log("didNetworkPathChanged, nat type is: \(self.natType)", level: .info) + case .unreachable: + self.logger.log("didNetworkPathUnreachable", level: .warning) + } + } + } + + private func startDnsClient() async throws { + let remoteDnsServer = config.remoteDnsServer + let dnsSocketAddress = try SocketAddress.makeAddressResolvingHost(remoteDnsServer, port: 15353) + self.dnsClientActor = try await SDLDNSClientActor(dnsServerAddress: dnsSocketAddress, logger: self.logger) + + try await withThrowingTaskGroup(of: Void.self) { group in + defer { + self.logger.log("[SDLContext] dns client task cancel", level: .warning) + } + + group.addTask { + try await self.dnsClientActor?.start() + } + + group.addTask { + if let packetFlow = self.dnsClientActor?.packetFlow { + for await packet in packetFlow { + let nePacket = NEPacket(data: packet, protocolFamily: 2) + await self.providerActor.writePackets(packets: [nePacket]) + } + } + + } + + if let _ = try await group.next() { + group.cancelAll() + } + } + } + + private func handleSuperEvent(event: SDLSuperClientActor.SuperEvent) async throws { + switch event { + case .ready: + await self.puncherActor.setSuperClientActor(superClientActor: self.superClientActor) + + self.logger.log("[SDLContext] get registerSuper, mac address: \(SDLUtil.formatMacAddress(mac: self.devAddr.mac))", level: .debug) + var registerSuper = SDLRegisterSuper() + registerSuper.version = UInt32(self.config.version) + registerSuper.clientID = self.config.clientId + registerSuper.devAddr = self.devAddr + registerSuper.pubKey = self.rsaCipher.pubKey + registerSuper.token = self.config.token + registerSuper.networkCode = self.config.networkCode + registerSuper.hostname = self.config.hostname + guard let message = try await self.superClientActor?.request(type: .registerSuper, data: try registerSuper.serializedData()) 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), network_id:\(registerSuperAck.devAddr.networkID)", 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网卡信息 + do { + let ipAddress = try await self.providerActor.setNetworkSettings(devAddr: self.devAddr, dnsServer: SDLDNSClientActor.Helper.dnsServer) + await self.noticeClient?.send(data: NoticeMessage.ipAdress(ip: ipAddress)) + + self.startReader() + } catch let err { + self.logger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error) + exit(-1) + } + + 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包 + var register = SDLRegister() + register.networkID = self.devAddr.networkID + register.srcMac = self.devAddr.mac + register.dstMac = sendRegisterEvent.dstMac + await self.udpHoleActor?.send(type: .register, data: try register.serializedData(), remoteAddress: remoteAddress) + } + + 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网卡信息 + do { + let ipAddress = try await self.providerActor.setNetworkSettings(devAddr: self.devAddr, dnsServer: SDLDNSClientActor.Helper.dnsServer) + await self.noticeClient?.send(data: NoticeMessage.ipAdress(ip: ipAddress)) + + self.startReader() + } catch let err { + self.logger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error) + exit(-1) + } + + self.aesKey = aesKey + + var commandAck = SDLCommandAck() + commandAck.status = true + await self.superClientActor?.send(type: .commandAck, packetId: packetId, data: try commandAck.serializedData()) + } + } + + } + + private func handleUDPEvent(event: SDLUDPHoleActor.UDPEvent) async throws { + switch event { + case .ready: + await self.puncherActor.setUDPHoleActor(udpHoleActor: self.udpHoleActor) + // 获取当前网络的类型 + self.natType = await getNatType() + self.logger.log("[SDLContext] broadcast 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包 + var registerAck = SDLRegisterAck() + registerAck.networkID = self.devAddr.networkID + registerAck.srcMac = self.devAddr.mac + registerAck.dstMac = register.srcMac + + await self.udpHoleActor?.send(type: .registerAck, data: try registerAck.serializedData(), remoteAddress: remoteAddress) + // 这里需要建立到来源的会话, 在复杂网络下,通过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) + await self.providerActor.writePackets(packets: [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) +// } +// } +// } +// } + + // 开始读取数据, 用单独的线程处理packetFlow + private func startReader() { + // 停止之前的任务 + self.readTask?.cancel() + + // 开启新的任务 + self.readTask = Task(priority: .high) { + repeat { + let packets = await self.providerActor.readPackets() + for packet in packets { + await self.dealPacket(data: packet) + } + } while true + } + } + + // 处理读取的每个数据包 + private func dealPacket(data: Data) async { + guard let packet = IPPacket(data) else { + return + } + + if SDLDNSClientActor.Helper.isDnsRequestPacket(ipPacket: packet) { + let destIp = packet.header.destination_ip + self.logger.log("[DNSQuery] destIp: \(destIp), int: \(packet.header.destination.asIpAddress())", level: .debug) + await self.dnsClientActor?.forward(ipPacket: packet) + } + else { + Task.detached { + let dstIp = packet.header.destination + // 本地通讯, 目标地址是本地服务器的ip地址 + if dstIp == self.devAddr.netAddr { + let nePacket = NEPacket(data: packet.data, protocolFamily: 2) + await self.providerActor.writePackets(packets: [nePacket]) + return + } + + // 查找arp缓存中是否有目标mac地址 + if let dstMac = await self.arpServer.query(ip: dstIp) { + await self.routeLayerPacket(dstMac: dstMac, type: .ipv4, data: packet.data) + } + else { + self.logger.log("[SDLContext] dstIp: \(dstIp.asIpAddress()) arp query not found, broadcast", level: .debug) + // 构造arp广播 + let arpReqeust = ARPPacket.arpRequest(senderIP: self.devAddr.netAddr, senderMAC: self.devAddr.mac, targetIP: dstIp) + await self.routeLayerPacket(dstMac: ARPPacket.broadcastMac , type: .arp, data: arpReqeust.marshal()) + } + } + } + } + + 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 + } + + // 构造数据包 + var dataPacket = SDLData() + dataPacket.networkID = self.devAddr.networkID + dataPacket.srcMac = self.devAddr.mac + dataPacket.dstMac = dstMac + dataPacket.ttl = 255 + dataPacket.data = encodedPacket + + let data = try! dataPacket.serializedData() + // 广播地址不要去尝试打洞 + if ARPPacket.isBroadcastMac(dstMac) { + // 通过super_node进行转发 + await self.udpHoleActor?.send(type: .data, data: data, remoteAddress: self.config.stunSocketAddress) + } + else { + // 通过session发送到对端 + if let session = await self.sessionManager.getSession(toAddress: dstMac) { + self.logger.log("[SDLContext] send packet by session: \(session)", level: .debug) + await self.udpHoleActor?.send(type: .data, data: data, remoteAddress: session.natAddress) + await self.flowTracer.inc(num: data.count, type: .p2p) + } + else { + // 通过super_node进行转发 + await self.udpHoleActor?.send(type: .data, data: data, remoteAddress: self.config.stunSocketAddress) + // 流量统计 + await self.flowTracer.inc(num: data.count, type: .forward) + // 尝试打洞 + await self.puncherActor.submitRegisterRequest(request: .init(srcMac: self.devAddr.mac, dstMac: dstMac, networkId: self.devAddr.networkID)) + } + } + } + + deinit { + self.rootTask?.cancel() + self.udpHoleActor = nil + self.superClientActor = nil + self.dnsClientActor = nil + } + + // 获取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) + } + +} + +// 网络类型探测 +extension SDLContext { + // 定义nat类型 + enum NatType: UInt8, Encodable { + case blocked = 0 + case noNat = 1 + case fullCone = 2 + case portRestricted = 3 + case coneRestricted = 4 + case symmetric = 5 + } + + // 获取当前所处的网络的nat类型 + func getNatType() async -> NatType { + guard let udpHole = self.udpHoleActor 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 func getNatAddress(_ udpHole: SDLUDPHoleActor, remoteAddress: SocketAddress, attr: SDLProbeAttr) async -> SocketAddress? { + let stunProbeReply = try? await udpHole.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: 5) + return stunProbeReply?.socketAddress() + } + +} + +private extension UInt32 { + // 转换成ip地址 + func asIpAddress() -> String { + return SDLUtil.int32ToIp(self) + } +} 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/SDLFlowTracerActor.swift b/Tun/Punchnet/SDLFlowTracerActor.swift similarity index 100% rename from Tun/SDLFlowTracerActor.swift rename to Tun/Punchnet/SDLFlowTracerActor.swift 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/SDLMessage.pb.swift b/Tun/Punchnet/SDLMessage.pb.swift similarity index 95% rename from Tun/SDLMessage.pb.swift rename to Tun/Punchnet/SDLMessage.pb.swift index 60cfbd7..4c750c5 100644 --- a/Tun/SDLMessage.pb.swift +++ b/Tun/Punchnet/SDLMessage.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: sdlan_pb.proto @@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -struct SDLV4Info { +struct SDLV4Info: @unchecked Sendable { // 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. @@ -36,7 +37,7 @@ struct SDLV4Info { init() {} } -struct SDLV6Info { +struct SDLV6Info: @unchecked Sendable { // 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. @@ -51,7 +52,7 @@ struct SDLV6Info { } /// 设备网络地址信息 -struct SDLDevAddr { +struct SDLDevAddr: @unchecked Sendable { // 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. @@ -64,13 +65,15 @@ struct SDLDevAddr { var netBitLen: UInt32 = 0 + var networkDomain: String = String() + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} } /// tcp通讯消息 -struct SDLEmpty { +struct SDLEmpty: Sendable { // 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. @@ -80,7 +83,7 @@ struct SDLEmpty { init() {} } -struct SDLRegisterSuper { +struct SDLRegisterSuper: Sendable { // 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. @@ -104,6 +107,10 @@ struct SDLRegisterSuper { var token: String = String() + var networkCode: String = String() + + var hostname: String = String() + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -111,7 +118,7 @@ struct SDLRegisterSuper { fileprivate var _devAddr: SDLDevAddr? = nil } -struct SDLRegisterSuperAck { +struct SDLRegisterSuperAck: @unchecked Sendable { // 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. @@ -156,7 +163,7 @@ struct SDLRegisterSuperAck { fileprivate var _upgradeAddress: String? = nil } -struct SDLRegisterSuperNak { +struct SDLRegisterSuperNak: Sendable { // 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. @@ -170,7 +177,7 @@ struct SDLRegisterSuperNak { init() {} } -struct SDLQueryInfo { +struct SDLQueryInfo: @unchecked Sendable { // 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. @@ -182,7 +189,7 @@ struct SDLQueryInfo { init() {} } -struct SDLPeerInfo { +struct SDLPeerInfo: @unchecked Sendable { // 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. @@ -198,8 +205,6 @@ struct SDLPeerInfo { /// 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} @@ -217,7 +222,7 @@ struct SDLPeerInfo { fileprivate var _v6Info: SDLV6Info? = nil } -struct SDLNatChangedEvent { +struct SDLNatChangedEvent: @unchecked Sendable { // 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. @@ -231,7 +236,7 @@ struct SDLNatChangedEvent { init() {} } -struct SDLSendRegisterEvent { +struct SDLSendRegisterEvent: @unchecked Sendable { // 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. @@ -242,6 +247,8 @@ struct SDLSendRegisterEvent { var natPort: UInt32 = 0 + var natType: UInt32 = 0 + var v6Info: SDLV6Info { get {return _v6Info ?? SDLV6Info()} set {_v6Info = newValue} @@ -258,7 +265,7 @@ struct SDLSendRegisterEvent { fileprivate var _v6Info: SDLV6Info? = nil } -struct SDLNetworkShutdownEvent { +struct SDLNetworkShutdownEvent: Sendable { // 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. @@ -270,7 +277,7 @@ struct SDLNetworkShutdownEvent { init() {} } -struct SDLChangeNetworkCommand { +struct SDLChangeNetworkCommand: @unchecked Sendable { // 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. @@ -293,7 +300,7 @@ struct SDLChangeNetworkCommand { fileprivate var _devAddr: SDLDevAddr? = nil } -struct SDLCommandAck { +struct SDLCommandAck: Sendable { // 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. @@ -317,7 +324,7 @@ struct SDLCommandAck { fileprivate var _message: String? = nil } -struct SDLFlows { +struct SDLFlows: Sendable { // 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. @@ -336,7 +343,7 @@ struct SDLFlows { init() {} } -struct SDLStunRequest { +struct SDLStunRequest: @unchecked Sendable { // 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. @@ -369,7 +376,7 @@ struct SDLStunRequest { fileprivate var _v6Info: SDLV6Info? = nil } -struct SDLStunReply { +struct SDLStunReply: Sendable { // 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. @@ -381,7 +388,7 @@ struct SDLStunReply { init() {} } -struct SDLData { +struct SDLData: @unchecked Sendable { // 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. @@ -403,7 +410,7 @@ struct SDLData { init() {} } -struct SDLRegister { +struct SDLRegister: @unchecked Sendable { // 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. @@ -419,7 +426,7 @@ struct SDLRegister { init() {} } -struct SDLRegisterAck { +struct SDLRegisterAck: @unchecked Sendable { // 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. @@ -435,7 +442,7 @@ struct SDLRegisterAck { init() {} } -struct SDLStunProbe { +struct SDLStunProbe: Sendable { // 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. @@ -449,7 +456,7 @@ struct SDLStunProbe { init() {} } -struct SDLStunProbeReply { +struct SDLStunProbeReply: Sendable { // 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. @@ -465,31 +472,6 @@ struct SDLStunProbeReply { 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 { @@ -581,6 +563,7 @@ extension SDLDevAddr: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 2: .same(proto: "mac"), 3: .standard(proto: "net_addr"), 4: .standard(proto: "net_bit_len"), + 5: .standard(proto: "network_domain"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -593,6 +576,7 @@ extension SDLDevAddr: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 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) }() + case 5: try { try decoder.decodeSingularStringField(value: &self.networkDomain) }() default: break } } @@ -611,6 +595,9 @@ extension SDLDevAddr: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if self.netBitLen != 0 { try visitor.visitSingularUInt32Field(value: self.netBitLen, fieldNumber: 4) } + if !self.networkDomain.isEmpty { + try visitor.visitSingularStringField(value: self.networkDomain, fieldNumber: 5) + } try unknownFields.traverse(visitor: &visitor) } @@ -619,6 +606,7 @@ extension SDLDevAddr: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if lhs.mac != rhs.mac {return false} if lhs.netAddr != rhs.netAddr {return false} if lhs.netBitLen != rhs.netBitLen {return false} + if lhs.networkDomain != rhs.networkDomain {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -629,8 +617,8 @@ extension SDLEmpty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB static let _protobuf_nameMap = SwiftProtobuf._NameMap() mutating func decodeMessage(decoder: inout D) throws { - while let _ = try decoder.nextFieldNumber() { - } + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} } func traverse(visitor: inout V) throws { @@ -652,6 +640,8 @@ extension SDLRegisterSuper: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme 4: .standard(proto: "dev_addr"), 5: .standard(proto: "pub_key"), 6: .same(proto: "token"), + 7: .standard(proto: "network_code"), + 8: .same(proto: "hostname"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -666,6 +656,8 @@ extension SDLRegisterSuper: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme 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) }() + case 7: try { try decoder.decodeSingularStringField(value: &self.networkCode) }() + case 8: try { try decoder.decodeSingularStringField(value: &self.hostname) }() default: break } } @@ -694,6 +686,12 @@ extension SDLRegisterSuper: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme if !self.token.isEmpty { try visitor.visitSingularStringField(value: self.token, fieldNumber: 6) } + if !self.networkCode.isEmpty { + try visitor.visitSingularStringField(value: self.networkCode, fieldNumber: 7) + } + if !self.hostname.isEmpty { + try visitor.visitSingularStringField(value: self.hostname, fieldNumber: 8) + } try unknownFields.traverse(visitor: &visitor) } @@ -704,6 +702,8 @@ extension SDLRegisterSuper: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme if lhs._devAddr != rhs._devAddr {return false} if lhs.pubKey != rhs.pubKey {return false} if lhs.token != rhs.token {return false} + if lhs.networkCode != rhs.networkCode {return false} + if lhs.hostname != rhs.hostname {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -844,8 +844,7 @@ extension SDLPeerInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati 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"), + 3: .standard(proto: "v6_info"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -856,8 +855,7 @@ extension SDLPeerInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati 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) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._v6Info) }() default: break } } @@ -874,11 +872,8 @@ extension SDLPeerInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati 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 visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() try unknownFields.traverse(visitor: &visitor) } @@ -886,7 +881,6 @@ extension SDLPeerInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati 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 @@ -937,7 +931,8 @@ extension SDLSendRegisterEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImp 1: .standard(proto: "dst_mac"), 2: .standard(proto: "nat_ip"), 3: .standard(proto: "nat_port"), - 4: .standard(proto: "v6_info"), + 4: .standard(proto: "nat_type"), + 5: .standard(proto: "v6_info"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -949,7 +944,8 @@ extension SDLSendRegisterEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImp 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) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.natType) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._v6Info) }() default: break } } @@ -969,8 +965,11 @@ extension SDLSendRegisterEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if self.natPort != 0 { try visitor.visitSingularUInt32Field(value: self.natPort, fieldNumber: 3) } + if self.natType != 0 { + try visitor.visitSingularUInt32Field(value: self.natType, fieldNumber: 4) + } try { if let v = self._v6Info { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) } }() try unknownFields.traverse(visitor: &visitor) } @@ -979,6 +978,7 @@ extension SDLSendRegisterEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if lhs.dstMac != rhs.dstMac {return false} if lhs.natIp != rhs.natIp {return false} if lhs.natPort != rhs.natPort {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 diff --git a/Tun/SDLMessage.swift b/Tun/Punchnet/SDLMessage.swift similarity index 94% rename from Tun/SDLMessage.swift rename to Tun/Punchnet/SDLMessage.swift index f5c1f35..888eb2a 100644 --- a/Tun/SDLMessage.swift +++ b/Tun/Punchnet/SDLMessage.swift @@ -54,7 +54,7 @@ enum SDLUpgradeType: UInt32 { } // Id生成器 -struct SDLIdGenerator { +struct SDLIdGenerator: Sendable { // 消息体id private var packetId: UInt32 @@ -153,4 +153,14 @@ struct SDLSuperInboundMessage { case event(SDLEvent) case command(SDLCommand) } + + func isPong() -> Bool { + switch self.packet { + case .pong: + return true + default: + return false + } + } + } diff --git a/Tun/SDLNetAddress.swift b/Tun/Punchnet/SDLNetAddress.swift similarity index 100% rename from Tun/SDLNetAddress.swift rename to Tun/Punchnet/SDLNetAddress.swift diff --git a/Tun/SDLNetworkMonitor.swift b/Tun/Punchnet/SDLNetworkMonitor.swift similarity index 66% rename from Tun/SDLNetworkMonitor.swift rename to Tun/Punchnet/SDLNetworkMonitor.swift index 2e13336..5a76bee 100644 --- a/Tun/SDLNetworkMonitor.swift +++ b/Tun/Punchnet/SDLNetworkMonitor.swift @@ -10,14 +10,14 @@ import Network import Combine // 监控网络的变化 -class SDLNetworkMonitor { +class SDLNetworkMonitor: @unchecked Sendable { private var monitor: NWPathMonitor private var interfaceType: NWInterface.InterfaceType? private let publisher = PassthroughSubject() private var cancel: AnyCancellable? - private let queue = DispatchQueue(label: "networkMonitorQueue") - public let eventFlow = PassthroughSubject() + public let eventStream: AsyncStream + private let eventContinuation: AsyncStream.Continuation enum MonitorEvent { case changed @@ -26,10 +26,11 @@ class SDLNetworkMonitor { init() { self.monitor = NWPathMonitor() + (self.eventStream , self.eventContinuation) = AsyncStream.makeStream(of: MonitorEvent.self, bufferingPolicy: .unbounded) } func start() { - self.monitor.pathUpdateHandler = { path in + self.monitor.pathUpdateHandler = {path in if path.status == .satisfied { if path.usesInterfaceType(.wifi) { self.publisher.send(.wifi) @@ -39,16 +40,16 @@ class SDLNetworkMonitor { self.publisher.send(.wiredEthernet) } } else { - self.eventFlow.send(.unreachable) + self.eventContinuation.yield(.unreachable) self.interfaceType = nil } } - self.monitor.start(queue: self.queue) + self.monitor.start(queue: DispatchQueue.global()) - self.cancel = publisher.throttle(for: 5.0, scheduler: self.queue, latest: true) + self.cancel = publisher.throttle(for: 5.0, scheduler: DispatchQueue.global(), latest: true) .sink { type in if self.interfaceType != nil && self.interfaceType != type { - self.eventFlow.send(.changed) + self.eventContinuation.yield(.changed) } self.interfaceType = type } @@ -57,6 +58,7 @@ class SDLNetworkMonitor { 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/SDLProtoMessageExtension.swift b/Tun/Punchnet/SDLProtoMessageExtension.swift similarity index 100% rename from Tun/SDLProtoMessageExtension.swift rename to Tun/Punchnet/SDLProtoMessageExtension.swift diff --git a/Tun/SDLQPSCounter.swift b/Tun/Punchnet/SDLQPSCounter.swift similarity index 74% rename from Tun/SDLQPSCounter.swift rename to Tun/Punchnet/SDLQPSCounter.swift index 57311fd..9c2859e 100644 --- a/Tun/SDLQPSCounter.swift +++ b/Tun/Punchnet/SDLQPSCounter.swift @@ -8,25 +8,25 @@ import Foundation // 计数器,用来统计qps -class SDLQPSCounter { +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: DispatchQueue(label: "com.yourapp.qps")) + 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 } - NSLog("[\(self.label)] QPS: \(self.count)") self.count = 0 } timer.resume() } func increment(num: Int = 1) { - DispatchQueue(label: "com.yourapp.qps").async { + queue.async { self.count += num } } diff --git a/Tun/SDLThrottler.swift b/Tun/Punchnet/SDLThrottler.swift similarity index 100% rename from Tun/SDLThrottler.swift rename to Tun/Punchnet/SDLThrottler.swift diff --git a/Tun/SDLUtil.swift b/Tun/Punchnet/SDLUtil.swift similarity index 85% rename from Tun/SDLUtil.swift rename to Tun/Punchnet/SDLUtil.swift index 2307a3c..69f46b9 100644 --- a/Tun/SDLUtil.swift +++ b/Tun/Punchnet/SDLUtil.swift @@ -42,5 +42,11 @@ struct SDLUtil { 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/UDPPacket.swift b/Tun/Punchnet/UDPPacket.swift new file mode 100644 index 0000000..60185e9 --- /dev/null +++ b/Tun/Punchnet/UDPPacket.swift @@ -0,0 +1,38 @@ +// +// UDPPacket.swift +// Tun +// +// Created by 安礼成 on 2025/12/13. +// + +import Foundation + +struct UDPHeader { + let sourcePort: UInt16 + let destinationPort: UInt16 + let length: UInt16 + let checksum: UInt16 +} + +struct UDPPacket { + let header: UDPHeader + let payload: Data + + init?(_ data: Data) { + // UDP header 至少 8 字节 + guard data.count >= 8 else { + return nil + } + + let header = UDPHeader(sourcePort: UInt16(bytes: (data[0], data[1])), + destinationPort: UInt16(bytes: (data[2], data[3])), + length: UInt16(bytes: (data[4], data[5])), + checksum: UInt16(bytes: (data[6], data[7])) + ) + // UDP payload = length - 8 + let payloadLength = Int(header.length) - 8 + + self.header = header + self.payload = data.subdata(in: 8..<(8 + payloadLength)) + } +} diff --git a/Tun/UIntExtension.swift b/Tun/Punchnet/UIntExtension.swift similarity index 100% rename from Tun/UIntExtension.swift rename to Tun/Punchnet/UIntExtension.swift diff --git a/Tun/SDLContext.swift b/Tun/SDLContext.swift deleted file mode 100644 index 685d22f..0000000 --- a/Tun/SDLContext.swift +++ /dev/null @@ -1,833 +0,0 @@ -// -// SDLContext.swift -// Tun -// -// Created by 安礼成 on 2024/2/29. -// - -import Foundation -import NetworkExtension -import NIOCore -import Combine - -// 上下文环境变量,全局共享 -/* -1. 处理rsa的加解密逻辑 - */ - -class SDLContext { - - // 路由信息 - struct Route { - let dstAddress: String - let subnetMask: String - - var debugInfo: String { - return "\(dstAddress):\(subnetMask)" - } - } - - // 配置项目 - final class Configuration { - - struct StunServer { - let host: String - let ports: [Int] - } - - // 当前的客户端版本 - let version: UInt8 - - // 安装渠道 - let installedChannel: String - - let superHost: String - let superPort: Int - - let stunServers: [StunServer] - - 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 - - init(version: UInt8, installedChannel: String, superHost: String, superPort: Int, stunServers: [StunServer], clientId: String, token: String) { - self.version = version - self.installedChannel = installedChannel - self.superHost = superHost - self.superPort = superPort - self.stunServers = stunServers - self.clientId = clientId - self.token = token - } - - } - - let config: Configuration - - // tun网络地址信息 - var devAddr: SDLDevAddr - - // nat映射的相关信息, 暂时没有用处 - //var natAddress: SDLNatAddress? - // nat的网络类型 - var natType: NatType = .blocked - - // AES加密,授权通过后,对象才会被创建 - var aesCipher: AESCipher? - // rsa的相关配置, public_key是本地生成的 - let rsaCipher: RSACipher - - // 依赖的变量 - var udpHole: SDLUDPHole? - private var udpCancel: AnyCancellable? - - var superClient: SDLSuperClient? - private var superCancel: AnyCancellable? - - // 数据包读取任务 - private var readTask: Task<(), Never>? - - let provider: PacketTunnelProvider - - private var sessionManager: SessionManager - private var holerManager: HolerManager - private var arpServer: ArpServer - - // 记录最后发送的stunRequest的cookie - private var lastCookie: UInt32? = 0 - - // 定时器 - private var stunCancel: AnyCancellable? - - // 网络状态变化的健康 - private var monitor = SDLNetworkMonitor() - private var monitorCancel: AnyCancellable? - - // 内部socket通讯 - private var noticeClient: SDLNoticeClient - - // 流量统计 - private var flowTracer = SDLFlowTracerActor() - private var flowTracerCancel: AnyCancellable? - - init(provider: PacketTunnelProvider, config: Configuration) throws { - self.config = config - self.rsaCipher = try RSACipher(keySize: 1024) - - // 生成mac地址 - var devAddr = SDLDevAddr() - devAddr.mac = Self.getMacAddress() - self.devAddr = devAddr - - self.provider = provider - self.sessionManager = SessionManager() - self.holerManager = HolerManager() - self.arpServer = ArpServer(known_macs: [:]) - self.noticeClient = SDLNoticeClient() - } - - func start() async throws { - try await self.startSuperClient() - try await self.startUDPHole() - self.noticeClient.start() - - // 启动网络监控 - self.monitorCancel = self.monitor.eventFlow.sink { event in - switch event { - case .changed: - // 需要重新探测网络的nat类型 - Task { - self.natType = await self.getNatType() - NSLog("didNetworkPathChanged, nat type is: \(self.natType)") - } - case .unreachable: - NSLog("didNetworkPathUnreachable") - } - } - self.monitor.start() - } - - private func startSuperClient() async throws { - self.superClient = SDLSuperClient(host: config.superHost, port: config.superPort) - // 建立super的绑定关系 - self.superCancel?.cancel() - self.superCancel = self.superClient?.eventFlow.sink { event in - Task.detached { - await self.handleSuperEvent(event: event) - } - } - try await self.superClient?.start() - } - - private func handleSuperEvent(event: SDLSuperClient.SuperEvent) async { - switch event { - case .ready: - NSLog("[SDLContext] get registerSuper, mac address: \(Self.formatMacAddress(mac: self.devAddr.mac))") - guard let message = await self.superClient?.registerSuper(context: self) 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) - - NSLog("[SDLContext] get registerSuperAck, aes_key len: \(aesKey.count)") - self.devAddr = registerSuperAck.devAddr - - if upgradeType == .force { - let forceUpgrade = NoticeMessage.UpgradeMessage(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress) - self.noticeClient.send(data: forceUpgrade.binaryData) - - exit(-1) - } - - // 服务器分配的tun网卡信息 - await self.didNetworkConfigChanged(devAddr: self.devAddr) - self.aesCipher = AESCipher(aesKey: aesKey) - if upgradeType == .normal { - let normalUpgrade = NoticeMessage.UpgradeMessage(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress) - self.noticeClient.send(data: normalUpgrade.binaryData) - } - - 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.AlertMessage(alert: errorMessage) - self.noticeClient.send(data: alertNotice.binaryData) - exit(-1) - case .noIpAddress, .networkFault, .internalFault: - let alertNotice = NoticeMessage.AlertMessage(alert: errorMessage) - self.noticeClient.send(data: alertNotice.binaryData) - } - SDLLogger.log("Get a SuperNak message exit", level: .error) - default: - () - } - - case .closed: - SDLLogger.log("[SDLContext] super client closed", level: .debug) - await self.arpServer.clear() - DispatchQueue.global().asyncAfter(deadline: .now() + 5) { - Task { - try await self.startSuperClient() - } - } - case .event(let evt): - switch evt { - case .natChanged(let natChangedEvent): - let dstMac = natChangedEvent.mac - NSLog("natChangedEvent, dstMac: \(dstMac)") - await sessionManager.removeSession(dstMac: dstMac) - case .sendRegister(let sendRegisterEvent): - NSLog("sendRegisterEvent, ip: \(sendRegisterEvent)") - let address = SDLUtil.int32ToIp(sendRegisterEvent.natIp) - if let remoteAddress = try? SocketAddress.makeAddressResolvingHost(address, port: Int(sendRegisterEvent.natPort)) { - // 发送register包 - self.udpHole?.sendRegister(context: self, remoteAddress: remoteAddress, dst_mac: sendRegisterEvent.dstMac) - } - - case .networkShutdown(let shutdownEvent): - let alertNotice = NoticeMessage.AlertMessage(alert: shutdownEvent.message) - self.noticeClient.send(data: alertNotice.binaryData) - 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)) - NSLog("[SDLContext] change network command get aes_key len: \(aesKey.count)") - self.devAddr = changeNetworkCommand.devAddr - - // 服务器分配的tun网卡信息 - await self.didNetworkConfigChanged(devAddr: self.devAddr) - self.aesCipher = AESCipher(aesKey: aesKey) - - var commandAck = SDLCommandAck() - commandAck.status = true - - self.superClient?.commandAck(packetId: packetId, ack: commandAck) - } - } - - } - - private func startUDPHole() async throws { - self.udpHole = SDLUDPHole() - - self.udpCancel?.cancel() - self.udpCancel = self.udpHole?.eventFlow.sink { event in - Task.detached { - await self.handleUDPEvent(event: event) - } - } - - try await self.udpHole?.start() - } - - private func handleUDPEvent(event: SDLUDPHole.UDPEvent) async { - switch event { - case .ready: - // 获取当前网络的类型 - self.natType = await self.getNatType() - SDLLogger.log("[SDLContext] nat type is: \(self.natType)", level: .debug) - - let timer = Timer.publish(every: 5.0, on: .main, in: .common).autoconnect() - self.stunCancel = Just(Date()).merge(with: timer).sink { _ in - self.lastCookie = self.udpHole?.stunRequest(context: self) - } - - case .closed: - DispatchQueue.global().asyncAfter(deadline: .now() + 5) { - Task { - try await self.startUDPHole() - } - } - - case .message(let remoteAddress, let message): - switch message { - case .register(let register): - NSLog("register packet: \(register), dev_addr: \(self.devAddr)") - // 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下 - if register.dstMac == self.devAddr.mac && register.networkID == self.devAddr.networkID { - // 回复ack包 - 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 { - SDLLogger.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 { - SDLLogger.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 - SDLLogger.log("[SDLContext] get a stunReply: \(try! stunReply.jsonString())") - } - default: - () - } - - case .data(let data): - let mac = LayerPacket.MacAddress(data: data.dstMac) - guard (data.dstMac == self.devAddr.mac || mac.isBroadcast() || mac.isMulticast()) else { - NSLog("[SDLContext] didReadData 1") - return - } - - guard let decyptedData = try? self.aesCipher?.decypt(data: Data(data.data)) else { - NSLog("[SDLContext] didReadData 2") - 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: - NSLog("[SDLContext] get arp request packet") - 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: - NSLog("[SDLContext] get arp response packet") - await self.arpServer.append(ip: arpPacket.senderIP, mac: arpPacket.senderMAC) - } - } else { - NSLog("[SDLContext] get invalid arp packet, target_ip: \(arpPacket)") - } - } else { - NSLog("[SDLContext] get invalid arp packet") - } - case .ipv4: - NSLog("[SDLContext] get ipv4 packet") - 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: - NSLog("[SDLContext] get invalid packet") - } - } catch let err { - NSLog("[SDLContext] didReadData err: \(err)") - } - } - - } - - // 流量统计 - 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() - 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"]) - } - - 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) - - await self.holerManager.cleanup() - - self.startReader() - } catch let err { - SDLLogger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error) - exit(-1) - } - } - - // 开始读取数据, 用单独的线程处理packetFlow - private func startReader() { - // 停止之前的任务 - self.readTask?.cancel() - - // 开启新的任务 - self.readTask = Task(priority: .high) { - repeat { - if Task.isCancelled { - break - } - - 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 = ARPPacket.arpRequest(senderIP: self.devAddr.netAddr, senderMAC: self.devAddr.mac, targetIP: dstIp) - await self.routeLayerPacket(dstMac: broadcastMac, type: .arp, data: arpReqeust.marshal()) - - NSLog("[SDLContext] dstIp: \(dstIp) arp query not found") - } - } - } - } - - } 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(data: layerPacket.marshal()) else { - return - } - - // 通过session发送到对端 - if let session = await self.sessionManager.getSession(toAddress: dstMac) { - NSLog("[SDLContext] send packet by session: \(session)") - self.udpHole?.sendPacket(context: self, session: session, data: encodedPacket) - - await self.flowTracer.inc(num: data.count, type: .p2p) - } else { - // 通过super_node进行转发 - self.udpHole?.forwardPacket(context: self, dst_mac: dstMac, data: encodedPacket) - // 流量统计 - await self.flowTracer.inc(num: data.count, type: .forward) - - // 尝试打洞 - await self.holerManager.addHoler(dstMac: dstMac) { - self.holerTask(dstMac: dstMac) - } - } - } - - deinit { - self.stunCancel?.cancel() - self.udpHole = nil - self.superClient = nil - } - -} - -//--MARK: 处理RSA加密算法 -extension SDLContext { - - struct RSACipher { - let pubKey: String - let privateKeyDER: Data - - init(keySize: Int) throws { - let (privateKey, publicKey) = try Self.loadKeys(keySize: keySize) - let privKeyStr = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKey) - - self.pubKey = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey) - self.privateKeyDER = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privKeyStr) - } - - public func decode(data: Data) throws -> Data { - let tag = Data() - let (decryptedData, _) = try CC.RSA.decrypt(data, derKey: self.privateKeyDER, tag: tag, padding: .pkcs1, digest: .none) - - return decryptedData - } - - private static func loadKeys(keySize: Int) throws -> (Data, Data) { - if let privateKey = UserDefaults.standard.data(forKey: "privateKey"), - let publicKey = UserDefaults.standard.data(forKey: "publicKey") { - - return (privateKey, publicKey) - } else { - let (privateKey, publicKey) = try CC.RSA.generateKeyPair(keySize) - UserDefaults.standard.setValue(privateKey, forKey: "privateKey") - UserDefaults.standard.setValue(publicKey, forKey: "publicKey") - - return (privateKey, publicKey) - } - } - - } -} - -// --MARK: 处理AES加密, AES256 -extension SDLContext { - - struct AESCipher { - let aesKey: Data - let ivData: Data - - init(aesKey: Data) { - self.aesKey = aesKey - self.ivData = Data(aesKey.prefix(16)) - } - - func decypt(data: Data) throws -> Data { - return try CC.crypt(.decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData) - } - - func encrypt(data: Data) throws -> Data { - return try CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData) - } - } - -} - -// --MARK: session管理, session的有效时间为10s,没次使用后更新最后使用时间 -extension SDLContext { - - 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) - } - - } - -} - -// --MARK: known_ips管理 -extension SDLContext { - - 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 = [:] - } - } - -} - -// --MARK: 打洞流程管理 -extension SDLContext { - - actor HolerManager { - private var holers: [Data:Task<(), Never>] = [:] - - func addHoler(dstMac: Data, creator: @escaping () -> Task<(), Never>) { - if let task = self.holers[dstMac] { - if task.isCancelled { - self.holers[dstMac] = creator() - } - } else { - self.holers[dstMac] = creator() - } - } - - func cleanup() { - for holer in holers.values { - holer.cancel() - } - self.holers.removeAll() - } - - } - - func holerTask(dstMac: Data) -> Task<(), Never> { - return Task { - guard let message = try? await self.superClient?.queryInfo(context: self, dst_mac: dstMac) else { - return - } - - switch message.packet { - case .empty: - SDLLogger.log("[SDLContext] hole query_info get empty: \(message)", level: .debug) - case .peerInfo(let peerInfo): - if let remoteAddress = peerInfo.v4Info.socketAddress() { - SDLLogger.log("[SDLContext] hole sock address: \(remoteAddress)", level: .warning) - // 发送register包 - self.udpHole?.sendRegister(context: self, remoteAddress: remoteAddress, dst_mac: dstMac) - } else { - SDLLogger.log("[SDLContext] hole sock address is invalid: \(peerInfo.v4Info)", level: .warning) - } - default: - SDLLogger.log("[SDLContext] hole query_info is packet: \(message)", level: .warning) - } - } - } - -} - -//--MARK: 网络类型探测 -extension SDLContext { - - // 定义nat类型 - enum NatType: UInt8, Encodable { - case blocked = 0 - case noNat = 1 - case fullCone = 2 - case portRestricted = 3 - case coneRestricted = 4 - case symmetric = 5 - } - - private func getNatAddress(remoteAddress: SocketAddress, attr: SDLProbeAttr) async -> SocketAddress? { - let stunProbeReply = await self.udpHole?.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: 5) - - return stunProbeReply?.socketAddress() - } - - // 获取当前所处的网络的nat类型 - func getNatType() async -> NatType { - let addressArray = config.stunProbeSocketAddressArray - // step1: ip1:port1 <---- ip1:port1 - guard let natAddress1 = await getNatAddress(remoteAddress: addressArray[0][0], attr: .none) else { - return .blocked - } - - // 网络没有在nat下 - if natAddress1 == self.udpHole?.localAddress { - return .noNat - } - - // step2: ip2:port2 <---- ip2:port2 - guard let natAddress2 = await getNatAddress(remoteAddress: addressArray[1][1], attr: .none) else { - return .blocked - } - - // 如果natAddress2 的IP地址与上次回来的IP是不一样的,它就是对称型NAT; 这次的包也一定能发成功并收到 - // 如果ip地址变了,这说明{dstIp, dstPort, srcIp, srcPort}, 其中有一个变了;则用新的ip地址 - NSLog("nat_address1: \(natAddress1), nat_address2: \(natAddress2)") - 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(remoteAddress: addressArray[0][0], attr: .peer) { - NSLog("nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address3: \(natAddress3)") - return .fullCone - } - - // step3: ip1:port1 <---- ip1:port2 (port改变情况) - // 如果能收到的说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。 - if let natAddress4 = await getNatAddress(remoteAddress: addressArray[0][0], attr: .port) { - NSLog("nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address4: \(natAddress4)") - return .coneRestricted - } else { - return .portRestricted - } - } - -} - - -//--MARK: 获取设备的UUID - -extension SDLContext { - - 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地址 - 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) - } - - // 将mac地址转换成字符串 - private static func formatMacAddress(mac: Data) -> String { - let bytes = [UInt8](mac) - - return bytes.map { String(format: "%02X", $0) }.joined(separator: ":").lowercased() - } - -} diff --git a/Tun/SDLHoler.swift b/Tun/SDLHoler.swift deleted file mode 100644 index 770f5c8..0000000 --- a/Tun/SDLHoler.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// SDLHoler.swift -// Tun -// -// Created by 安礼成 on 2024/3/12. -// - -import Foundation -import Combine - - diff --git a/Tun/SDLLogger.swift b/Tun/SDLLogger.swift deleted file mode 100644 index e66e49a..0000000 --- a/Tun/SDLLogger.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// SDLLogger.swift -// Tun -// -// Created by 安礼成 on 2024/3/13. -// - -import Foundation - -struct SDLLogger { - enum Level { - case debug - case info - case warning - case error - } - - static var logLevel: Level = .debug - - static func log(_ message: String, level: Level = .debug) { - switch logLevel { - case .debug: - NSLog(message) - case .info: - if level == .info || level == .warning || level == .error { - NSLog(message) - } - case .warning: - if level == .warning || level == .error { - NSLog(message) - } - case .error: - if level == .error { - NSLog(message) - } - } - } - -} diff --git a/Tun/SDLNoticeClient.swift b/Tun/SDLNoticeClient.swift deleted file mode 100644 index 71a12de..0000000 --- a/Tun/SDLNoticeClient.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// 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服务器之间的通讯 -class SDLNoticeClient: ChannelInboundHandler { - public typealias InboundIn = AddressedEnvelope - public typealias OutboundOut = AddressedEnvelope - - private var thread: Thread? - var context: ChannelHandlerContext? - private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - private let remoteAddress: SocketAddress - - init() { - self.remoteAddress = try! SocketAddress(ipAddress: "127.0.0.1", port: 50195) - } - - // 启动函数 - func start() { - self.thread = Thread { - let bootstrap = DatagramBootstrap(group: self.group) - .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) - .channelInitializer { channel in - // 接收缓冲区 - channel.pipeline.addHandler(self) - } - - let channel = try! bootstrap.bind(host: "0.0.0.0", port: 0).wait() - SDLLogger.log("[SDLNoticeClient] started and listening on: \(channel.localAddress!)", level: .debug) - - // This will never unblock as we don't close the channel - try! channel.closeFuture.wait() - } - self.thread?.start() - } - - // -- MARK: ChannelInboundHandler Methods - - public func channelActive(context: ChannelHandlerContext) { - self.context = context - } - - // 接收到的消息, 消息需要根据类型分流 - public func channelRead(context: ChannelHandlerContext, data: NIOAny) { - context.fireChannelRead(data) - } - - public func errorCaught(context: ChannelHandlerContext, error: Error) { - // As we are not really interested getting notified on success or failure we just pass nil as promise to - // reduce allocations. - context.close(promise: nil) - self.context = nil - } - - public func channelInactive(context: ChannelHandlerContext) { - self.context = nil - context.close(promise: nil) - } - - // 处理写入逻辑 - func send(data: Data) { - guard let context = self.context else { - return - } - - context.eventLoop.execute { - let buffer = context.channel.allocator.buffer(bytes: data) - - let envelope = AddressedEnvelope(remoteAddress: self.remoteAddress, data: buffer) - context.writeAndFlush(self.wrapOutboundOut(envelope), promise: nil) - } - } - - deinit { - self.thread?.cancel() - try? self.group.syncShutdownGracefully() - } -} diff --git a/Tun/SDLSuperClient.swift b/Tun/SDLSuperClient.swift deleted file mode 100644 index e673619..0000000 --- a/Tun/SDLSuperClient.swift +++ /dev/null @@ -1,373 +0,0 @@ -// -// SDLWebsocketClient.swift -// Tun -// -// Created by 安礼成 on 2024/3/28. -// - -import Foundation -import NIOCore -import NIOPosix -import Combine - -// --MARK: 和SuperNode的客户端 -class SDLSuperClient: ChannelInboundHandler { - public typealias InboundIn = ByteBuffer - public typealias OutboundOut = ByteBuffer - - public typealias CallbackFun = (SDLSuperInboundMessage?) -> Void - - private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - private var channel: Channel? - - // id生成器 - var idGenerator = SDLIdGenerator(seed: 1) - private let callbackManager = SuperCallbackManager() - - let host: String - let port: Int - - private var pingCancel: AnyCancellable? - - public var eventFlow = PassthroughSubject() - - // 定义事件类型 - enum SuperEvent { - case ready - case closed - case event(SDLEvent) - case command(UInt32, SDLCommand) - } - - init(host: String, port: Int) { - self.host = host - self.port = port - } - - func start() async throws { - let bootstrap = ClientBootstrap(group: self.group) - .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) - .channelInitializer { channel in - return channel.pipeline.addHandlers([ - ByteToMessageHandler(FixedHeaderDelimiterCoder()), - MessageToByteHandler(FixedHeaderDelimiterCoder()), - self - ]) - } - - do { - NSLog("super client connect: \(self.host):\(self.port)") - self.channel = try await bootstrap.connect(host: self.host, port: self.port).get() - } catch let err { - NSLog("super client get error: \(err)") - self.eventFlow.send(.closed) - } - } - - // -- 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) async -> SDLSuperInboundMessage? { - return await withCheckedContinuation { c in - self.registerSuper(context: ctx) { message in - c.resume(returning: message) - } - } - } - - func registerSuper(context ctx: SDLContext, callback: @escaping CallbackFun) { - 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() - - self.write(type: .registerSuper, data: data, callback: callback) - } - - func queryInfo(context ctx: SDLContext, dst_mac: Data) async throws -> SDLSuperInboundMessage? { - return await withCheckedContinuation { c in - self.queryInfo(context: ctx, dst_mac: dst_mac) { message in - c.resume(returning: message) - } - } - } - - // 查询目标服务器的相关信息 - func queryInfo(context ctx: SDLContext, dst_mac: Data, callback: @escaping CallbackFun) { - var queryInfo = SDLQueryInfo() - queryInfo.dstMac = dst_mac - - self.write(type: .queryInfo, data: try! queryInfo.serializedData(), callback: callback) - } - - 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()) - } - - // --MARK: ChannelInboundHandler - - public func channelActive(context: ChannelHandlerContext) { - self.startPingTicker() - self.eventFlow.send(.ready) - } - - public func channelRead(context: ChannelHandlerContext, data: NIOAny) { - var buffer = self.unwrapInboundIn(data) - if let message = decode(buffer: &buffer) { - SDLLogger.log("[SDLSuperTransport] read message: \(message)", level: .warning) - - switch message.packet { - case .event(let event): - self.eventFlow.send(.event(event)) - case .command(let command): - self.eventFlow.send(.command(message.msgId, command)) - default: - self.callbackManager.fireCallback(message: message) - } - } - } - - public func errorCaught(context: ChannelHandlerContext, error: Error) { - SDLLogger.log("[SDLSuperTransport] error: \(error)", level: .warning) - self.channel = nil - self.eventFlow.send(.closed) - context.close(promise: nil) - } - - public func channelInactive(context: ChannelHandlerContext) { - SDLLogger.log("[SDLSuperTransport] channelInactive", level: .warning) - self.channel = nil - context.close(promise: nil) - } - - func write(type: SDLPacketType, data: Data, callback: @escaping CallbackFun) { - guard let channel = self.channel else { - return - } - - SDLLogger.log("[SDLSuperTransport] will write data: \(data)", level: .debug) - - let packetId = idGenerator.nextId() - self.callbackManager.addCallback(id: packetId, callback: callback) - - channel.eventLoop.execute { - var buffer = channel.allocator.buffer(capacity: data.count + 5) - buffer.writeInteger(packetId, as: UInt32.self) - buffer.writeBytes([type.rawValue]) - buffer.writeBytes(data) - - channel.writeAndFlush(self.wrapOutboundOut(buffer), promise: nil) - } - } - - func send(type: SDLPacketType, packetId: UInt32, data: Data) { - guard let channel = self.channel else { - return - } - - channel.eventLoop.execute { - var buffer = channel.allocator.buffer(capacity: data.count + 5) - buffer.writeInteger(packetId, as: UInt32.self) - buffer.writeBytes([type.rawValue]) - buffer.writeBytes(data) - - channel.writeAndFlush(self.wrapOutboundOut(buffer), promise: nil) - } - } - - // --MARK: 心跳机制 - - private func startPingTicker() { - self.pingCancel = Timer.publish(every: 5.0, on: .main, in: .common).autoconnect() - .sink { _ in - // 保持和super-node的心跳机制 - self.ping() - } - } - - deinit { - self.pingCancel?.cancel() - try! group.syncShutdownGracefully() - } - -} - -/// 基于2字节固定长度的分包协议 -extension SDLSuperClient { - private final class FixedHeaderDelimiterCoder: ByteToMessageDecoder, MessageToByteEncoder { - 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 - } - } - - func encode(data: ByteBuffer, out: inout ByteBuffer) throws { - let len = data.readableBytes - out.writeInteger(UInt16(len)) - out.writeBytes(data.readableBytesView) - } - } -} - -// 回调函数管理器 -extension SDLSuperClient { - private final class SuperCallbackManager { - // 对应请求体和相应的关系 - private var callbacks: [UInt32:CallbackFun] = [:] - private let locker = NSLock() - - func addCallback(id: UInt32, callback: @escaping CallbackFun) { - locker.lock() - defer { - locker.unlock() - } - self.callbacks[id] = callback - } - - func fireCallback(message: SDLSuperInboundMessage) { - locker.lock() - defer { - locker.unlock() - } - - if let callback = self.callbacks[message.msgId] { - callback(message) - self.callbacks.removeValue(forKey: message.msgId) - } - } - - func fireAllCallbacks(message: SDLSuperInboundMessage) { - locker.lock() - defer { - locker.unlock() - } - - for (_, callback) in self.callbacks { - callback(nil) - } - self.callbacks.removeAll() - } - } -} - -// --MARK: 编解码器 -extension SDLSuperClient { - // 消息格式为: <> - 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(serializedData: Data(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(serializedData: Data(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(serializedData: Data(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(serializedData: Data(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(serializedData: Data(bytes)) else { - return nil - } - return .init(msgId: msgId, packet: .event(.natChanged(natChangedEvent))) - case .sendRegister: - guard let sendRegisterEvent = try? SDLSendRegisterEvent(serializedData: Data(bytes)) else { - return nil - } - return .init(msgId: msgId, packet: .event(.sendRegister(sendRegisterEvent))) - case .networkShutdown: - guard let networkShutdownEvent = try? SDLNetworkShutdownEvent(serializedData: Data(bytes)) else { - return nil - } - return .init(msgId: msgId, packet: .event(.networkShutdown(networkShutdownEvent))) - } - - default: - return nil - } - } - -} diff --git a/Tun/SDLUDPHole.swift b/Tun/SDLUDPHole.swift deleted file mode 100644 index 3c941c1..0000000 --- a/Tun/SDLUDPHole.swift +++ /dev/null @@ -1,337 +0,0 @@ -// -// SDLanServer.swift -// Tun -// -// Created by 安礼成 on 2024/1/31. -// - -import Foundation -import NIOCore -import NIOPosix -import Combine - -// 处理和sn-server服务器之间的通讯 -class SDLUDPHole: ChannelInboundHandler { - public typealias InboundIn = AddressedEnvelope - public typealias OutboundOut = AddressedEnvelope - - // 回调函数 - public typealias CallbackFun = (SDLStunProbeReply?) -> Void - - private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - private var cookieGenerator = SDLIdGenerator(seed: 1) - private let callbackManager = HoleCallbackManager() - - public var localAddress: SocketAddress? - public var channel: Channel? - - public var eventFlow = PassthroughSubject() - - // 定义事件类型 - enum UDPEvent { - case ready - case closed - case message(SocketAddress, SDLHoleInboundMessage) - case data(SDLData) - } - - init() { - - } - - // 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) - - SDLLogger.log("[SDLUDPHole] stunRequest: \(remoteAddress), host: \(ctx.config.stunServers[0].host):\(ctx.config.stunServers[0].ports[0])", level: .warning) - - self.send(remoteAddress: remoteAddress, type: .stunRequest, data: try! stunRequest.serializedData()) - - return cookie - } - - // 探测tun信息 - func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int = 5) async -> SDLStunProbeReply? { - return await withCheckedContinuation { continuation in - self.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: timeout) { probeReply in - continuation.resume(returning: probeReply) - } - } - } - - private func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int, callback: @escaping CallbackFun) { - 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()) - - SDLLogger.log("[SDLUDPHole] stunProbe: \(remoteAddress)", level: .warning) - - self.callbackManager.addCallback(id: cookie, timeout: timeout, callback: callback) - } - - // MARK: client-client apis - - // 发送数据包到其他session - func sendPacket(context ctx: SDLContext, session: SDLContext.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 - let packet = try! dataPacket.serializedData() - - SDLLogger.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 - - let packet = try! dataPacket.serializedData() - - NSLog("[SDLContext] forward packet, remoteAddress: \(remoteAddress), data size: \(packet.count)") - - self.send(remoteAddress: remoteAddress, type: .data, data: packet) - } - - // 发送register包 - func sendRegister(context ctx: SDLContext, remoteAddress: SocketAddress, dst_mac: Data) { - var register = SDLRegister() - register.networkID = ctx.devAddr.networkID - register.srcMac = ctx.devAddr.mac - register.dstMac = dst_mac - - SDLLogger.log("[SDLUDPHole] SendRegister: \(remoteAddress), src_mac: \(LayerPacket.MacAddress.description(data: ctx.devAddr.mac)), dst_mac: \(LayerPacket.MacAddress.description(data: dst_mac))", level: .debug) - - self.send(remoteAddress: remoteAddress, type: .register, data: try! register.serializedData()) - } - - // 回复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 - - SDLLogger.log("[SDLUDPHole] SendRegisterAck: \(remoteAddress), \(registerAck)", level: .debug) - - self.send(remoteAddress: remoteAddress, type: .registerAck, data: try! registerAck.serializedData()) - } - - // 启动函数 - func start() async throws { - let bootstrap = DatagramBootstrap(group: self.group) - .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) - .channelInitializer { channel in - // 接收缓冲区 - return channel.setOption(ChannelOptions.socketOption(.so_rcvbuf), value: 5 * 1024 * 1024) - .flatMap { - channel.setOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_SNDBUF), value: 5 * 1024 * 1024) - }.flatMap { - channel.pipeline.addHandler(self) - } - } - - let channel = try await bootstrap.bind(host: "0.0.0.0", port: 0).get() - - SDLLogger.log("[UDPHole] started and listening on: \(channel.localAddress!)", level: .debug) - self.localAddress = channel.localAddress - self.channel = channel - } - - // -- MARK: ChannelInboundHandler Methods - - public func channelActive(context: ChannelHandlerContext) { - self.eventFlow.send(.ready) - } - - // 接收到的消息, 消息需要根据类型分流 - public func channelRead(context: ChannelHandlerContext, data: NIOAny) { - let envelope = self.unwrapInboundIn(data) - var buffer = envelope.data - let remoteAddress = envelope.remoteAddress - - do { - if let message = try decode(buffer: &buffer) { - Task { - switch message { - case .data(let data): - SDLLogger.log("[SDLUDPHole] read data: \(data.format()), from: \(remoteAddress)", level: .debug) - self.eventFlow.send(.data(data)) - case .stunProbeReply(let probeReply): - self.callbackManager.fireCallback(message: probeReply) - default: - self.eventFlow.send(.message(remoteAddress, message)) - } - } - } else { - SDLLogger.log("[SDLUDPHole] decode message, get null", level: .warning) - } - } catch let err { - SDLLogger.log("[SDLUDPHole] decode message, get error: \(err)", level: .debug) - } - } - - public func errorCaught(context: ChannelHandlerContext, error: Error) { - SDLLogger.log("[SDLUDPHole] get error: \(error)", level: .error) - // As we are not really interested getting notified on success or failure we just pass nil as promise to - // reduce allocations. - context.close(promise: nil) - self.channel = nil - self.eventFlow.send(.closed) - } - - public func channelInactive(context: ChannelHandlerContext) { - self.channel = nil - context.close(promise: nil) - } - - // 处理写入逻辑 - func send(remoteAddress: SocketAddress, type: SDLPacketType, data: Data) { - guard let channel = self.channel else { - return - } - - // 在Eventloop中时直接写入数据避免线程切换 - if channel.eventLoop.inEventLoop { - var buffer = channel.allocator.buffer(capacity: data.count + 1) - buffer.writeBytes([type.rawValue]) - buffer.writeBytes(data) - - let envelope = AddressedEnvelope(remoteAddress: remoteAddress, data: buffer) - channel.writeAndFlush(self.wrapOutboundOut(envelope), promise: nil) - } else { - channel.eventLoop.execute { - var buffer = channel.allocator.buffer(capacity: data.count + 1) - buffer.writeBytes([type.rawValue]) - buffer.writeBytes(data) - - let envelope = AddressedEnvelope(remoteAddress: remoteAddress, data: buffer) - channel.writeAndFlush(self.wrapOutboundOut(envelope), promise: nil) - } - } - } - - deinit { - try? self.group.syncShutdownGracefully() - } -} - -//--MARK: 编解码器 -extension SDLUDPHole { - - 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 { - SDLLogger.log("[SDLUDPHole] decode error", level: .error) - return nil - } - - switch packetType { - case .data: - let dataPacket = try SDLData(serializedData: Data(bytes)) - return .data(dataPacket) - case .register: - let registerPacket = try SDLRegister(serializedData: Data(bytes)) - return .register(registerPacket) - case .registerAck: - let registerAck = try SDLRegisterAck(serializedData: Data(bytes)) - return .registerAck(registerAck) - case .stunReply: - let stunReply = try SDLStunReply(serializedData: Data(bytes)) - return .stunReply(stunReply) - case .stunProbeReply: - let stunProbeReply = try SDLStunProbeReply(serializedData: Data(bytes)) - return .stunProbeReply(stunProbeReply) - default: - return nil - } - } -} - -// --MARK: 回调函数管理器 -extension SDLUDPHole { - private final class HoleCallbackManager { - // 对应请求体和相应的关系 - private var callbacks: [UInt32:CallbackFun] = [:] - private let locker = NSLock() - - func addCallback(id: UInt32, timeout: Int, callback: @escaping CallbackFun) { - locker.lock() - defer { - locker.unlock() - } - - DispatchQueue.global().asyncAfter(deadline: .now() + Double(timeout)) { - self.fireCallback(cookie: id) - } - - self.callbacks[id] = callback - } - - func fireCallback(message: SDLStunProbeReply) { - locker.lock() - defer { - locker.unlock() - } - - if let callback = self.callbacks[message.cookie] { - callback(message) - self.callbacks.removeValue(forKey: message.cookie) - } - } - - func fireAllCallbacks(message: SDLSuperInboundMessage) { - locker.lock() - defer { - locker.unlock() - } - - for (_, callback) in self.callbacks { - callback(nil) - } - self.callbacks.removeAll() - } - - private func fireCallback(cookie: UInt32) { - locker.lock() - defer { - locker.unlock() - } - - if let callback = self.callbacks[cookie] { - callback(nil) - self.callbacks.removeValue(forKey: cookie) - } - } - - } -} diff --git a/dmg.sh b/dmg.sh index 38bbb4e..82241bd 100755 --- a/dmg.sh +++ b/dmg.sh @@ -1,3 +1,3 @@ #! /bin/sh -create-dmg --volname "punchnet" --window-pos 200 120 --window-size 800 400 --icon "punchnet.app" 200 190 --hide-extension "punchnet.app" --app-drop-link 600 185 ~/Desktop/punchnet.dmg /Users/anlicheng/Desktop/punchnet_v1 +create-dmg --volname "punchnet" --window-pos 200 120 --window-size 800 400 --icon "punchnet.app" 200 190 --hide-extension "punchnet.app" --app-drop-link 600 185 ~/Desktop/punchnet.dmg /Users/anlicheng/Desktop/punchnet_macos_v1 diff --git a/docs.md b/docs.md new file mode 100644 index 0000000..114447f --- /dev/null +++ b/docs.md @@ -0,0 +1,12 @@ +1. 查看dns的设置 + networksetup -getdnsservers Wi-Fi + scutil --dns + +2. 修改系统的dns设置 + networksetup -setdnsservers Wi-Fi 8.8.8.8 1.1.1.1 + + 恢复为自动获取 + + networksetup -setdnsservers Wi-Fi empty + + 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/PunchnetConfig.swift b/punchnet/Core/PunchnetConfig.swift deleted file mode 100644 index 0fd065f..0000000 --- a/punchnet/Core/PunchnetConfig.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Config.swift -// punchnet -// -// Created by 安礼成 on 2025/5/14. -// -import Foundation - -enum PunchnetError: Error { - case dnsUnreachable -} - -struct PunchnetConfig { - static let server = "punchnet.aioe.tech" - static let port = 18083 - - static func getOptions() throws -> [String:NSObject] { - var options: [String: NSObject] = [:] - - if let ip = DNSResolver.resolveAddrInfos(PunchnetConfig.server).first { - options["super_ip"] = ip as NSObject - } else { - throw PunchnetError.dnsUnreachable - } - - return options - } - -} - diff --git a/punchnet/Core/SDLAPI.swift b/punchnet/Core/SDLAPI.swift index e3a5824..48aacec 100644 --- a/punchnet/Core/SDLAPI.swift +++ b/punchnet/Core/SDLAPI.swift @@ -20,12 +20,23 @@ struct JSONRPCError: Decodable { struct SDLAPI { + static let baseUrl: String = "https://punchnet.s5s8.com/api" + static let testBaseUrl: String = "http://127.0.0.1:19082/test" + struct Upgrade: Decodable { let upgrade_type: Int let upgrade_prompt: String let upgrade_address: String } - + + struct NetworkProfile: Decodable { + struct NetworkItem: Decodable { + let name: String + let code: String + } + let network: [NetworkItem] + } + static func checkVersion(clientId: String, version: Int, channel: String) async throws -> JSONRPCResponse { let params: [String:Any] = [ "client_id": clientId, @@ -34,7 +45,7 @@ struct SDLAPI { ] let postData = try! JSONSerialization.data(withJSONObject: params) - var request = URLRequest(url: URL(string: "http://127.0.0.1:18082/test/upgrade")!) + var request = URLRequest(url: URL(string: baseUrl + "/upgrade")!) request.httpMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = postData @@ -44,4 +55,20 @@ struct SDLAPI { return try JSONDecoder().decode(JSONRPCResponse.self, from: data) } + static func getUserNetworks(clientId: String) async throws -> JSONRPCResponse { + let params: [String:Any] = [ + "client_id": clientId + ] + + let postData = try! JSONSerialization.data(withJSONObject: params) + var request = URLRequest(url: URL(string: baseUrl + "/get_user_network")!) + request.httpMethod = "POST" + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = postData + + let (data, _) = try await URLSession.shared.data(for: request) + + return try JSONDecoder().decode(JSONRPCResponse.self, from: data) + } + } diff --git a/punchnet/Core/SystemConfig.swift b/punchnet/Core/SystemConfig.swift index 9f00db5..898ea31 100644 --- a/punchnet/Core/SystemConfig.swift +++ b/punchnet/Core/SystemConfig.swift @@ -15,4 +15,49 @@ struct SystemConfig { // 安装渠道 static let installedChannel = "MacAppStore" + + // super 节点 + //static let superHost = "118.178.229.213" + + static let superHost = "punchnet.s5s8.com" + static let superPort = 18083 + + // stun探测服务 + static let stunServers = "118.178.229.213:1265,1266;118.178.229.213:1265,1266" + //static let stunServers = "127.0.0.1:1265,1266;127.0.0.1:1265,1266" + + static func getOptions(networkCode: String, token: String, clientId: String, hostname: String, noticePort: Int) -> [String:NSObject]? { + guard let superIp = DNSResolver.resolveAddrInfos(superHost).first else { + return nil + } + + let options = [ + "version:": version as NSObject, + "installed_channel": installedChannel as NSObject, + "client_id": clientId as NSObject, + "network_code": networkCode as NSObject, + "token": token as NSObject, + "super_ip": superIp as NSObject, + "super_port": superPort as NSObject, + "stun_servers": stunServers as NSObject, + "remote_dns_server": superIp as NSObject, + "hostname": hostname as NSObject, + "notice_port": noticePort as NSObject + ] + + return options + } + + public static func getClientId() -> 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 + } + } + } diff --git a/punchnet/Core/UDPNoticeCenterServer.swift b/punchnet/Core/UDPNoticeCenterServer.swift index e0f3bf0..d93bd8c 100644 --- a/punchnet/Core/UDPNoticeCenterServer.swift +++ b/punchnet/Core/UDPNoticeCenterServer.swift @@ -15,32 +15,23 @@ final class UDPNoticeCenterServer: ChannelInboundHandler { public typealias OutboundOut = AddressedEnvelope private var group: MultiThreadedEventLoopGroup? - private var thread: Thread? + private var channel: Channel? var messageFlow = PassthroughSubject() - static let shared = UDPNoticeCenterServer() - - private init() { - - } + public var port: Int = 0 func start() { - self.thread = Thread { - self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let bootstrap = DatagramBootstrap(group: self.group!) - .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) - .channelInitializer { channel in - channel.pipeline.addHandler(self) - } - - let channel = try! bootstrap.bind(host: "127.0.0.1", port: 50195).wait() - try! channel.closeFuture.wait() - } - self.thread?.start() + self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let bootstrap = DatagramBootstrap(group: self.group!) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .channelInitializer { channel in + channel.pipeline.addHandler(self) + } + self.channel = try! bootstrap.bind(host: "127.0.0.1", port: 0).wait() + self.port = self.channel?.localAddress?.port ?? 0 } func stop() { - self.thread?.cancel() try? self.group?.syncShutdownGracefully() } @@ -49,26 +40,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/VPNManager.swift b/punchnet/Core/VPNManager.swift similarity index 89% rename from punchnet/VPNManager.swift rename to punchnet/Core/VPNManager.swift index d905fab..ec8ec80 100644 --- a/punchnet/VPNManager.swift +++ b/punchnet/Core/VPNManager.swift @@ -28,14 +28,12 @@ class VPNManager: ObservableObject { // 开启vpn func enableVpn(options: [String : NSObject]) async throws { + NSLog("enable vpn with options: \(options)") let manager = try await loadAndCreateProviderManager() try await manager.loadFromPreferences() self.addVPNStatusObserver(manager) - var configOptions = try PunchnetConfig.getOptions() - configOptions.merge(options, uniquingKeysWith: {$1}) - - try manager.connection.startVPNTunnel(options: configOptions) + try manager.connection.startVPNTunnel(options: options) } // 关闭vpn @@ -76,14 +74,14 @@ class VPNManager: ObservableObject { let managers = try await NETunnelProviderManager.loadAllFromPreferences() let manager = managers.first ?? NETunnelProviderManager() - manager.localizedDescription = "punchnet" + manager.localizedDescription = "punchnetmac" manager.isEnabled = true // 设置相关参数,在PacketTunnel中可以用 let protocolConfiguration = NETunnelProviderProtocol() - protocolConfiguration.serverAddress = "punchnet" + protocolConfiguration.serverAddress = "punchnetmac" protocolConfiguration.providerConfiguration = [String:AnyObject]() - protocolConfiguration.providerBundleIdentifier = "com.jihe.punchnet.tun" + protocolConfiguration.providerBundleIdentifier = "com.jihe.punchnetmac.tun" manager.protocolConfiguration = protocolConfiguration manager.isOnDemandEnabled = false diff --git a/punchnet/AbortView.swift b/punchnet/Views/AbortView.swift similarity index 99% rename from punchnet/AbortView.swift rename to punchnet/Views/AbortView.swift index 7c05c29..36ba170 100644 --- a/punchnet/AbortView.swift +++ b/punchnet/Views/AbortView.swift @@ -9,7 +9,6 @@ import Foundation import SwiftUI struct AbortView: View { - struct AlertShow: Identifiable { enum ShowContent { case error(String) diff --git a/punchnet/ContentView.swift b/punchnet/Views/IndexView.swift similarity index 54% rename from punchnet/ContentView.swift rename to punchnet/Views/IndexView.swift index 8256770..5b4ac3d 100644 --- a/punchnet/ContentView.swift +++ b/punchnet/Views/IndexView.swift @@ -9,9 +9,11 @@ import SwiftUI import SwiftData import Combine -struct ContentView: View { - +struct IndexView: View { @AppStorage("token") private var token: String = "" + @AppStorage("hostname") private var hostname: String = "" + @AppStorage("network_code") private var networkCode: String = "" + @State private var showToken: Bool = false @ObservedObject private var vpnManager = VPNManager.shared @@ -22,6 +24,15 @@ struct ContentView: View { @State private var showMenu: Bool = false + @State private var networkProfile: SDLAPI.NetworkProfile = .init(network: []) + @State private var selectedIdx: Int = 0 + + // 显示ip信息 + @State private var showIpAdress: Bool = false + @State private var ipAddress: String = "" + + public var noticeServer: UDPNoticeCenterServer + var body: some View { VStack(alignment: .center, spacing: 10) { @@ -47,20 +58,63 @@ struct ContentView: View { .onTapGesture { self.showMenu = false } + TextField("主机名", text: $hostname) + .multilineTextAlignment(.leading) + .textFieldStyle(PlainTextFieldStyle()) + .frame(width: 200, height: 25) + .background(Color.white) + .foregroundColor(Color.black) + .cornerRadius(5.0) + + if showIpAdress { + HStack { + Spacer() + + Text("ip: ") + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + .cornerRadius(5.0) + + Text(ipAddress) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + .cornerRadius(5.0) + + Spacer() + } + } Spacer() .frame(width: 1, height: 10) - if showToken { - TextField("邀请码", text: $token) - .multilineTextAlignment(.leading) - .textFieldStyle(PlainTextFieldStyle()) - .frame(width: 200, height: 25) - .background(Color.white) - .foregroundColor(Color.black) - .cornerRadius(5.0) + VStack(spacing: 0) { + ForEach(Array(networkProfile.network.enumerated()), id: \.offset) { idx, network in + NetworkItemView(idx: idx, item: network) + .padding(.horizontal, 8) + .padding(.vertical, 6) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(selectedIdx == idx ? Color.blue.opacity(0.3) : Color.clear) + ) + .contentShape(Rectangle()) + .onTapGesture { + withAnimation(.easeInOut(duration: 0.2)) { + selectedIdx = idx + self.networkCode = network.code + } + } + } } + TextField("邀请码", text: $token) + .multilineTextAlignment(.leading) + .textFieldStyle(PlainTextFieldStyle()) + .frame(width: 200, height: 25) + .background(Color.white) + .foregroundColor(Color.black) + .cornerRadius(5.0) + .opacity(showToken ? 1 : 0) + Spacer() .frame(width: 1, height: 10) @@ -82,8 +136,6 @@ struct ContentView: View { } } } - - Spacer() } .overlay(alignment: .top) { @@ -161,26 +213,50 @@ struct ContentView: View { } .offset(x: 0, y: 10) } - .frame(width: 300, height: 500) + .padding([.leading, .trailing, .top], 10) + .padding([.bottom], 20) .background(Color(red: 36 / 255, green: 38 / 255, blue: 51 / 255)) + .frame(width: 320) .alert(isPresented: $showAlert) { Alert(title: Text("请输入正确的邀请码")) } .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("")) } } + .task { + do { + let response = try await SDLAPI.getUserNetworks(clientId: SystemConfig.getClientId()) + print("get user networks: \(response)") + if let result = response.result { + self.networkProfile = result + if self.networkProfile.network.count > 0 { + self.networkCode = self.networkProfile.network[0].code + } + } + } catch let err { + NSLog("get user networks get error: \(err)") + } + } .onAppear { - self.cancel = UDPNoticeCenterServer.shared.messageFlow.sink{ message in + self.cancel = self.noticeServer.messageFlow.sink{ message in DispatchQueue.main.async { - self.message = message - self.showStunAlert = true + switch message { + case .none: + () + case .ip(let ip): + self.showIpAdress = true + self.ipAddress = ip + default: + self.message = message + self.showStunAlert = true + } } } } @@ -189,26 +265,43 @@ struct ContentView: View { private func clickSwitchButton() async throws { switch self.vpnManager.vpnStatus { case .connected: + self.showIpAdress = false + self.ipAddress = "" try await vpnManager.disableVpn() case .disconnected: - /* - if self.token.isEmpty { - self.showAlert = true - return - } - */ - //print("use port: \(vpnManager.noticePort as NSObject)") - try await vpnManager.enableVpn(options: [ - "version:": SystemConfig.version as NSObject, - "installed_channel": SystemConfig.installedChannel as NSObject, - "token": self.token as NSObject - ]) + let clientId = SystemConfig.getClientId() + NSLog("[IndexView] use token: \(self.token), network_code: \(networkCode)") + // token存在则优先使用token + try await vpnManager.enableVpn(options: SystemConfig.getOptions(networkCode: self.networkCode, token: self.token, clientId: clientId, hostname: self.hostname, noticePort: self.noticeServer.port)!) } } } +extension IndexView { + struct NetworkItemView: View { + let idx: Int + let item: SDLAPI.NetworkProfile.NetworkItem + + var body: some View { + HStack { + Text(item.name) + .font(.system(size: 14)) + .foregroundColor(.white) + .frame(width: 80, alignment: .leading) + + Text(item.code) + .font(.system(size: 14)) + .foregroundColor(.white) + + Spacer() + } + } + } +} + #Preview { - ContentView() + let server = UDPNoticeCenterServer() + IndexView(noticeServer: server) //.modelContainer(for: Item.self, inMemory: true) } diff --git a/punchnet/punchnetApp.swift b/punchnet/punchnetApp.swift index 0eb8178..9e5f4e0 100644 --- a/punchnet/punchnetApp.swift +++ b/punchnet/punchnetApp.swift @@ -31,12 +31,20 @@ struct punchnetApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @AppStorage("token") var token: String = "" + @AppStorage("network_code") var networkCode: String = "" + @AppStorage("hostname") var hostname: String = "" @ObservedObject var vpnManager = VPNManager.shared + private var noticeServer: UDPNoticeCenterServer + + init() { + self.noticeServer = UDPNoticeCenterServer() + self.noticeServer.start() + } + var body: some Scene { WindowGroup(id: "mainWindow") { - ContentView() - .frame(minWidth: 300, maxWidth: 300, minHeight: 500, maxHeight: 500) + IndexView(noticeServer: self.noticeServer) .onAppear { // 获取主屏幕的尺寸 guard let screenFrame = NSScreen.main?.frame else { return } @@ -53,6 +61,8 @@ struct punchnetApp: App { window.setFrameOrigin(NSPoint(x: centerX, y: centerY)) } } + .toolbar(.hidden) + .navigationTitle("") } .commands { CommandGroup(replacing: .appInfo) { @@ -94,11 +104,8 @@ struct punchnetApp: App { switch self.vpnManager.vpnStatus { case .disconnected: Task { - try await vpnManager.enableVpn(options: [ - "version:": SystemConfig.version as NSObject, - "installed_channel": SystemConfig.installedChannel as NSObject, - "token": token as NSObject - ]) + let clientId = SystemConfig.getClientId() + try await vpnManager.enableVpn(options: SystemConfig.getOptions(networkCode: self.networkCode, token: self.token, clientId: clientId, hostname: self.hostname, noticePort: self.noticeServer.port)!) } case .connected: Task { @@ -113,7 +120,7 @@ struct punchnetApp: App { class AppDelegate: NSObject, NSApplicationDelegate { func applicationWillFinishLaunching(_ notification: Notification) { - UDPNoticeCenterServer.shared.start() + } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -122,7 +129,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { DispatchQueue.main.async { sender.reply(toApplicationShouldTerminate: true) } - UDPNoticeCenterServer.shared.stop() } return .terminateLater