From 7602831ecf9ca0e3b324a74eaddbbde5405e00b0 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Mon, 14 Jul 2025 23:24:12 +0800 Subject: [PATCH] fix Context --- Sources/sdlan/AESCipher.swift | 25 +++ Sources/sdlan/ArpServer.swift | 31 +++ Sources/sdlan/HolerManager.swift | 31 +++ Sources/sdlan/NoticeMessage.swift | 58 +++++ Sources/sdlan/RSACipher.swift | 42 ++++ Sources/sdlan/SDLConfiguration.swift | 57 +++++ Sources/sdlan/SDLContext.swift | 311 ++------------------------- Sources/sdlan/SDLNatProber.swift | 75 +++++++ Sources/sdlan/SessionManager.swift | 57 +++++ 9 files changed, 388 insertions(+), 299 deletions(-) create mode 100644 Sources/sdlan/AESCipher.swift create mode 100644 Sources/sdlan/ArpServer.swift create mode 100644 Sources/sdlan/HolerManager.swift create mode 100644 Sources/sdlan/NoticeMessage.swift create mode 100644 Sources/sdlan/RSACipher.swift create mode 100644 Sources/sdlan/SDLConfiguration.swift create mode 100644 Sources/sdlan/SDLNatProber.swift create mode 100644 Sources/sdlan/SessionManager.swift diff --git a/Sources/sdlan/AESCipher.swift b/Sources/sdlan/AESCipher.swift new file mode 100644 index 0000000..861899f --- /dev/null +++ b/Sources/sdlan/AESCipher.swift @@ -0,0 +1,25 @@ +// +// AESCipher.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation + +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) + } +} diff --git a/Sources/sdlan/ArpServer.swift b/Sources/sdlan/ArpServer.swift new file mode 100644 index 0000000..1963f97 --- /dev/null +++ b/Sources/sdlan/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/Sources/sdlan/HolerManager.swift b/Sources/sdlan/HolerManager.swift new file mode 100644 index 0000000..e37ab86 --- /dev/null +++ b/Sources/sdlan/HolerManager.swift @@ -0,0 +1,31 @@ +// +// HolerManager.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation + +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() + } + +} + + diff --git a/Sources/sdlan/NoticeMessage.swift b/Sources/sdlan/NoticeMessage.swift new file mode 100644 index 0000000..11b892d --- /dev/null +++ b/Sources/sdlan/NoticeMessage.swift @@ -0,0 +1,58 @@ +// +// NoticeMessage.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// + + +// +// NoticeMessage.swift +// sdlan +// +// Created by 安礼成 on 2024/6/3. +// + +import Foundation + +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) + } + +} diff --git a/Sources/sdlan/RSACipher.swift b/Sources/sdlan/RSACipher.swift new file mode 100644 index 0000000..98a738d --- /dev/null +++ b/Sources/sdlan/RSACipher.swift @@ -0,0 +1,42 @@ +// +// RSACipher.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation + +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) + } + } + +} diff --git a/Sources/sdlan/SDLConfiguration.swift b/Sources/sdlan/SDLConfiguration.swift new file mode 100644 index 0000000..7595ac3 --- /dev/null +++ b/Sources/sdlan/SDLConfiguration.swift @@ -0,0 +1,57 @@ +// +// SDLConfiguration.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation +import NIOCore + +// 配置项目 +final class SDLConfiguration { + + 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 + } + +} diff --git a/Sources/sdlan/SDLContext.swift b/Sources/sdlan/SDLContext.swift index 7871c71..1936568 100644 --- a/Sources/sdlan/SDLContext.swift +++ b/Sources/sdlan/SDLContext.swift @@ -27,56 +27,7 @@ class SDLContext { } } - // 配置项目 - 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 + let config: SDLConfiguration // tun网络地址信息 var devAddr: SDLDevAddr @@ -124,8 +75,7 @@ class SDLContext { private var flowTracer = SDLFlowTracerActor() private var flowTracerCancel: AnyCancellable? - init(provider: NEPacketTunnelProvider, config: Configuration) throws { - + init(provider: NEPacketTunnelProvider, config: SDLConfiguration) throws { self.config = config self.rsaCipher = try RSACipher(keySize: 1024) @@ -194,7 +144,6 @@ class SDLContext { if upgradeType == .force { let forceUpgrade = NoticeMessage.UpgradeMessage(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress) self.noticeClient.send(data: forceUpgrade.binaryData) - exit(-1) } @@ -229,8 +178,8 @@ class SDLContext { case .closed: SDLLogger.log("[SDLContext] super client closed", level: .debug) await self.arpServer.clear() - DispatchQueue.global().asyncAfter(deadline: .now() + 5) { - Task { + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + Task {@MainActor in try await self.startSuperClient() } } @@ -291,7 +240,7 @@ class SDLContext { switch event { case .ready: // 获取当前网络的类型 - self.natType = await self.getNatType() + self.natType = await SDLNatProber.getNatType(udpHole: self.udpHole, config: self.config) SDLLogger.log("[SDLContext] nat type is: \(self.natType)", level: .debug) let timer = Timer.publish(every: 5.0, on: .main, in: .common).autoconnect() @@ -300,7 +249,7 @@ class SDLContext { } case .closed: - DispatchQueue.global().asyncAfter(deadline: .now() + 5) { + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { Task { try await self.startUDPHole() } @@ -515,184 +464,6 @@ class SDLContext { } } - 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 { @@ -716,72 +487,14 @@ extension SDLContext { } } + deinit { + self.stunCancel?.cancel() + self.udpHole = nil + self.superClient = nil + } + } -//--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 { diff --git a/Sources/sdlan/SDLNatProber.swift b/Sources/sdlan/SDLNatProber.swift new file mode 100644 index 0000000..37675e2 --- /dev/null +++ b/Sources/sdlan/SDLNatProber.swift @@ -0,0 +1,75 @@ +// +// File.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// + +import Foundation +import NIOCore + +// 网络类型探测器 +struct SDLNatProber { + // 定义nat类型 + enum NatType: UInt8, Encodable { + case blocked = 0 + case noNat = 1 + case fullCone = 2 + case portRestricted = 3 + case coneRestricted = 4 + case symmetric = 5 + } + + // 获取当前所处的网络的nat类型 + static func getNatType(udpHole: SDLUDPHole?, config: SDLConfiguration) async -> NatType { + guard let udpHole else { + return .blocked + } + + let addressArray = config.stunProbeSocketAddressArray + // step1: ip1:port1 <---- ip1:port1 + guard let natAddress1 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .none) else { + return .blocked + } + + // 网络没有在nat下 + if 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地址 + 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(udpHole, 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(udpHole, remoteAddress: addressArray[0][0], attr: .port) { + NSLog("nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address4: \(natAddress4)") + return .coneRestricted + } else { + return .portRestricted + } + } + + private static func getNatAddress(_ udpHole: SDLUDPHole, remoteAddress: SocketAddress, attr: SDLProbeAttr) async -> SocketAddress? { + let stunProbeReply = await udpHole.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: 5) + + return stunProbeReply?.socketAddress() + } + +} diff --git a/Sources/sdlan/SessionManager.swift b/Sources/sdlan/SessionManager.swift new file mode 100644 index 0000000..8fe1505 --- /dev/null +++ b/Sources/sdlan/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) + } + +}