解决dns的回路问题
This commit is contained in:
parent
c8b2218841
commit
a697770187
@ -60,8 +60,29 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
||||
// 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:
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -134,8 +134,13 @@ actor SDLContextActor {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateSDLConfiguration(config: SDLConfiguration) async throws {
|
||||
self.config = config
|
||||
// 取消出口节点的时候,ip地址为: 0.0.0.0
|
||||
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)
|
||||
}
|
||||
|
||||
@ -225,9 +230,8 @@ actor SDLContextActor {
|
||||
self.dnsWorker = nil
|
||||
|
||||
// 启动dns服务
|
||||
let dnsSocketAddress = try SocketAddress.makeAddressResolvingHost(self.config.serverHost, port: 15353)
|
||||
let dnsClient = try await SDLDNSClient(dnsServerAddress: dnsSocketAddress, logger: SDLLogger.shared)
|
||||
try dnsClient.start()
|
||||
let dnsClient = SDLDNSClient(host: self.config.serverHost, port: 15353, logger: SDLLogger.shared)
|
||||
dnsClient.start()
|
||||
SDLLogger.shared.log("[SDLContext] dnsClient started")
|
||||
self.dnsClient = dnsClient
|
||||
self.dnsWorker = Task.detached {
|
||||
@ -662,7 +666,7 @@ actor SDLContextActor {
|
||||
let networkAddr = self.config.networkAddress
|
||||
|
||||
if SDLDNSClient.Helper.isDnsRequestPacket(ipPacket: packet) {
|
||||
self.dnsClient?.forward(ipPacket: packet)
|
||||
self.dnsClient?.forward(ipPacketData: packet.data)
|
||||
return
|
||||
}
|
||||
|
||||
@ -768,7 +772,7 @@ actor SDLContextActor {
|
||||
]
|
||||
|
||||
// 如果存在出口节点配置,则接管系统默认留有
|
||||
if let exitNode = config.exitNode {
|
||||
if config.exitNode != nil {
|
||||
routes.append(.default())
|
||||
}
|
||||
|
||||
@ -779,15 +783,22 @@ actor SDLContextActor {
|
||||
// 设置网卡的DNS解析
|
||||
let networkDomain = networkAddress.networkDomain
|
||||
let dnsSettings = NEDNSSettings(servers: [dnsServer])
|
||||
|
||||
dnsSettings.searchDomains = [networkDomain]
|
||||
dnsSettings.matchDomains = [networkDomain]
|
||||
// 必须设置为 false,否则它会尝试接管全局解析
|
||||
dnsSettings.matchDomainsNoSearch = false
|
||||
|
||||
networkSettings.dnsSettings = dnsSettings
|
||||
|
||||
let ipv4Settings = NEIPv4Settings(addresses: [networkAddress.ipAddress], subnetMasks: [networkAddress.maskAddress])
|
||||
// 设置路由表
|
||||
//NEIPv4Route.default()
|
||||
ipv4Settings.includedRoutes = routes
|
||||
// TODO 要排除的路由表
|
||||
ipv4Settings.excludedRoutes = [
|
||||
|
||||
]
|
||||
|
||||
networkSettings.ipv4Settings = ipv4Settings
|
||||
// 网卡配置设置必须成功
|
||||
try await self.provider.setTunnelNetworkSettings(networkSettings)
|
||||
|
||||
@ -239,6 +239,7 @@ final class SDLQUICClient {
|
||||
case .event:
|
||||
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
|
||||
let event = try? SDLEvent(serializedBytes: bytes) else {
|
||||
SDLLogger.shared.log("SDLQUICClient decode Event Error")
|
||||
return nil
|
||||
}
|
||||
return .event(event)
|
||||
|
||||
@ -74,7 +74,7 @@ public class SDLConfiguration {
|
||||
let accessToken: String
|
||||
let identityId: UInt32
|
||||
|
||||
let exitNode: ExitNode?
|
||||
var exitNode: ExitNode?
|
||||
|
||||
public init(version: Int,
|
||||
serverHost: String,
|
||||
|
||||
@ -1,84 +1,106 @@
|
||||
//
|
||||
// DNSClient.swift
|
||||
// Tun
|
||||
// SDLDNSClient 2.swift
|
||||
// punchnet
|
||||
//
|
||||
// Created by 安礼成 on 2025/12/10.
|
||||
// Created by 安礼成 on 2026/4/9.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
import Network
|
||||
|
||||
// 处理和sn-server服务器之间的通讯
|
||||
final class SDLDNSClient: ChannelInboundHandler {
|
||||
typealias InboundIn = AddressedEnvelope<ByteBuffer>
|
||||
|
||||
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||
|
||||
private var channel: Channel?
|
||||
final class SDLDNSClient {
|
||||
private var connection: NWConnection?
|
||||
private let logger: SDLLogger
|
||||
private let dnsServerAddress: SocketAddress
|
||||
private let dnsServerAddress: NWEndpoint
|
||||
|
||||
// 用于对外输出收到的 DNS 响应包
|
||||
public let packetFlow: AsyncStream<Data>
|
||||
private let packetContinuation: AsyncStream<Data>.Continuation
|
||||
|
||||
// 启动函数
|
||||
init(dnsServerAddress: SocketAddress, logger: SDLLogger) async throws {
|
||||
self.dnsServerAddress = dnsServerAddress
|
||||
// 用来处理关闭事件
|
||||
private let (closeStream, closeContinuation) = AsyncStream.makeStream(of: Void.self)
|
||||
|
||||
/// - Parameter host: 你的 sn-server 地址 (如 "8.8.8.8")
|
||||
/// - Parameter port: 端口 (如 53)
|
||||
init(host: String, port: UInt16, logger: SDLLogger) {
|
||||
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 {
|
||||
let bootstrap = DatagramBootstrap(group: group)
|
||||
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
|
||||
.channelInitializer { channel in
|
||||
channel.pipeline.addHandler(self)
|
||||
func start() {
|
||||
// 1. 配置参数:这是解决环路的关键
|
||||
let parameters = NWParameters.udp
|
||||
|
||||
// 禁止此连接走 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
|
||||
}
|
||||
}
|
||||
|
||||
let channel = try bootstrap.bind(host: "0.0.0.0", port: 0).wait()
|
||||
self.logger.log("[DNSClient] started", level: .debug)
|
||||
self.channel = channel
|
||||
// 启动连接队列
|
||||
connection.start(queue: .global())
|
||||
}
|
||||
|
||||
func waitClose() async throws {
|
||||
try await self.channel?.closeFuture.get()
|
||||
public func waitClose() async {
|
||||
for await _ in closeStream { }
|
||||
}
|
||||
|
||||
// --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))
|
||||
/// 接收数据的递归循环
|
||||
private func receiveLoop() {
|
||||
connection?.receiveMessage { [weak self] content, _, isComplete, error in
|
||||
if let data = content, !data.isEmpty {
|
||||
// 将收到的 DNS 响应写回 AsyncStream
|
||||
self?.packetContinuation.yield(data)
|
||||
}
|
||||
|
||||
if error == nil && self?.connection?.state == .ready {
|
||||
self?.receiveLoop() // 继续监听下一个包
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func channelInactive(context: ChannelHandlerContext) {
|
||||
self.packetContinuation.finish()
|
||||
}
|
||||
|
||||
func forward(ipPacket: IPPacket) {
|
||||
guard let channel = self.channel else {
|
||||
/// 发送 DNS 查询包(由 TUN 拦截到的原始 IP 包数据)
|
||||
func forward(ipPacketData: Data) {
|
||||
guard let connection = self.connection, connection.state == .ready else {
|
||||
return
|
||||
}
|
||||
|
||||
let buffer = channel.allocator.buffer(bytes: ipPacket.data)
|
||||
let envelope = AddressedEnvelope<ByteBuffer>(remoteAddress: self.dnsServerAddress, data: buffer)
|
||||
channel.pipeline.eventLoop.execute {
|
||||
channel.writeAndFlush(envelope, promise: nil)
|
||||
}
|
||||
connection.send(content: ipPacketData, completion: .contentProcessed { [weak self] error in
|
||||
if let error = error {
|
||||
self?.logger.log("[DNSClient] Send error: \(error)", level: .error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func stop() {
|
||||
connection?.cancel()
|
||||
connection = nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
try? self.group.syncShutdownGracefully()
|
||||
self.packetContinuation.finish()
|
||||
stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -58,6 +58,20 @@ struct NEMessage: Sendable {
|
||||
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.
|
||||
|
||||
extension NEMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
@ -139,3 +153,41 @@ extension NEMessage.ExitNodeIpChanged: SwiftProtobuf.Message, SwiftProtobuf._Mes
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ extension SDLAPIClient {
|
||||
let nodeName: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case nnid
|
||||
case nnid = "node_id"
|
||||
case nodeName = "node_name"
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,6 +103,8 @@ struct NetworkView: View {
|
||||
struct NetworkStatusBar: View {
|
||||
@Environment(AppContext.self) private var appContext
|
||||
@State private var vpnManger = VPNManager.shared
|
||||
|
||||
@State private var exitNodeIp: String = ""
|
||||
|
||||
var body: some View {
|
||||
let isOnBinding = Binding(
|
||||
@ -154,6 +156,20 @@ struct NetworkStatusBar: View {
|
||||
Toggle("", isOn: isOnBinding)
|
||||
.toggleStyle(.switch)
|
||||
.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)
|
||||
}
|
||||
@ -239,6 +255,7 @@ struct NetworkDisconnectedView: View {
|
||||
|
||||
do {
|
||||
try await self.appContext.connectNetwork()
|
||||
try await self.appContext.startTun()
|
||||
} catch let err as SDLAPIError {
|
||||
self.showAlert = true
|
||||
self.errorMessage = err.message
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user