Files
2026-08-31 16:44:54 -05:00

97 lines
4.6 KiB
Swift

import Foundation
import Darwin
import UltraMeshCore
import UMNSystem
private struct HelperOptions {
var uid: uid_t
var socket = "/var/run/ultramesh-helper.sock"
init() throws {
let args = Array(CommandLine.arguments.dropFirst())
guard let uidIndex = args.firstIndex(of: "--uid"), uidIndex + 1 < args.count,
let parsed = UInt32(args[uidIndex + 1]) else { throw UMNError.message("umn-helper requires --uid") }
uid = parsed
if let index = args.firstIndex(of: "--socket"), index + 1 < args.count { socket = args[index + 1] }
}
}
private func runCommand(_ executable: String, _ arguments: [String]) throws {
let process = Process(); process.executableURL = URL(fileURLWithPath: executable); process.arguments = arguments
process.standardOutput = FileHandle.nullDevice; process.standardError = FileHandle.standardError
try process.run(); process.waitUntilExit()
guard process.terminationStatus == 0 else {
throw UMNError.message("\(executable) failed with status \(process.terminationStatus)")
}
}
private func readLine(_ fd: Int32, limit: Int = 32_768) throws -> String? {
var bytes: [UInt8] = []
while bytes.count < limit {
var byte: UInt8 = 0; let count = Darwin.read(fd, &byte, 1)
if count == 0 { return bytes.isEmpty ? nil : String(decoding: bytes, as: UTF8.self) }
if count < 0 { if errno == EINTR { continue }; throw POSIXError(.init(rawValue: errno) ?? .EIO) }
if byte == 10 { return String(decoding: bytes, as: UTF8.self) }
bytes.append(byte)
}
throw UMNError.invalidFrame
}
private func handle(_ client: Int32, allowedUID: uid_t) {
var device: Int32 = -1; var interface = ""; var routes = Set<MeshAddress>()
defer {
for route in routes { try? runCommand("/sbin/route", ["-n", "delete", "-inet6", route.description]) }
if !interface.isEmpty { try? runCommand("/sbin/ifconfig", [interface, "down"]) }
if device >= 0 { Darwin.close(device) }
UnixIPC.close(client)
}
do {
var peerUID: uid_t = 0; var peerGID: gid_t = 0
guard umn_get_peer_eid(client, &peerUID, &peerGID) == 0, peerUID == allowedUID else {
throw UMNError.message("helper authentication failed")
}
guard let hello = try readLine(client), hello.hasPrefix("UMN2 "),
let local = try? MeshAddress(String(hello.dropFirst(5))) else { throw UMNError.invalidFrame }
var name = [CChar](repeating: 0, count: Int(IFNAMSIZ))
device = umn_utun_open(&name, name.count)
guard device >= 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) }
interface = String(cString: name)
try runCommand("/sbin/ifconfig", [interface, "inet6", local.description, "prefixlen", "128", "mtu", "1280", "up"])
let ready = Data("READY \(interface) 1280\n".utf8)
let sent = ready.withUnsafeBytes { umn_send_fd(client, device, $0.baseAddress, ready.count) }
guard sent == 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) }
while let line = try readLine(client) {
guard line.hasPrefix("ROUTES") else { throw UMNError.invalidFrame }
let fields = line.split(separator: " ").dropFirst()
var desired = Set<MeshAddress>()
for field in fields {
guard let address = try? MeshAddress(String(field)) else { throw UMNError.invalidFrame }
desired.insert(address)
}
try HelperRouteSet.validate(desired, local: local)
let diff = HelperRouteSet.diff(current: routes, desired: desired)
for address in diff.removals {
try runCommand("/sbin/route", ["-n", "delete", "-inet6", address.description]); routes.remove(address)
}
for address in diff.additions {
try runCommand("/sbin/route", ["-n", "add", "-inet6", address.description, "-interface", interface]); routes.insert(address)
}
}
} catch {
FileHandle.standardError.write(Data("umn-helper: \(error.localizedDescription)\n".utf8))
}
}
do {
guard geteuid() == 0 else { throw UMNError.message("umn-helper must run as root") }
let options = try HelperOptions()
let server = try UnixIPC.listen(path: options.socket)
guard chown(options.socket, options.uid, gid_t.max) == 0, chmod(options.socket, 0o600) == 0 else {
throw POSIXError(.init(rawValue: errno) ?? .EIO)
}
signal(SIGPIPE, SIG_IGN)
while true { let client = try UnixIPC.accept(server); handle(client, allowedUID: options.uid) }
} catch {
FileHandle.standardError.write(Data("umn-helper: \(error.localizedDescription)\n".utf8)); exit(1)
}