This commit is contained in:
anlicheng 2026-05-28 14:34:11 +08:00
parent 8aeb543b17
commit ce764d2314

View File

@ -14,23 +14,15 @@ actor DNSLocalClient {
let tracker: DNSTracker let tracker: DNSTracker
} }
private enum State {
case idle
case starting
case running
case stopped
}
enum DNSLocalError: Error { enum DNSLocalError: Error {
case failed(Error) case failed(Error)
case cancelled case cancelled
case sendFailed(Error) case sendFailed(Error)
} }
private var state: State = .idle private let queue = DispatchQueue(label: "com.sdl.DNSCloudClient.queue")
private let connection: NWConnection
private let dnsServerEndpoint: NWEndpoint
private var connection: NWConnection?
private let timeoutInterval: TimeInterval = 3.0 private let timeoutInterval: TimeInterval = 3.0
nonisolated let packetFlow: AsyncThrowingStream<Data, Error> nonisolated let packetFlow: AsyncThrowingStream<Data, Error>
@ -40,8 +32,11 @@ actor DNSLocalClient {
private var pendingRequests: [UInt16: PendingRequest] = [:] private var pendingRequests: [UInt16: PendingRequest] = [:]
private var nextTransactionID: UInt16 = 1 private var nextTransactionID: UInt16 = 1
private let readySignal = AsyncOneShot<Void>()
private var isStopped: Bool = false
init(host: String) { init(host: String) {
self.dnsServerEndpoint = .hostPort(host: Self.makeEndpointHost(ip: host), port: 53) let dnsServerEndpoint = NWEndpoint.hostPort(host: Self.makeEndpointHost(ip: host), port: 53)
let (stream, continuation) = AsyncThrowingStream.makeStream(of: Data.self, bufferingPolicy: .bufferingNewest(256)) let (stream, continuation) = AsyncThrowingStream.makeStream(of: Data.self, bufferingPolicy: .bufferingNewest(256))
self.packetFlow = stream self.packetFlow = stream
@ -50,6 +45,13 @@ actor DNSLocalClient {
self.packetContinuation.onTermination = { termination in self.packetContinuation.onTermination = { termination in
SDLLogger.log("[DNSLocalClient] packetFlow terminated: \(termination)") SDLLogger.log("[DNSLocalClient] packetFlow terminated: \(termination)")
} }
let parameters = NWParameters.udp
parameters.prohibitedInterfaceTypes = [.other]
// 2. pathSelectionOptions
parameters.multipathServiceType = .handover
self.connection = NWConnection(to: dnsServerEndpoint, using: parameters)
} }
private static func makeEndpointHost(ip: String) -> NWEndpoint.Host { private static func makeEndpointHost(ip: String) -> NWEndpoint.Host {
@ -65,26 +67,12 @@ actor DNSLocalClient {
} }
func run() async throws { func run() async throws {
guard self.state == .idle else { self.connection.stateUpdateHandler = { [weak self] state in
return
}
self.state = .starting
let parameters = NWParameters.udp
parameters.prohibitedInterfaceTypes = [.other]
// 2. pathSelectionOptions
parameters.multipathServiceType = .handover
let connection = NWConnection(to: self.dnsServerEndpoint, using: parameters)
connection.stateUpdateHandler = { [weak self] state in
Task { Task {
await self?.handleConnectionStateUpdate(state, for: connection) await self?.handleConnectionStateUpdate(state)
} }
} }
self.connection.start(queue: self.queue)
self.connection = connection
connection.start(queue: .global())
try await withTaskCancellationHandler { try await withTaskCancellationHandler {
try await withThrowingTaskGroup(of: Void.self) { group in try await withThrowingTaskGroup(of: Void.self) { group in
@ -92,34 +80,32 @@ actor DNSLocalClient {
group.cancelAll() group.cancelAll()
} }
group.addTask { [weak self] in group.addTask {
let stream = Self.makeReceiveStream(for: connection) try await self.readySignal.wait()
let stream = Self.makeReceiveStream(for: self.connection)
for await data in stream { for await data in stream {
try Task.checkCancellation() try Task.checkCancellation()
guard let self else {
return
}
await self.handleResponse(data: data) await self.handleResponse(data: data)
} }
} }
group.addTask { [weak self] in group.addTask {
while !Task.isCancelled { while !Task.isCancelled {
try await Task.sleep(for: .seconds(3)) try await Task.sleep(for: .seconds(3))
await self?.performCleanup() await self.performCleanup()
} }
} }
try await group.next() try await group.next()
} }
} onCancel: { } onCancel: {
connection.cancel() self.connection.cancel()
} }
} }
func query(tracker: DNSTracker, dnsPayload: Data) { func query(tracker: DNSTracker, dnsPayload: Data) {
guard self.state != .stopped, guard connection.state == .ready, dnsPayload.count >= 2 else {
let connection = self.connection, connection.state == .ready, dnsPayload.count >= 2 else {
return return
} }
@ -142,32 +128,30 @@ actor DNSLocalClient {
} }
func stop() { func stop() {
guard self.state != .stopped else { guard !self.isStopped else {
return return
} }
self.isStopped = true
self.state = .stopped self.connection.cancel()
let connection = self.connection
self.connection = nil
self.pendingRequests.removeAll() self.pendingRequests.removeAll()
self.nextTransactionID = 1 self.nextTransactionID = 1
connection?.cancel()
self.finishPacketContinuationIfNeed(throwing: nil) self.finishPacketContinuationIfNeed(throwing: nil)
SDLLogger.log("[SDLLocalClient] stopped") SDLLogger.log("[SDLLocalClient] stopped")
} }
private func handleConnectionStateUpdate(_ state: NWConnection.State, for conn: NWConnection) { private func handleConnectionStateUpdate(_ state: NWConnection.State) async {
switch state { switch state {
case .ready: case .ready:
self.markConnectionReady(conn) await self.readySignal.succeed(())
case .failed(let error): case .failed(let error):
SDLLogger.log("[DNSLocalClient] failed with error: \(error.localizedDescription)", for: .debug) await self.readySignal.fail(DNSLocalError.failed(error))
self.finishPacketContinuationIfNeed(throwing: .failed(error)) self.finishPacketContinuationIfNeed(throwing: .failed(error))
case .cancelled: case .cancelled:
await self.readySignal.fail(DNSLocalError.cancelled)
self.finishPacketContinuationIfNeed(throwing: .cancelled) self.finishPacketContinuationIfNeed(throwing: .cancelled)
default: default:
() ()
@ -206,22 +190,6 @@ actor DNSLocalClient {
self.packetContinuation.yield(packet) self.packetContinuation.yield(packet)
} }
private func markConnectionReady(_ conn: NWConnection) {
guard self.state != .stopped, self.isCurrentConnection(conn) else {
return
}
self.state = .running
}
private func isCurrentConnection(_ conn: NWConnection) -> Bool {
guard let currentConnection = self.connection else {
return false
}
return currentConnection === conn
}
private func handleSendFailure(transactionID: UInt16, error: NWError) { private func handleSendFailure(transactionID: UInt16, error: NWError) {
self.pendingRequests.removeValue(forKey: transactionID) self.pendingRequests.removeValue(forKey: transactionID)
self.finishPacketContinuationIfNeed(throwing: .sendFailed(error)) self.finishPacketContinuationIfNeed(throwing: .sendFailed(error))
@ -273,15 +241,15 @@ actor DNSLocalClient {
return rewrittenPayload return rewrittenPayload
} }
private static func makeReceiveStream(for conn: NWConnection) -> AsyncStream<Data> { private static func makeReceiveStream(for connection: NWConnection) -> AsyncStream<Data> {
return AsyncStream(bufferingPolicy: .bufferingNewest(256)) { continuation in return AsyncStream(bufferingPolicy: .bufferingNewest(256)) { continuation in
func receiveNext() { func receiveNext() {
conn.receiveMessage { content, _, _, error in connection.receiveMessage { content, _, _, error in
if let data = content, !data.isEmpty { if let data = content, !data.isEmpty {
continuation.yield(data) continuation.yield(data)
} }
if error == nil && conn.state == .ready { if error == nil && connection.state == .ready {
receiveNext() receiveNext()
} else { } else {
continuation.finish() continuation.finish()