From f22e962bd7f1afdf8a126f5a51a715290e01f908 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Fri, 10 Apr 2026 15:23:12 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3dns=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tun/PacketTunnelProvider.swift | 12 +-- Tun/Punchnet/Actors/SDLContextActor.swift | 38 +++++-- Tun/Punchnet/DNS/DNSLocalClient.swift | 119 +++++++++++++++++++++- Tun/Punchnet/DNS/DNSParser.swift | 22 ++-- Tun/Punchnet/NetworkStack/IPPacket.swift | 7 +- Tun/Punchnet/SDLConfiguration.swift | 12 ++- Tun/Punchnet/SDLUtil.swift | 27 +++++ 7 files changed, 209 insertions(+), 28 deletions(-) diff --git a/Tun/PacketTunnelProvider.swift b/Tun/PacketTunnelProvider.swift index 6629664..216539b 100644 --- a/Tun/PacketTunnelProvider.swift +++ b/Tun/PacketTunnelProvider.swift @@ -23,12 +23,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { SDLLogger.shared.log("NE read message: \(msg ?? "failed")") DarwinNotificationCenter.shared.post(.vpnStatusChanged) - - // host: "192.168.0.101", port: 1265 - guard let options, let config = SDLConfiguration.parse(options: options) else { - completionHandler(TunnelError.invalidConfiguration) - return - } // 如果当前在运行状态,不允许重复请求 guard self.contextActor == nil else { @@ -39,6 +33,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // 加密算法 let rsaCipher = try! CCRSACipher(keySize: 1024) self.rootTask = Task { + // host: "192.168.0.101", port: 1265 + guard let options, let config = await SDLConfiguration.parse(options: options) else { + completionHandler(TunnelError.invalidConfiguration) + return + } + self.contextActor = SDLContextActor(provider: self, config: config, rsaCipher: rsaCipher) await self.contextActor?.start() completionHandler(nil) diff --git a/Tun/Punchnet/Actors/SDLContextActor.swift b/Tun/Punchnet/Actors/SDLContextActor.swift index 4cfa1ea..ca028d9 100644 --- a/Tun/Punchnet/Actors/SDLContextActor.swift +++ b/Tun/Punchnet/Actors/SDLContextActor.swift @@ -103,6 +103,7 @@ actor SDLContextActor { } public func start() async { + self.startMonitor() // 启动arp的定时清理任务 @@ -233,7 +234,7 @@ actor SDLContextActor { self.dnsWorker = nil // 启动dns服务 - let dnsClient = DNSCloudClient(host: self.config.serverHost, port: 15353, logger: SDLLogger.shared) + let dnsClient = DNSCloudClient(host: self.config.serverIp, port: 15353, logger: SDLLogger.shared) dnsClient.start() SDLLogger.shared.log("[SDLContext] dnsClient started") self.dnsClient = dnsClient @@ -264,6 +265,8 @@ actor SDLContextActor { if Task.isCancelled { break } + + // 要想办法构造一个完整的Ip包 let nePacket = NEPacket(data: packet, protocolFamily: 2) self.provider.packetFlow.writePacketObjects([nePacket]) } @@ -693,8 +696,31 @@ actor SDLContextActor { let networkAddr = self.config.networkAddress if DNSHelper.isDnsRequestPacket(ipPacket: packet) { + // 数据是通过offset解析的, dns查询必然是udp包 + if case .udp(let udpPacket) = packet.transportPacket { + let payloadOffset = udpPacket.payloadOffset + let dnsParser = DNSParser(data: packet.data, offset: payloadOffset) + if let dnsMessage = dnsParser.parse(), let name = dnsMessage.questions.first?.name { + // 如果是内部域名,则转发整个ip包的内容到云端服务器 + if name.contains(self.config.networkAddress.networkDomain) { + SDLLogger.shared.log("[Tun] get cloud dns request: \(name)") + self.dnsClient?.forward(ipPacketData: packet.data) + } + // 通过本地的dns解析,发送的是udp的payload部分 + else if packet.data.count > payloadOffset { + // 尝试解析下对不对 + let dnsPayload = Data(packet.data[payloadOffset.. private let packetContinuation: AsyncStream.Continuation + + private let locker = NSLock() + private var trackers: [UInt16: [DNSTracker]] = [:] init() { let (stream, continuation) = AsyncStream.makeStream(of: Data.self, bufferingPolicy: .unbounded) @@ -29,6 +40,7 @@ final class DNSLocalClient { case .ready: self?.receiveLoop(for: conn) case .failed(let error): + SDLLogger.shared.log("[DNSLocalClient] failed with error: \(error.localizedDescription)") self?.stop() case .cancelled: self?.packetContinuation.finish() @@ -43,7 +55,11 @@ final class DNSLocalClient { } /// 并发查询:对所有服务器广播 - func query(dnsPayload: Data) { + func query(tracker: DNSTracker, dnsPayload: Data) { + locker.lock() + self.trackers[tracker.transactionID, default: []].append(tracker) + locker.unlock() + for conn in connections where conn.state == .ready { conn.send(content: dnsPayload, completion: .contentProcessed({ _ in })) } @@ -55,8 +71,7 @@ final class DNSLocalClient { // !!!核心:由于 AsyncStream 是流式的 // 谁先 yield,上层就先收到谁。 // 只要上层收到了第一个有效响应并回填给系统, - // 后面迟到的重复响应会被系统协议栈自动忽略(因为 Transaction ID 已失效) - self?.packetContinuation.yield(data) + self?.handleResponse(data: data) } if error == nil && conn.state == .ready { @@ -65,6 +80,30 @@ final class DNSLocalClient { } } + private func handleResponse(data: Data) { + let dnsParser = DNSParser(data: data, offset: 0) + if let message = dnsParser.parse() { + let tranId = message.transactionID + + locker.lock() + let items = self.trackers.removeValue(forKey: tranId) + locker.unlock() + + if let items { + items.forEach { tracker in + let packet = Self.createDNSResponse( + payload: data, + srcIP: DNSHelper.dnsDestIpAddr, + srcPort: 53, + destIP: tracker.clientIP, + destPort: tracker.clientPort + ) + self.packetContinuation.yield(packet) + } + } + } + } + func stop() { connections.forEach { conn in conn.cancel() @@ -73,3 +112,77 @@ final class DNSLocalClient { } } + +extension DNSLocalClient { + /// 构造发回 TUN 的完整 UDP/IPv4 数据包 + static func createDNSResponse(payload: Data, srcIP: UInt32, srcPort: UInt16, destIP: UInt32, destPort: UInt16) -> Data { + let udpLen = 8 + payload.count + let ipLen = 20 + udpLen + + // --- 1. IPv4 Header (20 字节) --- + var ipHeader = Data(count: 20) + ipHeader[0] = 0x45 // Version 4, IHL 5 + ipHeader[2...3] = withUnsafeBytes(of: UInt16(ipLen).bigEndian) { Data($0) } + ipHeader[8] = 64 // TTL + ipHeader[9] = 17 // Protocol UDP + + // 填充 IP 地址 + ipHeader[12...15] = withUnsafeBytes(of: srcIP.bigEndian) { Data($0) } + ipHeader[16...19] = withUnsafeBytes(of: destIP.bigEndian) { Data($0) } + + // 计算 IP Checksum + let ipChecksum = calculateChecksum(data: ipHeader) + ipHeader[10...11] = withUnsafeBytes(of: ipChecksum.bigEndian) { Data($0) } + + // --- 2. UDP Header (8 字节) --- + var udpHeader = Data(count: 8) + udpHeader[0...1] = withUnsafeBytes(of: srcPort.bigEndian) { Data($0) } + udpHeader[2...3] = withUnsafeBytes(of: destPort.bigEndian) { Data($0) } + udpHeader[4...5] = withUnsafeBytes(of: UInt16(udpLen).bigEndian) { Data($0) } + // UDP Checksum 在 IPv4 中可选,设为 0 可跳过计算(大部分系统接受) + udpHeader[6...7] = Data([0, 0]) + + // --- 3. 拼接 --- + var packet = Data(capacity: ipLen) + packet.append(ipHeader) + packet.append(udpHeader) + packet.append(payload) + + return packet + } + + /// 经典的 Internet Checksum 算法 + static func calculateChecksum(data: Data) -> UInt16 { + var sum: UInt32 = 0 + let count = data.count + + data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in + guard let baseAddress = ptr.baseAddress else { return } + + // 1. 处理成对的 16-bit 单词 + let wordCount = count / 2 + let words = baseAddress.bindMemory(to: UInt16.self, capacity: wordCount) + + for i in 0..> 16) != 0 { + sum = (sum & 0xffff) + (sum >> 16) + } + + return UInt16(~sum & 0xffff) + } + + +} diff --git a/Tun/Punchnet/DNS/DNSParser.swift b/Tun/Punchnet/DNS/DNSParser.swift index 0be12d1..9c7d9fa 100644 --- a/Tun/Punchnet/DNS/DNSParser.swift +++ b/Tun/Punchnet/DNS/DNSParser.swift @@ -29,7 +29,9 @@ struct DNSMessage { var questions: [DNSQuestion] = [] var answers: [DNSResourceRecord] = [] - var isResponse: Bool { (flags & 0x8000) != 0 } + var isResponse: Bool { + (flags & 0x8000) != 0 + } } // MARK: - DNS 完整解析器 @@ -37,13 +39,15 @@ final class DNSParser { private let data: Data private var offset: Int = 0 - init(data: Data) { + init(data: Data, offset: Int) { self.data = data + self.offset = offset } func parse() -> DNSMessage? { - guard data.count >= 12 else { return nil } - offset = 0 + guard data.count >= 12 + self.offset else { + return nil + } let id = readUInt16() let flags = readUInt16() @@ -55,11 +59,17 @@ final class DNSParser { var message = DNSMessage(transactionID: id, flags: flags) for _ in 0..= offset + 8 else { return nil } - + self.srcPort = UInt16(bytes: (data[offset], data[offset + 1])) self.dstPort = UInt16(bytes: (data[offset + 2], data[offset + 3])) self.length = UInt16(bytes: (data[offset + 4], data[offset + 5])) self.checksum = UInt16(bytes: (data[offset + 6], data[offset + 7])) + self.payloadOffset = offset + 8 } + } // MARK: - ICMP Packet diff --git a/Tun/Punchnet/SDLConfiguration.swift b/Tun/Punchnet/SDLConfiguration.swift index 7b05db4..b763705 100644 --- a/Tun/Punchnet/SDLConfiguration.swift +++ b/Tun/Punchnet/SDLConfiguration.swift @@ -49,6 +49,7 @@ public class SDLConfiguration { let version: Int let serverHost: String + let serverIp: String let stunServers: [String] let noticePort: Int @@ -78,6 +79,7 @@ public class SDLConfiguration { public init(version: Int, serverHost: String, + serverIp: String, stunServers: [String], clientId: String, networkAddress: NetworkAddress, @@ -86,9 +88,9 @@ public class SDLConfiguration { accessToken: String, identityId: UInt32, exitNode: ExitNode?) { - self.version = version self.serverHost = serverHost + self.serverIp = serverIp self.stunServers = stunServers self.clientId = clientId self.networkAddress = networkAddress @@ -104,7 +106,7 @@ public class SDLConfiguration { // 解析配置文件 extension SDLConfiguration { - static func parse(options: [String: NSObject]) -> SDLConfiguration? { + static func parse(options: [String: NSObject]) async -> SDLConfiguration? { guard let version = options["version"] as? Int, let serverHost = options["server_host"] as? String, let stunAssistHost = options["stun_assist_host"] as? String, @@ -121,6 +123,11 @@ extension SDLConfiguration { return nil } + // 解析dns域名所在的服务器地址 + guard let serverIp = await SDLUtil.resolveHostname(host: serverHost) else { + return nil + } + // 网络出口配置是可选的 var exitNode: ExitNode? = nil if let exitNodeIpStr = options["exit_node_ip"] as? String, let exitNodeIp = SDLUtil.ipv4StrToInt32(exitNodeIpStr) { @@ -129,6 +136,7 @@ extension SDLConfiguration { return SDLConfiguration(version: version, serverHost: serverHost, + serverIp: serverIp, stunServers: [serverHost, stunAssistHost], clientId: clientId, networkAddress: networkAddress, diff --git a/Tun/Punchnet/SDLUtil.swift b/Tun/Punchnet/SDLUtil.swift index a5df685..124fe03 100644 --- a/Tun/Punchnet/SDLUtil.swift +++ b/Tun/Punchnet/SDLUtil.swift @@ -7,6 +7,7 @@ import Foundation import SystemConfiguration +import Network struct SDLUtil { @@ -76,5 +77,31 @@ struct SDLUtil { } return results } + + // 域名解析 + static func resolveHostname(host: String) async -> String? { + let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: 53) + let parameters = NWParameters.udp + // 即使还没正式开始,也加上这个,确保不会被残留的旧 utun 路由卡死 + parameters.prohibitedInterfaceTypes = [.other] + + let connection = NWConnection(to: endpoint, using: parameters) + + return await withCheckedContinuation { continuation in + connection.stateUpdateHandler = { state in + if case .ready = state { + if let path = connection.currentPath, + case .hostPort(let resolvedHost, _) = path.remoteEndpoint { + let ip = String(describing: resolvedHost) + continuation.resume(returning: ip) + connection.cancel() + } + } else if case .failed = state { + continuation.resume(returning: nil) + } + } + connection.start(queue: .global()) + } + } }