This commit is contained in:
Owen Qwen
2026-08-31 16:44:54 -05:00
parent e71b3ada71
commit 5f99d9b137
21 changed files with 1045 additions and 47 deletions
+121
View File
@@ -0,0 +1,121 @@
import Foundation
import Darwin
import UltraMeshCore
import UMNSystem
struct NativeInterfaceStatus {
var name: String?
var address: MeshAddress
var mtu = 1280
var helperState: String
var routeCount = 0
}
final class NativeInterfaceClient {
private let local: MeshAddress
private let socketPath: String
private let callbackQueue: DispatchQueue
private var helperFD: Int32 = -1
private var deviceFD: Int32 = -1
private var readSource: DispatchSourceRead?
private var helperSource: DispatchSourceRead?
private let writeLock = NSLock()
private(set) var status: NativeInterfaceStatus
var onPacket: ((Data) -> Void)?
var onDisconnect: (() -> Void)?
init(local: MeshAddress, socketPath: String = "/var/run/ultramesh-helper.sock", queue: DispatchQueue) {
self.local = local; self.socketPath = socketPath; callbackQueue = queue
status = .init(address: local, helperState: "disconnected")
}
func connect() throws {
guard helperFD < 0 else { return }
let socket = try UnixIPC.connect(path: socketPath)
do {
try writeAll(Data("UMN2 \(local)\n".utf8), fd: socket)
var receivedFD: Int32 = -1; var buffer = [UInt8](repeating: 0, count: 256)
let count = umn_recv_fd(socket, &receivedFD, &buffer, buffer.count)
guard count > 0, receivedFD >= 0 else { throw UMNError.invalidFrame }
var adopted = false
defer { if !adopted { Darwin.close(receivedFD) } }
var readyData = Data(buffer.prefix(count))
while !readyData.contains(10), readyData.count < buffer.count {
var byte: UInt8 = 0
let amount = Darwin.read(socket, &byte, 1)
guard amount == 1 else { throw UMNError.invalidFrame }
readyData.append(byte)
}
guard readyData.last == 10, let ready = String(data: readyData, encoding: .utf8) else { throw UMNError.invalidFrame }
let fields = ready.trimmingCharacters(in: .whitespacesAndNewlines).split(separator: " ")
guard fields.count == 3, fields[0] == "READY", fields[1].hasPrefix("utun"), fields[2] == "1280" else {
throw UMNError.invalidFrame
}
helperFD = socket; deviceFD = receivedFD
adopted = true
status.name = String(fields[1]); status.helperState = "connected"
let source = DispatchSource.makeReadSource(fileDescriptor: receivedFD, queue: callbackQueue)
source.setEventHandler { [weak self] in self?.readPacket() }
source.setCancelHandler { Darwin.close(receivedFD) }
source.resume(); readSource = source
let helperMonitor = DispatchSource.makeReadSource(fileDescriptor: socket, queue: callbackQueue)
helperMonitor.setEventHandler { [weak self] in self?.helperBecameReadable() }
helperMonitor.resume(); helperSource = helperMonitor
} catch { UnixIPC.close(socket); throw error }
}
func setRoutes(_ addresses: Set<MeshAddress>) throws {
guard helperFD >= 0 else { throw UMNError.message("helper disconnected") }
let line = "ROUTES" + addresses.sorted().map { " \($0)" }.joined() + "\n"
do { try writeAll(Data(line.utf8), fd: helperFD); status.routeCount = addresses.count }
catch { disconnect(); throw error }
}
func inject(_ packet: Data) -> Bool {
guard deviceFD >= 0 else { return false }
var family = UInt32(AF_INET6).bigEndian
let framed = withUnsafeBytes(of: &family) { Data($0) } + packet
writeLock.lock(); defer { writeLock.unlock() }
return (try? writeAll(framed, fd: deviceFD)) != nil
}
func disconnect() {
let wasConnected = helperFD >= 0
readSource?.cancel(); readSource = nil; deviceFD = -1
helperSource?.cancel(); helperSource = nil
if helperFD >= 0 { UnixIPC.close(helperFD); helperFD = -1 }
status.name = nil; status.helperState = "disconnected"; status.routeCount = 0
if wasConnected { onDisconnect?() }
}
private func readPacket() {
guard deviceFD >= 0 else { return }
var buffer = [UInt8](repeating: 0, count: IPv6PacketParser.maximumPacketSize + 4)
let count = Darwin.read(deviceFD, &buffer, buffer.count)
guard count > 4 else { if count <= 0 { disconnect() }; return }
let family = buffer.prefix(4).reduce(UInt32(0)) { ($0 << 8) | UInt32($1) }
guard family == UInt32(AF_INET6) else { return }
onPacket?(Data(buffer[4..<count]))
}
private func helperBecameReadable() {
guard helperFD >= 0 else { return }
var byte: UInt8 = 0
_ = recv(helperFD, &byte, 1, MSG_PEEK)
// The protocol has no unsolicited helper messages, so data or EOF means
// the session is no longer trustworthy.
disconnect()
}
private func writeAll(_ data: Data, fd: Int32) throws {
try data.withUnsafeBytes { bytes in
guard let base = bytes.baseAddress else { return }
var offset = 0
while offset < data.count {
let count = Darwin.write(fd, base.advanced(by: offset), data.count - offset)
if count < 0 { if errno == EINTR { continue }; throw POSIXError(.init(rawValue: errno) ?? .EIO) }
offset += count
}
}
}
}