This commit is contained in:
anlicheng 2026-05-28 13:20:18 +08:00
parent ef1c7fb34d
commit 707bb82ea0
2 changed files with 64 additions and 40 deletions

View File

@ -7,29 +7,33 @@
import Foundation import Foundation
import Network import Network
final class DNSCloudClient { actor DNSCloudClient {
enum DNSCloudError: Error { enum DNSCloudError: Error {
case failed(Error) case failed(Error)
case cancelled case cancelled
case sendFailed(Error) case sendFailed(Error)
case invalidData
} }
private enum State { private enum State {
case idle case idle
case starting
case running case running
case stopped case stopped
} }
private var state: State = .idle private var state: State = .idle
private let queue = DispatchQueue(label: "com.sdl.DNSCloudClient.queue")
private var connection: NWConnection? private var connection: NWConnection?
private let dnsServerAddress: NWEndpoint private let dnsServerAddress: NWEndpoint
// DNS // DNS
public let packetFlow: AsyncThrowingStream<Data, Error> nonisolated let packetFlow: AsyncThrowingStream<Data, Error>
private let packetContinuation: AsyncThrowingStream<Data, Error>.Continuation private let packetContinuation: AsyncThrowingStream<Data, Error>.Continuation
private var isPacketContinuationFinished: Bool = false private var isPacketContinuationFinished: Bool = false
private let readySignal = AsyncOneShot<Void>()
/// - Parameter serverIP: sn-server IP ( "8.8.8.8") /// - Parameter serverIP: sn-server IP ( "8.8.8.8")
/// - Parameter port: ( 53) /// - Parameter port: ( 53)
@ -57,6 +61,7 @@ final class DNSCloudClient {
guard self.state == .idle else { guard self.state == .idle else {
return return
} }
self.state = .starting
// 1. // 1.
let parameters = NWParameters.udp let parameters = NWParameters.udp
@ -68,17 +73,20 @@ final class DNSCloudClient {
// 2. // 2.
let connection = NWConnection(to: self.dnsServerAddress, using: parameters) let connection = NWConnection(to: self.dnsServerAddress, using: parameters)
connection.stateUpdateHandler = { [weak self] state in connection.stateUpdateHandler = { [weak self] state in
self?.handleConnectionStateUpdate(state, for: connection) Task {
await self?.handleConnectionStateUpdate(state, for: connection)
}
} }
//
connection.start(queue: .global())
self.connection = connection self.connection = connection
let stream = Self.makeReceiveStream(for: connection) //
connection.start(queue: self.queue)
try await withTaskCancellationHandler { try await withTaskCancellationHandler {
for await data in stream { try await self.readySignal.wait()
while true {
try Task.checkCancellation() try Task.checkCancellation()
let data = try await self.readOnce()
self.packetContinuation.yield(data) self.packetContinuation.yield(data)
} }
} onCancel: { } onCancel: {
@ -88,46 +96,66 @@ final class DNSCloudClient {
/// DNS TUN IP /// DNS TUN IP
func forward(ipPacketData: Data) { func forward(ipPacketData: Data) {
guard let connection = self.connection, connection.state == .ready else { guard self.state == .running,
let connection = self.connection, connection.state == .ready else {
return return
} }
connection.send(content: ipPacketData, completion: .contentProcessed { error in connection.send(content: ipPacketData, completion: .contentProcessed { [weak self] error in
if let error = error { if let error {
self.finishPacketContinuationIfNeed(throwing: .sendFailed(error)) Task {
await self?.finishPacketContinuationIfNeed(throwing: .sendFailed(error))
}
} }
}) })
} }
func stop() { func stop() async {
guard self.state != .stopped else { guard self.state != .stopped else {
return return
} }
self.state = .stopped self.state = .stopped
self.connection?.cancel() let connection = self.connection
self.connection = nil self.connection = nil
connection?.cancel()
await self.readySignal.fail(DNSCloudError.cancelled)
self.finishPacketContinuationIfNeed(throwing: nil) self.finishPacketContinuationIfNeed(throwing: nil)
SDLLogger.log("[SDLCloudClient] stopped") SDLLogger.log("[SDLCloudClient] stopped")
} }
private func handleConnectionStateUpdate(_ state: NWConnection.State, for connection: NWConnection) { private func handleConnectionStateUpdate(_ state: NWConnection.State, for connection: NWConnection) async {
switch state { switch state {
case .ready: case .ready:
SDLLogger.log("[DNSClient] Connection ready", for: .debug) guard self.state != .stopped, self.isCurrentConnection(connection) else {
return
}
self.state = .running self.state = .running
SDLLogger.log("[DNSClient] Connection ready", for: .debug)
await self.readySignal.succeed(())
case .failed(let error): case .failed(let error):
await self.readySignal.fail(DNSCloudError.failed(error))
self.finishPacketContinuationIfNeed(throwing: .failed(error)) self.finishPacketContinuationIfNeed(throwing: .failed(error))
case .cancelled: case .cancelled:
await self.readySignal.fail(DNSCloudError.cancelled)
self.finishPacketContinuationIfNeed(throwing: .cancelled) self.finishPacketContinuationIfNeed(throwing: .cancelled)
default: default:
break break
} }
} }
private func isCurrentConnection(_ connection: NWConnection) -> Bool {
guard let currentConnection = self.connection else {
return false
}
return currentConnection === connection
}
private func finishPacketContinuationIfNeed(throwing error: DNSCloudError?) { private func finishPacketContinuationIfNeed(throwing error: DNSCloudError?) {
guard !self.isPacketContinuationFinished else { guard !self.isPacketContinuationFinished else {
return return
@ -141,26 +169,22 @@ final class DNSCloudClient {
} }
} }
/// private func readOnce() async throws -> Data {
private static func makeReceiveStream(for connection: NWConnection) -> AsyncStream<Data> { guard let connection = self.connection, connection.state == .ready else {
return AsyncStream(bufferingPolicy: .bufferingNewest(256)) { continuation in throw DNSCloudError.cancelled
func receiveNext() { }
return try await withCheckedThrowingContinuation { continuation in
connection.receiveMessage { content, _, _, error in connection.receiveMessage { content, _, _, error in
if let data = content, !data.isEmpty { if let error {
// DNS AsyncStream continuation.resume(throwing: error)
continuation.yield(data) } else if let data = content, !data.isEmpty {
} continuation.resume(returning: data)
if error == nil && connection.state == .ready {
receiveNext() //
} else { } else {
continuation.finish() continuation.resume(throwing: DNSCloudError.invalidData)
} }
} }
} }
receiveNext()
}
} }
deinit { deinit {

View File

@ -23,29 +23,29 @@ actor DNSCloudService {
do { do {
try await self.run(client: client) try await self.run(client: client)
self.clearCurrent(client, generation: generation) self.clearCurrent(client, generation: generation)
client.stop() await client.stop()
} catch is CancellationError { } catch is CancellationError {
self.clearCurrent(client, generation: generation) self.clearCurrent(client, generation: generation)
client.stop() await client.stop()
throw CancellationError() throw CancellationError()
} catch { } catch {
self.clearCurrent(client, generation: generation) self.clearCurrent(client, generation: generation)
client.stop() await client.stop()
throw error throw error
} }
} }
func stop() { func stop() async {
self.generation &+= 1 self.generation &+= 1
let client = self.currentClient let client = self.currentClient
self.currentClient = nil self.currentClient = nil
client?.stop() await client?.stop()
} }
func forward(ipPacketData: Data) { func forward(ipPacketData: Data) async {
self.currentClient?.forward(ipPacketData: ipPacketData) await self.currentClient?.forward(ipPacketData: ipPacketData)
} }
private func run(client: DNSCloudClient) async throws { private func run(client: DNSCloudClient) async throws {