解决dns的问题
This commit is contained in:
parent
a1c42d8eef
commit
f22e962bd7
@ -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)
|
||||
|
||||
@ -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..<packet.data.count])
|
||||
SDLLogger.shared.log("[Tun] get local dns request: \(name)")
|
||||
|
||||
let tracker = DNSLocalClient.DNSTracker(transactionID: dnsMessage.transactionID, clientIP: packet.header.source, clientPort: udpPacket.srcPort, createdAt: Date())
|
||||
self.dnsLocalClient?.query(tracker: tracker, dnsPayload: dnsPayload)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dnsClient?.forward(ipPacketData: packet.data)
|
||||
return
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
let dstIp = packet.header.destination
|
||||
@ -812,8 +838,8 @@ actor SDLContextActor {
|
||||
let dnsSettings = NEDNSSettings(servers: [dnsServer])
|
||||
|
||||
dnsSettings.searchDomains = [networkDomain]
|
||||
dnsSettings.matchDomains = [networkDomain]
|
||||
// 必须设置为 false,否则它会尝试接管全局解析
|
||||
dnsSettings.matchDomains = [networkDomain, ""]
|
||||
// 设置为 false 允许系统在补全 Search Domain 时也能匹配到此设置
|
||||
dnsSettings.matchDomainsNoSearch = false
|
||||
|
||||
networkSettings.dnsSettings = dnsSettings
|
||||
@ -822,12 +848,6 @@ actor SDLContextActor {
|
||||
// 设置路由表
|
||||
ipv4Settings.includedRoutes = routes
|
||||
|
||||
let rs = self.getIpv4ExcludeRoutes()
|
||||
|
||||
rs.forEach { x in
|
||||
SDLLogger.shared.log("excludsL: \(x.destinationAddress)")
|
||||
}
|
||||
|
||||
// 配置要排除的路由
|
||||
ipv4Settings.excludedRoutes = self.getIpv4ExcludeRoutes()
|
||||
|
||||
|
||||
@ -2,6 +2,14 @@ import Foundation
|
||||
import Network
|
||||
|
||||
final class DNSLocalClient {
|
||||
// 需要保存DNS请求的追踪信息
|
||||
struct DNSTracker {
|
||||
let transactionID: UInt16
|
||||
let clientIP: UInt32 // 原始包的源 IP (大端序)
|
||||
let clientPort: UInt16 // 原始包的源端口 (大端序)
|
||||
let createdAt: Date // 用于超时清理
|
||||
}
|
||||
|
||||
private var connections: [NWConnection] = []
|
||||
|
||||
// 准备多个公共 DNS
|
||||
@ -9,6 +17,9 @@ final class DNSLocalClient {
|
||||
|
||||
public let packetFlow: AsyncStream<Data>
|
||||
private let packetContinuation: AsyncStream<Data>.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..<wordCount {
|
||||
// 使用 bigEndian: words[i] 是正确的,但要确保不越界
|
||||
sum += UInt32(UInt16(bigEndian: words[i]))
|
||||
}
|
||||
|
||||
// 2. 关键修复:处理奇数长度的最后一个字节
|
||||
if count % 2 != 0 {
|
||||
// 获取最后一个字节,并左移 8 位(作为 16-bit 的高位)
|
||||
let lastByte = ptr[count - 1]
|
||||
sum += UInt32(lastByte) << 8
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 将进位加回低 16 位
|
||||
while (sum >> 16) != 0 {
|
||||
sum = (sum & 0xffff) + (sum >> 16)
|
||||
}
|
||||
|
||||
return UInt16(~sum & 0xffff)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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..<qdCount {
|
||||
if let q = parseQuestion() { message.questions.append(q) }
|
||||
if let q = parseQuestion() {
|
||||
message.questions.append(q)
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..<anCount {
|
||||
if let rr = parseRR() { message.answers.append(rr) }
|
||||
if let rr = parseRR() {
|
||||
message.answers.append(rr)
|
||||
}
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
|
||||
@ -201,17 +201,20 @@ struct UDPPacket {
|
||||
let dstPort: UInt16
|
||||
let length: UInt16
|
||||
let checksum: UInt16
|
||||
|
||||
let payloadOffset: Int
|
||||
|
||||
init?(_ data: Data, offset: Int) {
|
||||
guard data.count >= 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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user