a lot
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user