punchnet-macos/Tun/UDPHole/SDLUDPHole.swift
2026-05-27 21:24:22 +08:00

136 lines
4.2 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.

//
// SDLanServer.swift
// Tun
//
// Created by on 2024/1/31.
//
import Foundation
import NIOCore
import NIOPosix
// sn-server
final class SDLUDPHole: ChannelInboundHandler {
typealias InboundIn = AddressedEnvelope<ByteBuffer>
struct SDLHoleDatagram {
let remoteAddress: SocketAddress
let message: SDLHoleMessage
}
enum State {
case idle
case running
case stopped
}
private var state: State = .idle
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
private var channel: Channel?
let messageStream: AsyncThrowingStream<SDLHoleDatagram, Error>
private let messageContinuation: AsyncThrowingStream<SDLHoleDatagram, Error>.Continuation
init() throws {
let (stream, continuation) = AsyncThrowingStream.makeStream(of: SDLHoleDatagram.self, bufferingPolicy: .bufferingNewest(2048))
self.messageStream = stream
self.messageContinuation = continuation
}
func start() throws -> SocketAddress {
guard self.state == .idle else {
guard let localAddress = self.channel?.localAddress else {
throw SDLUDPHoleError.invalidLocalAddress
}
return localAddress
}
let bootstrap = DatagramBootstrap(group: group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.channelInitializer { channel in
channel.pipeline.addHandler(self)
}
// IPv4IPv4
let channel = try bootstrap.bind(host: "0.0.0.0", port: 0).wait()
guard let localAddress = channel.localAddress else {
throw SDLUDPHoleError.invalidLocalAddress
}
self.channel = channel
self.state = .running
return localAddress
}
// --MARK: ChannelInboundHandler delegate
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let envelope = unwrapInboundIn(data)
var buffer = envelope.data
let remoteAddress = envelope.remoteAddress
do {
if let message = try SDLHoleMessage.decode(buffer: &buffer) {
self.messageContinuation.yield(SDLHoleDatagram(remoteAddress: remoteAddress, message: message))
} else {
SDLLogger.log("[SDLUDPHole] decode message, get null", for: .debug)
}
} catch let err {
SDLLogger.log("[SDLUDPHole] decode message, get error: \(err)", for: .debug)
self.messageContinuation.finish(throwing: err)
}
}
func channelInactive(context: ChannelHandlerContext) {
self.messageContinuation.finish(throwing: SDLUDPHoleError.closed)
}
func errorCaught(context: ChannelHandlerContext, error: any Error) {
context.close(promise: nil)
self.messageContinuation.finish(throwing: SDLUDPHoleError.errorCaught)
}
// MARK:
func send(type: SDLPacketType, data: Data, remoteAddress: SocketAddress) {
guard self.state == .running, let channel = self.channel else {
return
}
var buffer = channel.allocator.buffer(capacity: data.count + 1)
buffer.writeBytes([type.rawValue])
buffer.writeBytes(data)
let envelope = AddressedEnvelope<ByteBuffer>(remoteAddress: remoteAddress, data: buffer)
let promise = channel.eventLoop.makePromise(of: Void.self)
channel.eventLoop.execute {
channel.writeAndFlush(envelope, promise: promise)
}
promise.futureResult.whenFailure { [weak self] err in
self?.messageContinuation.finish(throwing: SDLUDPHoleError.sendFaied(err))
}
}
func stop() {
guard self.state != .stopped else {
return
}
self.state = .stopped
self.messageContinuation.finish()
let channel = self.channel
self.channel = nil
try? channel?.close().wait()
try? self.group.syncShutdownGracefully()
SDLLogger.log("[SDLUDPHole] stopped", for: .debug)
}
deinit {
SDLLogger.log("[SDLUDPHole] deinit", for: .debug)
}
}