解决语法级别的错误

This commit is contained in:
anlicheng 2026-01-28 01:02:37 +08:00
parent cd4c977b83
commit 6faff2e6cc
5 changed files with 299 additions and 218 deletions

View File

@ -37,7 +37,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
let stunServersStr = options["stun_servers"] as! String
let noticePort = options["notice_port"] as! Int
let token = options["token"] as! String
let networkCode = options["network_code"] as! String
let accessToken = options["access_token"] as! String
let clientId = options["client_id"] as! String
let remoteDnsServer = options["remote_dns_server"] as! String
let hostname = options["hostname"] as! String
@ -61,7 +61,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return
}
NSLog("[PacketTunnelProvider] client_id: \(clientId), token: \(token), network_code: \(networkCode)")
NSLog("[PacketTunnelProvider] client_id: \(clientId), token: \(token)")
let config = SDLConfiguration(version: 1,
installedChannel: installed_channel,
@ -71,7 +71,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
clientId: clientId,
noticePort: noticePort,
token: token,
networkCode: networkCode,
accessToken: accessToken,
remoteDnsServer: remoteDnsServer,
hostname: hostname)
//

View File

@ -0,0 +1,179 @@
//
// SDLNATProberActor.swift
// punchnet
//
// Created by on 2026/1/28.
//
import Foundation
import NIOCore
actor SDLNATProberActor {
// MARK: - NAT Type
enum NatType: UInt8, Encodable {
case blocked = 0
case noNat = 1
case fullCone = 2
case portRestricted = 3
case coneRestricted = 4
case symmetric = 5
}
// MARK: - Internal State
private enum State {
case idle
case waiting(step: Step)
case finished
}
private enum Step: Int {
case step1 = 1
case step2 = 2
case step3 = 3
case step4 = 4
}
private var state: State = .idle
// MARK: - Dependencies
private let udpHole: SDLUDPHoleActor
private let config: SDLConfiguration
private let logger: SDLLogger
// MARK: - Probe Data
private var natAddress1: SocketAddress?
private var natAddress2: SocketAddress?
// MARK: - Completion
private var onFinished: ((NatType) -> Void)?
private var cookieId: UInt32 = 1
// MARK: - Init
init(udpHole: SDLUDPHoleActor, config: SDLConfiguration, logger: SDLLogger) {
self.udpHole = udpHole
self.config = config
self.logger = logger
}
// MARK: - Public API
/// NAT
func start(onFinished: @escaping (NatType) -> Void) async {
guard case .idle = state else {
logger.log("[NAT] probe already started", level: .warning)
return
}
self.onFinished = onFinished
transition(to: .waiting(step: .step1))
await self.sendProbe(step: .step1)
}
/// UDP STUN
func handleProbeReply(from address: SocketAddress, reply: SDLStunProbeReply) async {
guard case .waiting(let currentStep) = state else {
return
}
switch currentStep {
case .step1:
let localAddress = await self.udpHole.getLocalAddress()
if address == localAddress {
finish(.noNat)
return
}
natAddress1 = address
transition(to: .waiting(step: .step2))
await self.sendProbe(step: .step2)
case .step2:
natAddress2 = address
// natAddress2 IPIPNAT;
// ip{dstIp, dstPort, srcIp, srcPort}, ip
if let ip1 = natAddress1?.ipAddress, let ip2 = natAddress2?.ipAddress, ip1 != ip2 {
finish(.symmetric)
return
}
transition(to: .waiting(step: .step3))
await self.sendProbe(step: .step3)
case .step3:
// step3: ip1:port1 <---- ip2:port2 (ipport)
// IPNAT
finish(.fullCone)
case .step4:
finish(.coneRestricted)
}
}
/// Timer / Task
func handleTimeout() async {
guard case .waiting(let currentStep) = state else {
return
}
switch currentStep {
case .step3:
transition(to: .waiting(step: .step4))
await sendProbe(step: .step4)
case .step4:
finish(.portRestricted)
default:
finish(.blocked)
}
}
// MARK: - Internal helpers
private func sendProbe(step: Step) async {
let addressArray = config.stunProbeSocketAddressArray
let remote: SocketAddress
let attr: SDLProbeAttr
switch step {
case .step1:
remote = addressArray[0][0]
attr = .none
case .step2:
remote = addressArray[1][1]
attr = .none
case .step3:
remote = addressArray[0][0]
attr = .peer
case .step4:
remote = addressArray[0][0]
attr = .port
}
var stunProbe = SDLStunProbe()
stunProbe.cookie = self.cookieId
stunProbe.attr = UInt32(attr.rawValue)
self.cookieId &+= 1
await self.udpHole.send(type: .stunProbe, data: try! stunProbe.serializedData(), remoteAddress: remote)
}
private func finish(_ type: NatType) {
guard case .finished = state else {
transition(to: .finished)
logger.log("[NAT] finished with \(type)", level: .info)
onFinished?(type)
onFinished = nil
return
}
}
private func transition(to newState: State) {
state = newState
}
}

