解决dns的回路问题

This commit is contained in:
anlicheng 2026-04-09 17:31:32 +08:00
parent c8b2218841
commit a697770187
8 changed files with 189 additions and 65 deletions

View File

@ -60,8 +60,29 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
// Add code here to handle the message. // Add code here to handle the message.
if let handler = completionHandler {
handler(messageData) Task {
if let message = try? NEMessage(serializedBytes: messageData) {
switch message.message {
case .exitNodeIpChanged(let exitNodeIpChanged):
let exitNodeIp = exitNodeIpChanged.ip
do {
try await self.contextActor?.updateExitNode(exitNodeIp: exitNodeIp)
var reply = NEReply()
reply.code = 0
reply.message = "操作成功"
completionHandler?(try reply.serializedData())
} catch let err {
var reply = NEReply()
reply.code = 1
reply.message = err.localizedDescription
completionHandler?(try reply.serializedData())
}
case .none:
()
}
}
} }
} }

View File

@ -134,8 +134,13 @@ actor SDLContextActor {
} }
} }
public func updateSDLConfiguration(config: SDLConfiguration) async throws { // ip: 0.0.0.0
self.config = config public func updateExitNode(exitNodeIp: String) async throws {
if let ip = SDLUtil.ipv4StrToInt32(exitNodeIp), ip > 0 {
self.config.exitNode = .init(exitNodeIp: ip)
} else {
self.config.exitNode = nil
}
try await self.setNetworkSettings(config: config, dnsServer: SDLDNSClient.Helper.dnsServer) try await self.setNetworkSettings(config: config, dnsServer: SDLDNSClient.Helper.dnsServer)
} }
@ -225,9 +230,8 @@ actor SDLContextActor {
self.dnsWorker = nil self.dnsWorker = nil
// dns // dns
let dnsSocketAddress = try SocketAddress.makeAddressResolvingHost(self.config.serverHost, port: 15353) let dnsClient = SDLDNSClient(host: self.config.serverHost, port: 15353, logger: SDLLogger.shared)
let dnsClient = try await SDLDNSClient(dnsServerAddress: dnsSocketAddress, logger: SDLLogger.shared) dnsClient.start()
try dnsClient.start()
SDLLogger.shared.log("[SDLContext] dnsClient started") SDLLogger.shared.log("[SDLContext] dnsClient started")
self.dnsClient = dnsClient self.dnsClient = dnsClient
self.dnsWorker = Task.detached { self.dnsWorker = Task.detached {
@ -662,7 +666,7 @@ actor SDLContextActor {
let networkAddr = self.config.networkAddress let networkAddr = self.config.networkAddress
if SDLDNSClient.Helper.isDnsRequestPacket(ipPacket: packet) { if SDLDNSClient.Helper.isDnsRequestPacket(ipPacket: packet) {
self.dnsClient?.forward(ipPacket: packet) self.dnsClient?.forward(ipPacketData: packet.data)
return return
} }
@ -768,7 +772,7 @@ actor SDLContextActor {
] ]
// //
if let exitNode = config.exitNode { if config.exitNode != nil {
routes.append(.default()) routes.append(.default())
} }
@ -779,15 +783,22 @@ actor SDLContextActor {
// DNS // DNS
let networkDomain = networkAddress.networkDomain let networkDomain = networkAddress.networkDomain
let dnsSettings = NEDNSSettings(servers: [dnsServer]) let dnsSettings = NEDNSSettings(servers: [dnsServer])
dnsSettings.searchDomains = [networkDomain] dnsSettings.searchDomains = [networkDomain]
dnsSettings.matchDomains = [networkDomain] dnsSettings.matchDomains = [networkDomain]
// false
dnsSettings.matchDomainsNoSearch = false dnsSettings.matchDomainsNoSearch = false
networkSettings.dnsSettings = dnsSettings networkSettings.dnsSettings = dnsSettings
let ipv4Settings = NEIPv4Settings(addresses: [networkAddress.ipAddress], subnetMasks: [networkAddress.maskAddress]) let ipv4Settings = NEIPv4Settings(addresses: [networkAddress.ipAddress], subnetMasks: [networkAddress.maskAddress])
// //
//NEIPv4Route.default()
ipv4Settings.includedRoutes = routes ipv4Settings.includedRoutes = routes
// TODO
ipv4Settings.excludedRoutes = [
]
networkSettings.ipv4Settings = ipv4Settings networkSettings.ipv4Settings = ipv4Settings
// //
try await self.provider.setTunnelNetworkSettings(networkSettings) try await self.provider.setTunnelNetworkSettings(networkSettings)

View File

@ -239,6 +239,7 @@ final class SDLQUICClient {
case .event: case .event:
guard let bytes = buffer.readBytes(length: buffer.readableBytes), guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let event = try? SDLEvent(serializedBytes: bytes) else { let event = try? SDLEvent(serializedBytes: bytes) else {
SDLLogger.shared.log("SDLQUICClient decode Event Error")
return nil return nil
} }
return .event(event) return .event(event)

View File

@ -74,7 +74,7 @@ public class SDLConfiguration {
let accessToken: String let accessToken: String
let identityId: UInt32 let identityId: UInt32
let exitNode: ExitNode? var exitNode: ExitNode?
public init(version: Int, public init(version: Int,
serverHost: String, serverHost: String,

View File

@ -1,84 +1,106 @@
// //
// DNSClient.swift // SDLDNSClient 2.swift
// Tun // punchnet
// //
// Created by on 2025/12/10. // Created by on 2026/4/9.
// //
import Foundation import Foundation
import NIOCore import Network
import NIOPosix
// sn-server final class SDLDNSClient {
final class SDLDNSClient: ChannelInboundHandler { private var connection: NWConnection?
typealias InboundIn = AddressedEnvelope<ByteBuffer>
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
private var channel: Channel?
private let logger: SDLLogger private let logger: SDLLogger
private let dnsServerAddress: SocketAddress private let dnsServerAddress: NWEndpoint
// DNS
public let packetFlow: AsyncStream<Data> public let packetFlow: AsyncStream<Data>
private let packetContinuation: AsyncStream<Data>.Continuation private let packetContinuation: AsyncStream<Data>.Continuation
// //
init(dnsServerAddress: SocketAddress, logger: SDLLogger) async throws { private let (closeStream, closeContinuation) = AsyncStream.makeStream(of: Void.self)
self.dnsServerAddress = dnsServerAddress
/// - Parameter host: sn-server ( "8.8.8.8")
/// - Parameter port: ( 53)
init(host: String, port: UInt16, logger: SDLLogger) {
self.logger = logger self.logger = logger
(self.packetFlow, self.packetContinuation) = AsyncStream.makeStream(of: Data.self, bufferingPolicy: .unbounded) self.dnsServerAddress = .hostPort(host: NWEndpoint.Host(host), port: NWEndpoint.Port(integerLiteral: port))
let (stream, continuation) = AsyncStream.makeStream(of: Data.self, bufferingPolicy: .unbounded)
self.packetFlow = stream
self.packetContinuation = continuation
} }
func start() throws { func start() {
let bootstrap = DatagramBootstrap(group: group) // 1.
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) let parameters = NWParameters.udp
.channelInitializer { channel in
channel.pipeline.addHandler(self) // TUN NE TUN .other
parameters.prohibitedInterfaceTypes = [.other]
// 2. pathSelectionOptions
parameters.multipathServiceType = .handover
// 2.
let connection = NWConnection(to: self.dnsServerAddress, using: parameters)
self.connection = connection
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
self?.logger.log("[DNSClient] Connection ready", level: .debug)
self?.receiveLoop() //
case .failed(let error):
self?.logger.log("[DNSClient] Connection failed: \(error)", level: .error)
self?.stop()
case .cancelled:
self?.packetContinuation.finish()
self?.closeContinuation.finish()
default:
break
}
}
//
connection.start(queue: .global())
}
public func waitClose() async {
for await _ in closeStream { }
}
///
private func receiveLoop() {
connection?.receiveMessage { [weak self] content, _, isComplete, error in
if let data = content, !data.isEmpty {
// DNS AsyncStream
self?.packetContinuation.yield(data)
} }
let channel = try bootstrap.bind(host: "0.0.0.0", port: 0).wait() if error == nil && self?.connection?.state == .ready {
self.logger.log("[DNSClient] started", level: .debug) self?.receiveLoop() //
self.channel = channel }
}
func waitClose() async throws {
try await self.channel?.closeFuture.get()
}
// --MARK: ChannelInboundHandler delegate
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let envelope = unwrapInboundIn(data)
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))
} }
} }
func channelInactive(context: ChannelHandlerContext) { /// DNS TUN IP
self.packetContinuation.finish() func forward(ipPacketData: Data) {
} guard let connection = self.connection, connection.state == .ready else {
func forward(ipPacket: IPPacket) {
guard let channel = self.channel else {
return return
} }
let buffer = channel.allocator.buffer(bytes: ipPacket.data) connection.send(content: ipPacketData, completion: .contentProcessed { [weak self] error in
let envelope = AddressedEnvelope<ByteBuffer>(remoteAddress: self.dnsServerAddress, data: buffer) if let error = error {
channel.pipeline.eventLoop.execute { self?.logger.log("[DNSClient] Send error: \(error)", level: .error)
channel.writeAndFlush(envelope, promise: nil) }
} })
}
func stop() {
connection?.cancel()
connection = nil
} }
deinit { deinit {
try? self.group.syncShutdownGracefully() stop()
self.packetContinuation.finish()
} }
} }

