// // SDLContext.swift // Tun // // Created by 安礼成 on 2024/2/29. // import Foundation import NetworkExtension import NIOCore // 上下文环境变量,全局共享 /* 1. 处理rsa的加解密逻辑 */ actor SDLContextActor { enum State { case unregistered case registered } private var state: State = .unregistered nonisolated let config: SDLConfiguration // nat的网络类型 var natType: SDLNATProberActor.NatType = .blocked // AES加密,授权通过后,对象才会被创建 nonisolated let aesCipher: AESCipher // aes private var aesKey: Data? // session token private var sessionToken: Data? // rsa的相关配置, public_key是本地生成的 nonisolated let rsaCipher: RSACipher // 依赖的变量 private var udpHole: SDLUDPHole? private var udpHoleWorkers: [Task]? // dns的client对象 private var dnsClient: SDLDNSClient? private var dnsWorker: Task? private var quicClient: SDLQUICClient? private var quicWorker: Task? nonisolated private let puncherActor: SDLPuncherActor // 网络探测对象 nonisolated private let proberActor: SDLNATProberActor // 数据包读取任务 private var readTask: Task<(), Never>? private var sessionManager: SessionManager private var arpServer: ArpServerActor // 网络状态变化的健康 private var monitor: SDLNetworkMonitor? private var monitorWorker: Task? // 内部socket通讯 private var noticeClient: SDLNoticeClient? // 流量统计 nonisolated private let flowTracer = SDLFlowTracer() // 处理内部的需要长时间运行的任务 private var supervisor = SDLSupervisor() private let provider: NEPacketTunnelProvider // 处理权限控制 private let identifyStore: IdentityStore private var updatePolicyTask: Task? private let snapshotPublisher: SnapshotPublisher // Flow流会话管理, 过期时间为: 180秒 private let flowSessionManager = SDLFlowSessionManager(sessionTimeout: 180) // 注册任务 private var registerTask: Task? public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher) { self.provider = provider self.config = config self.rsaCipher = rsaCipher self.aesCipher = aesCipher self.sessionManager = SessionManager() self.arpServer = ArpServerActor() self.puncherActor = SDLPuncherActor() self.proberActor = SDLNATProberActor(addressArray: config.stunProbeSocketAddressArray) // 权限控制 let snapshotPublisher = SnapshotPublisher(initial: IdentitySnapshot.empty()) self.identifyStore = IdentityStore(publisher: snapshotPublisher) self.snapshotPublisher = snapshotPublisher } public func start() async { self.startMonitor() await self.supervisor.addWorker(name: "quicClient") { SDLLogger.shared.log("[SDLContext] try start quicClient") let quicClient = try await self.startQUICClient() SDLLogger.shared.log("[SDLContext] quicClient running!!!!") await quicClient.waitClose() SDLLogger.shared.log("[SDLContext] quicClient closed!!!!") } await self.supervisor.addWorker(name: "noticeClient") { let noticeClient = try self.startNoticeClient() SDLLogger.shared.log("[SDLContext] noticeClient running!!!!") try await noticeClient.waitClose() SDLLogger.shared.log("[SDLContext] noticeClient closed!!!!") } await self.supervisor.addWorker(name: "dnsClient") { let dnsClient = try await self.startDnsClient() SDLLogger.shared.log("[SDLContext] dns running!!!!") try await dnsClient.waitClose() SDLLogger.shared.log("[SDLContext] dns closed!!!!") } await self.supervisor.addWorker(name: "udpHole") { let udpHole = try await self.startUDPHole() SDLLogger.shared.log("[SDLContext] udp running!!!!") try await udpHole.waitClose() SDLLogger.shared.log("[SDLContext] udp closed!!!!") } } private func startQUICClient() async throws -> SDLQUICClient { self.quicWorker?.cancel() self.quicClient?.stop() // 启动monitor let quicClient = SDLQUICClient(host: self.config.serverIp, port: 443) quicClient.start() // 等待quic准备好 try await quicClient.waitReady() // 这里必须等待quic的协商完成 try await Task.sleep(for: .seconds(0.2)) SDLLogger.shared.log("[SDLContext] start quic client ready") self.quicWorker = Task.detached { for await message in quicClient.messageStream { switch message { case .welcome(let welcome): SDLLogger.shared.log("[SDLContext] quic welcome: \(welcome)") // 注册 await self.startRegisterLoop() case .pong: SDLLogger.shared.log("[SDLContext] quic pong") case .registerSuperAck(let registerSuperAck): await self.handleRegisterSuperAck(registerSuperAck: registerSuperAck) case .registerSuperNak(let registerSuperNak): await self.handleRegisterSuperNak(nakPacket: registerSuperNak) case .peerInfo(let peerInfo): SDLLogger.shared.log("[SDLContext] peer message: \(peerInfo)") case .event(let event): await self.handleEvent(event: event) case .policyReponse(let policyResponse): // 处理权限的请求问题 await self.identifyStore.applyPolicyResponse(policyResponse) case .arpResponse(let arpResponse): await self.arpServer.handleArpResponse(arpResponse: arpResponse) } } } self.quicClient = quicClient return quicClient } private func startNoticeClient() throws -> SDLNoticeClient { // 启动noticeClient let noticeClient = try SDLNoticeClient(noticePort: self.config.noticePort) noticeClient.start() SDLLogger.shared.log("[SDLContext] noticeClient started") self.noticeClient = noticeClient return noticeClient } private func startMonitor() { self.monitorWorker?.cancel() self.monitorWorker = nil // 启动monitor let monitor = SDLNetworkMonitor() monitor.start() SDLLogger.shared.log("[SDLContext] monitor started") self.monitor = monitor self.monitorWorker = Task.detached { for await event in monitor.eventStream { switch event { case .changed: // 需要重新探测网络的nat类型 await self.probeNatType() SDLLogger.shared.log("didNetworkPathChanged, nat type is:", level: .info) case .unreachable: SDLLogger.shared.log("didNetworkPathUnreachable", level: .warning) } } } } private func startDnsClient() async throws -> SDLDNSClient { self.dnsWorker?.cancel() self.dnsWorker = nil // 启动dns服务 let dnsSocketAddress = try SocketAddress.makeAddressResolvingHost(self.config.serverIp, port: 15353) let dnsClient = try await SDLDNSClient(dnsServerAddress: dnsSocketAddress, logger: SDLLogger.shared) try dnsClient.start() SDLLogger.shared.log("[SDLContext] dnsClient started") self.dnsClient = dnsClient self.dnsWorker = Task.detached { // 处理事件流 for await packet in dnsClient.packetFlow { if Task.isCancelled { break } let nePacket = NEPacket(data: packet, protocolFamily: 2) self.provider.packetFlow.writePacketObjects([nePacket]) } } return dnsClient } private func startUDPHole() async throws -> SDLUDPHole { self.udpHoleWorkers?.forEach {$0.cancel()} self.udpHoleWorkers = nil // 启动udp服务器 let udpHole = try SDLUDPHole() try udpHole.start() SDLLogger.shared.log("[SDLContext] udpHole started") // 获取当前udp启动的地址 let localAddress = udpHole.getLocalAddress() // 阻塞等待udpHole是准备好的状态 await udpHole.channelIsActived() // 处理心跳逻辑 let pingTask = Task.detached { let timerStream = SDLAsyncTimerStream() timerStream.start(interval: .seconds(5)) for await _ in timerStream.stream { if Task.isCancelled { break } await self.sendStunRequest() } SDLLogger.shared.log("[SDLContext] udp pingTask cancel") } // 处理消息流 let messageTask = Task.detached { for await (remoteAddress, message) in udpHole.messageStream { if Task.isCancelled { break } switch message { case .stunProbeReply(let probeReply): await self.proberActor.handleProbeReply(localAddress: localAddress, reply: probeReply) case .register(let register): try? await self.handleRegister(remoteAddress: remoteAddress, register: register) case .registerAck(let registerAck): await self.handleRegisterAck(remoteAddress: remoteAddress, registerAck: registerAck) case .data(let data): try? await self.handleHoleData(data: data) case .stunReply(let stunReply): SDLLogger.shared.log("[SDLContext] get a stunReply: \(stunReply)") } } SDLLogger.shared.log("[SDLContext] udp signalTask cancel") } self.udpHole = udpHole self.udpHoleWorkers = [pingTask, messageTask] // 开始探测nat的类型 self.probeNatType() return udpHole } // 处理context的停止问题 public func stop() async { await self.supervisor.stop() self.udpHoleWorkers?.forEach { $0.cancel() } self.udpHoleWorkers = nil self.quicWorker?.cancel() self.quicWorker = nil self.dnsWorker?.cancel() self.dnsWorker = nil self.monitorWorker?.cancel() self.monitorWorker = nil self.readTask?.cancel() self.readTask = nil self.registerTask?.cancel() self.registerTask = nil self.updatePolicyTask?.cancel() self.updatePolicyTask = nil } private func setNatType(natType: SDLNATProberActor.NatType) { self.natType = natType } // 开启注册任务 private func startRegisterLoop() { guard self.registerTask == nil else { return } self.registerTask = Task { while !Task.isCancelled { self.doRegisterSuper() try? await Task.sleep(for: .seconds(5)) if self.state == .registered { await self.whenRegistedSuper() break } SDLLogger.shared.log("[SDLContext] register super failed, retry") } self.registerTask = nil } } // 注册成功super的回调函数 private func whenRegistedSuper() async { self.updatePolicyTask?.cancel() self.updatePolicyTask = Task { while !Task.isCancelled { try? await Task.sleep(for: .seconds(300)) SDLLogger.shared.log("[SDLContext] updatePolicyTask execute") await self.identifyStore.batUpdatePolicy(using: self.quicClient, dstIdentityID: self.config.identityId) } } } private func sendStunRequest() { guard let sessionToken = self.sessionToken else { return } var stunRequest = SDLStunRequest() stunRequest.clientID = self.config.clientId stunRequest.networkID = self.config.networkAddress.networkId stunRequest.ip = self.config.networkAddress.ip stunRequest.mac = self.config.networkAddress.mac stunRequest.natType = UInt32(self.natType.rawValue) stunRequest.sessionToken = sessionToken if let stunData = try? stunRequest.serializedData() { let remoteAddress = self.config.stunSocketAddress self.udpHole?.send(type: .stunRequest, data: stunData, remoteAddress: remoteAddress) } } private func handleRegisterSuperAck(registerSuperAck: SDLRegisterSuperAck) async { // 需要对数据通过rsa的私钥解码 self.aesKey = try! self.rsaCipher.decode(data: Data(registerSuperAck.aesKey)) self.sessionToken = registerSuperAck.sessionToken SDLLogger.shared.log("[SDLContext] get registerSuperAck, aes_key len: \(self.aesKey!.count)", level: .info) // 服务器分配的tun网卡信息 do { try await self.setNetworkSettings(networkAddress: self.config.networkAddress, dnsServer: SDLDNSClient.Helper.dnsServer) SDLLogger.shared.log("[SDLContext] setNetworkSettings successed") self.state = .registered self.startReader() } catch let err { SDLLogger.shared.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error) self.provider.cancelTunnelWithError(err) } } private func handleRegisterSuperNak(nakPacket: SDLRegisterSuperNak) { 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) self.noticeClient?.send(data: alertNotice) // 报告错误并退出 let error = NSError(domain: "com.jihe.punchnet.tun", code: -1) self.provider.cancelTunnelWithError(error) case .noIpAddress, .networkFault, .internalFault: let alertNotice = NoticeMessage.alert(alert: errorMessage) self.noticeClient?.send(data: alertNotice) } SDLLogger.shared.log("[SDLContext] Get a SuperNak message exit", level: .warning) } private func handleEvent(event: SDLEvent) async { switch event { // case .dropMacs(let dropMacsEvent): // SDLLogger.shared.log("[SDLContext] drop macs", level: .info) // await self.arpServer.dropMacs(macs: dropMacsEvent.macs) case .natChanged(let natChangedEvent): let dstMac = natChangedEvent.mac SDLLogger.shared.log("[SDLContext] natChangedEvent, dstMac: \(dstMac)", level: .info) sessionManager.removeSession(dstMac: dstMac) case .sendRegister(let sendRegisterEvent): SDLLogger.shared.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.config.networkAddress.networkId register.srcMac = self.config.networkAddress.mac register.dstMac = sendRegisterEvent.dstMac self.udpHole?.send(type: .register, data: try! register.serializedData(), remoteAddress: remoteAddress) } case .networkShutdown(let shutdownEvent): let alertNotice = NoticeMessage.alert(alert: shutdownEvent.message) self.noticeClient?.send(data: alertNotice) // 报告错误并退出 let error = NSError(domain: "com.jihe.punchnet.tun", code: -2) self.provider.cancelTunnelWithError(error) } } private func doRegisterSuper() { // 注册 var registerSuper = SDLRegisterSuper() registerSuper.clientID = self.config.clientId registerSuper.networkID = self.config.networkAddress.networkId registerSuper.mac = self.config.networkAddress.mac registerSuper.ip = self.config.networkAddress.ip registerSuper.maskLen = UInt32(self.config.networkAddress.maskLen) registerSuper.hostname = self.config.hostname registerSuper.pubKey = self.rsaCipher.pubKey registerSuper.accessToken = self.config.accessToken if let registerSuperData = try? registerSuper.serializedData() { SDLLogger.shared.log("[SDLContext] will send register super") self.quicClient?.send(type: .registerSuper, data: registerSuperData) } } private func handleRegister(remoteAddress: SocketAddress, register: SDLRegister) throws { let networkAddr = config.networkAddress SDLLogger.shared.log("register packet: \(register), network_address: \(networkAddr)", level: .debug) // 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下 if register.dstMac == networkAddr.mac && register.networkID == networkAddr.networkId { // 回复ack包 var registerAck = SDLRegisterAck() registerAck.networkID = networkAddr.networkId registerAck.srcMac = networkAddr.mac registerAck.dstMac = register.srcMac self.udpHole?.send(type: .registerAck, data: try registerAck.serializedData(), remoteAddress: remoteAddress) // 这里需要建立到来源的会话, 在复杂网络下,通过super-node查询到的nat地址不一定靠谱,需要通过udp包的来源地址作为nat地址 let session = Session(dstMac: register.srcMac, natAddress: remoteAddress) self.sessionManager.addSession(session: session) } else { SDLLogger.shared.log("SDLContext didReadRegister get a invalid packet, because dst_ip not matched: \(register.dstMac)", level: .warning) } } private func handleRegisterAck(remoteAddress: SocketAddress, registerAck: SDLRegisterAck) { // 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下 let networkAddr = config.networkAddress if registerAck.dstMac == networkAddr.mac && registerAck.networkID == networkAddr.networkId { let session = Session(dstMac: registerAck.srcMac, natAddress: remoteAddress) self.sessionManager.addSession(session: session) } else { SDLLogger.shared.log("SDLContext didReadRegisterAck get a invalid packet, because dst_mac not matched: \(registerAck.dstMac)", level: .warning) } } private func handleHoleData(data: SDLData) async throws { guard let aesKey = self.aesKey else { return } let mac = LayerPacket.MacAddress(data: data.dstMac) let networkAddr = config.networkAddress guard (data.dstMac == networkAddr.mac || mac.isBroadcast() || mac.isMulticast()) else { return } guard let decyptedData = try? self.aesCipher.decypt(aesKey: aesKey, data: Data(data.data)) else { return } let layerPacket = try LayerPacket(layerData: decyptedData) 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 == networkAddr.ip { switch arpPacket.opcode { case .request: SDLLogger.shared.log("[SDLContext] get arp request packet", level: .debug) let response = ARPPacket.arpResponse(for: arpPacket, mac: networkAddr.mac, ip: networkAddr.ip) await self.routeLayerPacket(dstMac: arpPacket.senderMAC, type: .arp, data: response.marshal()) case .response: SDLLogger.shared.log("[SDLContext] get arp response packet", level: .debug) await self.arpServer.append(ip: arpPacket.senderIP, mac: arpPacket.senderMAC) } } else { SDLLogger.shared.log("[SDLContext] get invalid arp packet: \(arpPacket), target_ip: \(SDLUtil.int32ToIp(arpPacket.targetIP)), net ip: \(SDLUtil.int32ToIp(networkAddr.ip))", level: .debug) } } else { SDLLogger.shared.log("[SDLContext] get invalid arp packet", level: .debug) } case .ipv4: guard let ipPacket = IPPacket(layerPacket.data), ipPacket.header.destination == networkAddr.ip else { return } // 检查权限逻辑 let identitySnapshot = self.snapshotPublisher.current() let ruleMap = identitySnapshot.lookup(data.identityID) if self.checkPolicy(ipPacket: ipPacket, ruleMap: ruleMap) { let packet = NEPacket(data: ipPacket.data, protocolFamily: 2) self.provider.packetFlow.writePacketObjects([packet]) SDLLogger.shared.log("[SDLContext] identity: \(data.identityID), allow", level: .debug) } else { SDLLogger.shared.log("[SDLContext] not found identity: \(data.identityID) ruleMap", level: .debug) // 向服务器请求权限逻辑 await self.identifyStore.policyRequest(srcIdentityId: data.identityID, dstIdentityId: self.config.identityId, using: self.quicClient) } default: SDLLogger.shared.log("[SDLContext] get invalid packet", level: .debug) } } private func checkPolicy(ipPacket: IPPacket, ruleMap: IdentityRuleMap?) -> Bool { // 进来的数据反转一下,然后再处理 if let reverseFlowSession = ipPacket.flowSession()?.reverse(), self.flowSessionManager.hasSession(reverseFlowSession) { self.flowSessionManager.updateSession(reverseFlowSession) return true } // 检查权限逻辑 let proto = ipPacket.header.proto // 优先判断访问规则 switch ipPacket.transportPacket { case .tcp(let tcpPacket): if let ruleMap, ruleMap.isAllow(proto: proto, port: tcpPacket.header.dstPort) { return true } case .udp(let udpPacket): if let ruleMap, ruleMap.isAllow(proto: proto, port: udpPacket.dstPort) { return true } case .icmp(_): return true default: return false } return false } // 流量统计 // 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.detached(priority: .high) { while true { if Task.isCancelled { return } let (packets, numbers) = await self.provider.packetFlow.readPackets() for (data, number) in zip(packets, numbers) where number == 2 { if let ipPacket = IPPacket(data) { await self.dealTunPacket(packet: ipPacket) } } } } } // 处理读取的每个数据包 private func dealTunPacket(packet: IPPacket) async { let networkAddr = self.config.networkAddress if SDLDNSClient.Helper.isDnsRequestPacket(ipPacket: packet) { self.dnsClient?.forward(ipPacket: packet) return } let dstIp = packet.header.destination // 本地通讯, 目标地址是本地服务器的ip地址 if dstIp == networkAddr.ip { let nePacket = NEPacket(data: packet.data, protocolFamily: 2) self.provider.packetFlow.writePacketObjects([nePacket]) return } // 外部出去的数据,需要建立FlowSession // 外部数据进来的时候需要查找 if let flowSession = packet.flowSession() { self.flowSessionManager.updateSession(flowSession) } // 查找arp缓存中是否有目标mac地址 if let dstMac = await self.arpServer.query(ip: dstIp) { await self.routeLayerPacket(dstMac: dstMac, type: .ipv4, data: packet.data) } else { SDLLogger.shared.log("[SDLContext] dstIp: \(dstIp.asIpAddress()) arp query not found, broadcast", level: .debug) // // 构造arp广播 // let arpReqeust = ARPPacket.arpRequest(senderIP: networkAddr.ip, senderMAC: networkAddr.mac, targetIP: dstIp) // await self.routeLayerPacket(dstMac: ARPPacket.broadcastMac , type: .arp, data: arpReqeust.marshal()) try? await self.arpServer.arpRequest(targetIp: dstIp, use: self.quicClient) } } private func routeLayerPacket(dstMac: Data, type: LayerPacket.PacketType, data: Data) async { let networkAddr = self.config.networkAddress // 将数据封装层2层的数据包 let layerPacket = LayerPacket(dstMac: dstMac, srcMac: networkAddr.mac, type: type, data: data) guard let udpHole = self.udpHole, let aesKey = self.aesKey, let encodedPacket = try? self.aesCipher.encrypt(aesKey: aesKey, data: layerPacket.marshal()) else { return } // 构造数据包 var dataPacket = SDLData() dataPacket.networkID = networkAddr.networkId dataPacket.srcMac = networkAddr.mac dataPacket.dstMac = dstMac dataPacket.ttl = 255 dataPacket.identityID = self.config.identityId dataPacket.data = encodedPacket let data = try! dataPacket.serializedData() // 广播地址不要去尝试打洞 if ARPPacket.isBroadcastMac(dstMac) { // 通过super_node进行转发 udpHole.send(type: .data, data: data, remoteAddress: self.config.stunSocketAddress) } else { // 通过session发送到对端 if let session = await self.sessionManager.getSession(toAddress: dstMac) { SDLLogger.shared.log("[SDLContext] send packet by session: \(session)", level: .debug) udpHole.send(type: .data, data: data, remoteAddress: session.natAddress) self.flowTracer.inc(num: data.count, type: .p2p) } else { // 通过super_node进行转发 udpHole.send(type: .data, data: data, remoteAddress: self.config.stunSocketAddress) // 流量统计 self.flowTracer.inc(num: data.count, type: .forward) // 尝试打洞 await self.puncherActor.submitRegisterRequest(quicClient: self.quicClient, request: .init(srcMac: networkAddr.mac, dstMac: dstMac, networkId: networkAddr.networkId)) } } } // 网络改变时需要重新配置网络信息 private func setNetworkSettings(networkAddress: SDLConfiguration.NetworkAddress, dnsServer: String) async throws { let routes: [NEIPv4Route] = [ NEIPv4Route(destinationAddress: networkAddress.netAddress, subnetMask: networkAddress.maskAddress), NEIPv4Route(destinationAddress: 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 = 1250 // 设置网卡的DNS解析 let networkDomain = networkAddress.networkDomain let dnsSettings = NEDNSSettings(servers: [dnsServer]) dnsSettings.searchDomains = [networkDomain] dnsSettings.matchDomains = [networkDomain] dnsSettings.matchDomainsNoSearch = false networkSettings.dnsSettings = dnsSettings let ipv4Settings = NEIPv4Settings(addresses: [networkAddress.ipAddress], subnetMasks: [networkAddress.maskAddress]) // 设置路由表 //NEIPv4Route.default() ipv4Settings.includedRoutes = routes networkSettings.ipv4Settings = ipv4Settings // 网卡配置设置必须成功 try await self.provider.setTunnelNetworkSettings(networkSettings) } // 探测当前网络的类型 private func probeNatType() { Task { guard let udpHole = self.udpHole else { return } // 开始探测nat的类型 self.natType = await self.proberActor.probeNatType(using: udpHole) SDLLogger.shared.log("[SDLContext] nat_type is: \(natType)") } } private func spawnLoop(_ body: @escaping () async throws -> Void) -> Task { return Task.detached { while !Task.isCancelled { do { try await body() } catch is CancellationError { break } catch { try? await Task.sleep(nanoseconds: 2_000_000_000) } } } } deinit { self.udpHole = nil self.dnsClient = nil } } private extension UInt32 { // 转换成ip地址 func asIpAddress() -> String { return SDLUtil.int32ToIp(self) } }