punchnet-macos/Tun/Punchnet/DNS/DNSCloudClient.swift
2026-05-05 11:35:45 +08:00

160 lines
5.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// SDLDNSClient 2.swift
// punchnet
//
// Created by on 2026/4/9.
//
import Foundation
import Network
final class DNSCloudClient {
enum DNSCloudError: Error {
case failed(Error)
case cancelled
case sendFailed(Error)
}
private enum State {
case idle
case running
case stopped
}
private var state: State = .idle
private var connection: NWConnection?
private var receiveTask: Task<Void, Never>?
private let dnsServerAddress: NWEndpoint
// DNS
public let packetFlow: AsyncThrowingStream<Data, Error>
private let packetContinuation: AsyncThrowingStream<Data, Error>.Continuation
private var isPacketContinuationFinished: Bool = false
/// - Parameter host: sn-server ( "8.8.8.8")
/// - Parameter port: ( 53)
init(host: String, port: UInt16) {
self.dnsServerAddress = .hostPort(host: NWEndpoint.Host(host), port: NWEndpoint.Port(integerLiteral: port))
let packetPair = AsyncThrowingStream.makeStream(of: Data.self)
self.packetFlow = packetPair.stream
self.packetContinuation = packetPair.continuation
}
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)
connection.stateUpdateHandler = { [weak self] state in
self?.handleConnectionStateUpdate(state, for: connection)
}
//
connection.start(queue: .global())
self.connection = connection
}
/// DNS TUN IP
func forward(ipPacketData: Data) {
guard let connection = self.connection, connection.state == .ready else {
return
}
connection.send(content: ipPacketData, completion: .contentProcessed { error in
if let error = error {
self.finishPacketContinuationIfNeed(throwing: .sendFailed(error))
}
})
}
func stop() {
guard self.state != .stopped else {
return
}
self.state = .stopped
self.receiveTask?.cancel()
self.receiveTask = nil
self.connection?.cancel()
self.connection = nil
self.finishPacketContinuationIfNeed(throwing: nil)
}
private func handleConnectionStateUpdate(_ state: NWConnection.State, for connection: NWConnection) {
switch state {
case .ready:
SDLLogger.log("[DNSClient] Connection ready", for: .debug)
self.startReceiveTask(for: connection)
self.state = .running
case .failed(let error):
self.finishPacketContinuationIfNeed(throwing: .failed(error))
case .cancelled:
self.finishPacketContinuationIfNeed(throwing: .cancelled)
default:
break
}
}
private func startReceiveTask(for connection: NWConnection) {
guard self.receiveTask == nil else {
return
}
let stream = Self.makeReceiveStream(for: connection)
self.receiveTask = Task { [weak self] in
for await data in stream {
if Task.isCancelled {
break
}
self?.packetContinuation.yield(data)
}
}
}
private func finishPacketContinuationIfNeed(throwing error: DNSCloudError?) {
guard !self.isPacketContinuationFinished else {
return
}
self.isPacketContinuationFinished = true
if let error {
self.packetContinuation.finish(throwing: error)
} else {
self.packetContinuation.finish()
}
}
///
private static func makeReceiveStream(for connection: NWConnection) -> AsyncStream<Data> {
return AsyncStream(bufferingPolicy: .bufferingNewest(256)) { continuation in
func receiveNext() {
connection.receiveMessage { content, _, _, error in
if let data = content, !data.isEmpty {
// DNS AsyncStream
continuation.yield(data)
}
if error == nil && connection.state == .ready {
receiveNext() //
} else {
continuation.finish()
}
}
}
receiveNext()
}
}
}