View File

@ -58,6 +58,20 @@ struct NEMessage: Sendable {
init() {} init() {}
} }
struct NEReply: 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.
var code: Int32 = 0
var message: String = String()
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
// MARK: - Code below here is support for the SwiftProtobuf runtime. // MARK: - Code below here is support for the SwiftProtobuf runtime.
extension NEMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { extension NEMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
@ -139,3 +153,41 @@ extension NEMessage.ExitNodeIpChanged: SwiftProtobuf.Message, SwiftProtobuf._Mes
return true return true
} }
} }
extension NEReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "NEReply"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "code"),
2: .same(proto: "message"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularInt32Field(value: &self.code) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.message) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.code != 0 {
try visitor.visitSingularInt32Field(value: self.code, fieldNumber: 1)
}
if !self.message.isEmpty {
try visitor.visitSingularStringField(value: self.message, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: NEReply, rhs: NEReply) -> Bool {
if lhs.code != rhs.code {return false}
if lhs.message != rhs.message {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View File

@ -16,7 +16,7 @@ extension SDLAPIClient {
let nodeName: String let nodeName: String
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case nnid case nnid = "node_id"
case nodeName = "node_name" case nodeName = "node_name"
} }
} }

View File

@ -104,6 +104,8 @@ struct NetworkStatusBar: View {
@Environment(AppContext.self) private var appContext @Environment(AppContext.self) private var appContext
@State private var vpnManger = VPNManager.shared @State private var vpnManger = VPNManager.shared
@State private var exitNodeIp: String = ""
var body: some View { var body: some View {
let isOnBinding = Binding( let isOnBinding = Binding(
get: { vpnManger.isConnected }, get: { vpnManger.isConnected },
@ -154,6 +156,20 @@ struct NetworkStatusBar: View {
Toggle("", isOn: isOnBinding) Toggle("", isOn: isOnBinding)
.toggleStyle(.switch) .toggleStyle(.switch)
.controlSize(.small) // macOS 使 small .controlSize(.small) // macOS 使 small
TextField("出口节点:", text: $exitNodeIp)
Button {
Task {
let result = try await self.appContext.changeExitNodeIp(exitNodeIp: self.exitNodeIp)
let reply = try NEReply(serializedBytes: result)
NSLog("change exit node ip: \(reply)")
}
} label: {
Text("启动出口节点")
}
} }
.padding(.vertical, 5) .padding(.vertical, 5)
} }
@ -239,6 +255,7 @@ struct NetworkDisconnectedView: View {
do { do {
try await self.appContext.connectNetwork() try await self.appContext.connectNetwork()
try await self.appContext.startTun()
} catch let err as SDLAPIError { } catch let err as SDLAPIError {
self.showAlert = true self.showAlert = true
self.errorMessage = err.message self.errorMessage = err.message