View File

@ -12,32 +12,32 @@ import SwiftProtobuf
// sn-server
actor SDLUDPHoleActor {
typealias HoleMessage = (SocketAddress, SDLHoleInboundMessage)
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
private let asyncChannel: NIOAsyncChannel<AddressedEnvelope<ByteBuffer>, AddressedEnvelope<ByteBuffer>>
private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: UDPMessage.self, bufferingPolicy: .unbounded)
private let (writeStream, writeContinuation) = AsyncStream.makeStream(of: UDPHoleOutboundMessage.self, bufferingPolicy: .unbounded)
private var cookieGenerator = SDLIdGenerator(seed: 1)
private var promises: [UInt32:EventLoopPromise<SDLStunProbeReply>] = [:]
public var localAddress: SocketAddress?
public let messageStream: AsyncStream<HoleMessage>
private let messageContinuation: AsyncStream<HoleMessage>.Continuation
public let eventStream: AsyncStream<UDPHoleEvent>
private let eventContinuation: AsyncStream<UDPHoleEvent>.Continuation
private let logger: SDLLogger
struct UDPMessage {
struct UDPHoleOutboundMessage {
let remoteAddress: SocketAddress
let type: SDLPacketType
let data: Data
}
enum UDPHoleEvent {
case ready
case message(SocketAddress, SDLHoleInboundMessage)
}
//
init(logger: SDLLogger) async throws {
self.logger = logger
(self.messageStream, self.messageContinuation) = AsyncStream.makeStream(of: HoleMessage.self, bufferingPolicy: .unbounded)
(self.eventStream, self.eventContinuation) = AsyncStream.makeStream(of: UDPHoleEvent.self, bufferingPolicy: .unbounded)
let bootstrap = DatagramBootstrap(group: group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
@ -56,8 +56,9 @@ actor SDLUDPHoleActor {
}
func start() async throws {
try await withTaskCancellationHandler {
try await self.asyncChannel.executeThenClose {inbound, outbound in
self.eventContinuation.yield(.ready)
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
defer {
@ -71,16 +72,7 @@ actor SDLUDPHoleActor {
let remoteAddress = envelope.remoteAddress
do {
if let message = try Self.decode(buffer: &buffer) {
switch message {
case .data(let data):
self.logger.log("[SDLUDPHole] read data: \(data.format()), from: \(remoteAddress)", level: .debug)
self.messageContinuation.yield((remoteAddress, .data(data)))
case .stunProbeReply(let probeReply):
//
await self.trigger(probeReply: probeReply)
default:
()
}
self.eventContinuation.yield(.message(remoteAddress, message))
} else {
self.logger.log("[SDLUDPHole] decode message, get null", level: .warning)
}
@ -113,51 +105,16 @@ actor SDLUDPHoleActor {
}
}
}
} onCancel: {
self.writeContinuation.finish()
self.messageContinuation.finish()
self.logger.log("[SDLUDPHole] withTaskCancellationHandler cancel")
}
}
func getCookieId() -> UInt32 {
return self.cookieGenerator.nextId()
}
// tun
func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int = 5) async throws -> SDLStunProbeReply {
return try await self._stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: timeout).get()
}
private func _stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int) -> EventLoopFuture<SDLStunProbeReply> {
let cookie = self.cookieGenerator.nextId()
var stunProbe = SDLStunProbe()
stunProbe.cookie = cookie
stunProbe.attr = UInt32(attr.rawValue)
self.send( type: .stunProbe, data: try! stunProbe.serializedData(), remoteAddress: remoteAddress)
self.logger.log("[SDLUDPHole] stunProbe: \(remoteAddress)", level: .debug)
let promise = self.asyncChannel.channel.eventLoop.makePromise(of: SDLStunProbeReply.self)
self.promises[cookie] = promise
return promise.futureResult
}
private func trigger(probeReply: SDLStunProbeReply) {
let id = probeReply.cookie
//
if let promise = self.promises[id] {
self.asyncChannel.channel.eventLoop.execute {
promise.succeed(probeReply)
}
self.promises.removeValue(forKey: id)
}
func getLocalAddress() -> SocketAddress? {
return self.localAddress
}
// MARK: client-client apis
//
func send(type: SDLPacketType, data: Data, remoteAddress: SocketAddress) {
let message = UDPMessage(remoteAddress: remoteAddress, type: type, data: data)
let message = UDPHoleOutboundMessage(remoteAddress: remoteAddress, type: type, data: data)
self.writeContinuation.yield(message)
}
@ -238,7 +195,7 @@ actor SDLUDPHoleActor {
deinit {
try? self.group.syncShutdownGracefully()
self.writeContinuation.finish()
self.messageContinuation.finish()
self.eventContinuation.finish()
}
}

View File

@ -63,9 +63,9 @@ public class SDLConfiguration {
let clientId: String
let token: String
let networkCode: String
let accessToken: String
public init(version: UInt8, installedChannel: String, superHost: String, superPort: Int, stunServers: [StunServer], clientId: String, noticePort: Int, token: String, networkCode: String, remoteDnsServer: String, hostname: String) {
public init(version: UInt8, installedChannel: String, superHost: String, superPort: Int, stunServers: [StunServer], clientId: String, noticePort: Int, token: String, accessToken: String, remoteDnsServer: String, hostname: String) {
self.version = version
self.installedChannel = installedChannel
self.superHost = superHost
@ -74,7 +74,7 @@ public class SDLConfiguration {
self.clientId = clientId
self.noticePort = noticePort
self.token = token
self.networkCode = networkCode
self.accessToken = accessToken
self.remoteDnsServer = remoteDnsServer
self.hostname = hostname
}

View File

@ -30,10 +30,8 @@ public class SDLContext {
let config: SDLConfiguration
// nat,
//var natAddress: SDLNatAddress?
// nat
var natType: NatType = .blocked
var natType: SDLNATProberActor.NatType = .blocked
// AES
var aesCipher: AESCipher
@ -51,6 +49,9 @@ public class SDLContext {
// dnsclient
var dnsClientActor: SDLDNSClientActor?
//
var proberActor: SDLNATProberActor?
//
private var readTask: Task<(), Never>?
@ -162,7 +163,6 @@ public class SDLContext {
try Task.checkCancellation()
if let udpHoleActor = self.udpHoleActor {
let cookie = await udpHoleActor.getCookieId()
var stunRequest = SDLStunRequest()
stunRequest.clientID = self.config.clientId
stunRequest.networkID = self.config.networkAddress.networkId
@ -172,22 +172,26 @@ public class SDLContext {
let remoteAddress = self.config.stunSocketAddress
await udpHoleActor.send(type: .stunRequest, data: try stunRequest.serializedData(), remoteAddress: remoteAddress)
self.lastCookie = cookie
}
}
}
group.addTask {
if let messageStream = self.udpHoleActor?.messageStream {
for try await (remoteAddress, message) in messageStream {
if let eventStream = self.udpHoleActor?.eventStream {
for try await event in eventStream {
try Task.checkCancellation()
switch event {
case .ready:
try await self.handleUDPHoleReady()
case .message(let remoteAddress, let message):
switch message {
case .registerSuperAck(let registerSuperAck):
await self.handleRegisterSuperAck(registerSuperAck: registerSuperAck)
case .registerSuperNak(let registerSuperNak):
await self.handleRegisterSuperNak(nakPacket: registerSuperNak)
case .peerInfo(let peerInfo):
()
await self.puncherActor.handlePeerInfo(peerInfo: peerInfo)
case .event(let event):
try await self.handleEvent(event: event)
case .stunReply(let stunReply):
@ -200,8 +204,7 @@ public class SDLContext {
try await self.handleRegister(remoteAddress: remoteAddress, register: register)
case .registerAck(let registerAck):
await self.handleRegisterAck(remoteAddress: remoteAddress, registerAck: registerAck)
default:
self.logger.log("get unknown message: \(message)", level: .error)
}
}
}
}
@ -220,7 +223,8 @@ public class SDLContext {
switch event {
case .changed:
// nat
self.natType = await self.getNatType()
//self.natType = await self.getNatType()
self.logger.log("didNetworkPathChanged, nat type is: \(self.natType)", level: .info)
case .unreachable:
self.logger.log("didNetworkPathUnreachable", level: .warning)
@ -258,24 +262,30 @@ public class SDLContext {
}
}
// private func handleMessage(remoteAddress: SocketAddress, message: SDLHoleInboundMessage) async throws {
// switch message {
//// case .ready:
//// await self.puncherActor.setSuperClientActor(superClientActor: self.superClientActor)
////
//// self.logger.log("[SDLContext] get registerSuper, mac address: \(SDLUtil.formatMacAddress(mac: self.devAddr.mac))", level: .debug)
//// var registerSuper = SDLRegisterSuper()
//// registerSuper.version = UInt32(self.config.version)
//// registerSuper.clientID = self.config.clientId
//// registerSuper.devAddr = self.devAddr
//// registerSuper.pubKey = self.rsaCipher.pubKey
//// registerSuper.token = self.config.token
//// registerSuper.networkCode = self.config.networkCode
//// registerSuper.hostname = self.config.hostname
//// guard let message = try await self.superClientActor?.request(type: .registerSuper, data: try registerSuper.serializedData()) else {
//// return
//// }
///
private func handleUDPHoleReady() async throws {
await self.puncherActor.setUDPHoleActor(udpHoleActor: self.udpHoleActor)
// nat
if let udpHoleActor = self.udpHoleActor {
self.proberActor = SDLNATProberActor(udpHole: udpHoleActor, config: self.config, logger: self.logger)
await self.proberActor?.start { natType in
self.natType = natType
}
}
var registerSuper = SDLRegisterSuper()
registerSuper.pktID = 0
registerSuper.clientID = self.config.clientId
registerSuper.networkID = self.config.networkAddress.networkId
registerSuper.mac = self.config.networkAddress.mac
registerSuper.ip = self.config.networkAddress.ip
registerSuper.maskLen = UInt32(self.config.networkAddress.maskLen)
registerSuper.hostname = self.config.hostname
registerSuper.pubKey = self.rsaCipher.pubKey
registerSuper.accessToken = self.config.accessToken
await self.udpHoleActor?.send(type: .registerSuper, data: try registerSuper.serializedData(), remoteAddress: self.config.stunSocketAddress)
}
private func handleRegisterSuperAck(registerSuperAck: SDLRegisterSuperAck) async {
// rsa
@ -549,71 +559,6 @@ public class SDLContext {
}
}
//
extension SDLContext {
// nat
enum NatType: UInt8, Encodable {
case blocked = 0
case noNat = 1
case fullCone = 2
case portRestricted = 3
case coneRestricted = 4
case symmetric = 5
}
// nat
func getNatType() async -> NatType {
guard let udpHole = self.udpHoleActor else {
return .blocked
}
let addressArray = config.stunProbeSocketAddressArray
// step1: ip1:port1 <---- ip1:port1
guard let natAddress1 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .none) else {
return .blocked
}
// nat
if await natAddress1 == udpHole.localAddress {
return .noNat
}
// step2: ip2:port2 <---- ip2:port2
guard let natAddress2 = await getNatAddress(udpHole, remoteAddress: addressArray[1][1], attr: .none) else {
return .blocked
}
// natAddress2 IPIPNAT;
// ip{dstIp, dstPort, srcIp, srcPort}, ip
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2)", level: .debug)
if let ipAddress1 = natAddress1.ipAddress, let ipAddress2 = natAddress2.ipAddress, ipAddress1 != ipAddress2 {
return .symmetric
}
// step3: ip1:port1 <---- ip2:port2 (ipport)
// IPNAT
if let natAddress3 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .peer) {
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address3: \(natAddress3)", level: .debug)
return .fullCone
}
// step3: ip1:port1 <---- ip1:port2 (port)
// IPNAT
if let natAddress4 = await getNatAddress(udpHole, remoteAddress: addressArray[0][0], attr: .port) {
logger.log("[SDLNatProber] nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address4: \(natAddress4)", level: .debug)
return .coneRestricted
} else {
return .portRestricted
}
}
private func getNatAddress(_ udpHole: SDLUDPHoleActor, remoteAddress: SocketAddress, attr: SDLProbeAttr) async -> SocketAddress? {
let stunProbeReply = try? await udpHole.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: 5)
return stunProbeReply?.socketAddress()
}
}
private extension UInt32 {
// ip
func asIpAddress() -> String {