punchnet-macos/Tun/Punchnet/Actors/SDLQuicClient.swift
2026-04-28 20:44:22 +08:00

317 lines
11 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.

//
// SDLQuicClient.swift
// Tun
//
// Created by on 2026/2/13.
//
import Foundation
import NIOCore
import Network
import CryptoKit
import Security
// 便
enum SDLQUICError: Error {
case connectionFailed(Error)
case connectionCancelled
case timeout
case decodeError(String)
case packetTooLarge
}
enum SDLQUICEvent: Error {
case failed(Error)
case cancelled
case writeFailed(Error)
}
final class SDLQUICClient {
private let allocator = ByteBufferAllocator()
// 2M
private let maxBufferSize: Int
private static let maxPacketSize: Int = 64 * 1024
private let readyLatch = AsyncOneShot<Void>()
//
public var eventStream: AsyncStream<SDLQUICEvent>
private let eventCont: AsyncStream<SDLQUICEvent>.Continuation
private let connection: NWConnection
private let queue = DispatchQueue(label: "com.sdl.QUICClient.queue") // 线
init(host: String, port: UInt16, maxBufferSize: Int = 2 * 1024 * 1024) {
let options = NWProtocolQUIC.Options(alpn: ["punchnet/1.0"])
self.maxBufferSize = maxBufferSize
(self.eventStream, self.eventCont) = AsyncStream.makeStream(of: SDLQUICEvent.self)
//
sec_protocol_options_set_verify_block(
options.securityProtocolOptions,
{ metadata, trust, complete in
//
complete(QUICVerifier.verify(trust: trust, host: host))
},
self.queue
)
let params = NWParameters(quic: options)
self.connection = NWConnection(host: .init(host), port: .init(rawValue: port)!, using: params)
}
func start() {
connection.stateUpdateHandler = { [weak self] state in
Task {
await self?.handleConnectionStateUpdate(state)
}
}
connection.start(queue: self.queue)
}
private func handleConnectionStateUpdate(_ state: NWConnection.State) async {
SDLLogger.log("[SDLQUICClient] new state: \(state)", for: .debug)
switch state {
case .ready:
await self.readyLatch.succeed(())
case .failed(let error):
await self.readyLatch.fail(error)
self.eventCont.yield(.failed(error))
case .cancelled:
await self.readyLatch.fail(SDLQUICError.connectionCancelled)
self.eventCont.yield(.cancelled)
default:
()
}
}
func waitReady(timeout: Duration = .seconds(5)) async throws {
try await self.readyLatch.wait(timeout: timeout, timeoutError: SDLQUICError.timeout)
}
func send(type: SDLPacketType, data: Data) {
var len = UInt16(data.count + 1).bigEndian
var packet = Data(Data(bytes: &len, count: 2))
packet.append(type.rawValue)
packet.append(data)
connection.send(content: packet, completion: .contentProcessed { [weak self] error in
if let error {
SDLLogger.log("[SDLQUICClient] send data get error: \(error)", for: .debug)
self?.eventCont.yield(.writeFailed(error))
}
})
}
func stop() {
self.connection.cancel()
Task {
await self.readyLatch.fail(SDLQUICError.connectionCancelled)
}
self.eventCont.finish()
}
}
// --MARK: Reader
extension SDLQUICClient {
func messageStream() -> AsyncThrowingStream<SDLQUICInboundMessage, Error> {
return AsyncThrowingStream { continuation in
var buffer = allocator.buffer(capacity: self.maxBufferSize)
let threshold = self.maxBufferSize / 10 * 6
func readOnce() {
self.connection.receive(minimumIncompleteLength: 1, maximumLength: Self.maxPacketSize) { data, _, isComplete, error in
if let error {
continuation.finish(throwing: error)
return
}
do {
if let data, !data.isEmpty {
buffer.writeBytes(data)
let frames = try Self.parseFrames(buffer: &buffer)
if buffer.readerIndex > threshold {
buffer.discardReadBytes()
}
for frame in frames {
if let message = Self.decode(frame: frame) {
continuation.yield(message)
}
}
}
} catch let err {
continuation.finish(throwing: err)
return
}
if isComplete {
continuation.finish()
} else {
readOnce()
}
}
}
readOnce()
}
}
//
private static func parseFrames(buffer: inout ByteBuffer) throws -> [ByteBuffer] {
guard buffer.readableBytes >= 2 else {
return []
}
var frames: [ByteBuffer] = []
while true {
guard let len = buffer.getInteger(at: buffer.readerIndex, endianness: .big, as: UInt16.self) else {
break
}
if len > Self.maxPacketSize {
throw SDLQUICError.packetTooLarge
}
guard buffer.readableBytes >= len + 2 else {
break
}
buffer.moveReaderIndex(forwardBy: 2)
if let buf = buffer.readSlice(length: Int(len)) {
frames.append(buf)
}
}
return frames
}
}
// --MARK:
extension SDLQUICClient {
private static func decode(frame: ByteBuffer) -> SDLQUICInboundMessage? {
var buffer = frame
guard let type = buffer.readInteger(as: UInt8.self),
let packetType = SDLPacketType(rawValue: type) else {
return nil
}
switch packetType {
case .welcome:
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let welcome = try? SDLWelcome(serializedBytes: bytes) else {
return nil
}
return .welcome(welcome)
case .registerSuperAck:
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let registerSuperAck = try? SDLRegisterSuperAck(serializedBytes: bytes) else {
return nil
}
return .registerSuperAck(registerSuperAck)
case .registerSuperNak:
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let registerSuperNak = try? SDLRegisterSuperNak(serializedBytes: bytes) else {
return nil
}
return .registerSuperNak(registerSuperNak)
case .peerInfo:
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let peerInfo = try? SDLPeerInfo(serializedBytes: bytes) else {
return nil
}
return .peerInfo(peerInfo)
case .policyResponse:
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let policyResponse = try? SDLPolicyResponse(serializedBytes: bytes) else {
return nil
}
return .policyReponse(policyResponse)
case .arpResponse:
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let arpResponse = try? SDLArpResponse(serializedBytes: bytes) else {
return nil
}
return .arpResponse(arpResponse)
case .event:
guard let bytes = buffer.readBytes(length: buffer.readableBytes),
let event = try? SDLEvent(serializedBytes: bytes) else {
SDLLogger.log("SDLQUICClient decode Event Error", for: .debug)
return nil
}
return .event(event)
case .pong:
return .pong
default:
SDLLogger.log("SDLQUICClient decode miss type: \(type)", for: .debug)
return nil
}
}
}
// --MARK: quic
extension SDLQUICClient {
enum QUICVerifier {
// Base64
static let pinnedPublicKeyHashes = [
"Q41r6hbMWEVyxo6heNAH4Wx/TH5NNOWlNif9bewcJ3E="
]
static func verify(trust: sec_trust_t, host: String) -> Bool {
let secTrust = sec_trust_copy_ref(trust).takeRetainedValue()
// --- Step 1: ---
var error: CFError?
guard SecTrustEvaluateWithError(secTrust, &error) else {
SDLLogger.log("❌ 系统证书验证失败: \(error?.localizedDescription ?? "未知错误")", for: .debug)
return false
}
// --- Step 2: ---
let policy = SecPolicyCreateSSL(true, host as CFString)
SecTrustSetPolicies(secTrust, policy)
guard SecTrustEvaluateWithError(secTrust, &error) else {
SDLLogger.log("❌ 主机名校验失败: \(error?.localizedDescription ?? "未知错误")", for: .debug)
return false
}
// --- Step 3: ---
guard let chain = SecTrustCopyCertificateChain(secTrust) as? [SecCertificate],
let leafCertificate = chain.first else {
SDLLogger.log("❌ 无法获取证书链或叶子证书", for: .debug)
return false
}
// --- Step 4: ---
guard let publicKey = SecCertificateCopyKey(leafCertificate),
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else {
SDLLogger.log("❌ 无法提取公钥", for: .debug)
return false
}
// --- Step 5: SHA256 ---
let hash = SHA256.hash(data: publicKeyData)
let hashBase64 = Data(hash).base64EncodedString()
if pinnedPublicKeyHashes.contains(hashBase64) {
SDLLogger.log("✅ 公钥校验通过", for: .debug)
return true
} else {
SDLLogger.log("⚠️ 公钥不匹配! 收到: \(hashBase64)", for: .debug)
return false
}
}
}
}