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
+87 -6
View File
@@ -24,6 +24,7 @@ final class MeshDaemon {
private let identity: NodeIdentity
private let router: LinkStateRouter
private let firewall: MeshFirewall
private let nativeFirewall: NativePacketFirewall
private let configURL: URL
private var config: DaemonConfig
private let ipc: IPCServer
@@ -35,12 +36,19 @@ final class MeshDaemon {
private var lsaSequence: UInt64 = 0
private var seenPackets = Set<UUID>()
private var seenOrder: [UUID] = []
private var replayPackets = Set<UUID>()
private var replayOrder: [UUID] = []
private var textBindings: [UInt16: IPCClient] = [:]
private var streamBindings: [UInt16: IPCClient] = [:]
private var streams: [UInt64: StreamState] = [:]
private var pendingPings: [UUID: (IPCClient, Date)] = [:]
private var pingWindows: [MeshAddress: (Date, Int)] = [:]
private var timer: DispatchSourceTimer?
private var nativeInterface: NativeInterfaceClient?
private var synchronizedRoutes = Set<MeshAddress>()
private var nextInterfaceAttempt = Date.distantPast
private var dns: MeshDNSServer?
private var dnsState = "stopped"
init(baseURL: URL = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Library/Application Support/UltraMesh"), socketPath: String? = nil) throws {
@@ -50,6 +58,7 @@ final class MeshDaemon {
configURL = baseURL.appendingPathComponent("config.json")
config = DaemonConfig.load(from: configURL)
firewall = MeshFirewall(rules: config.firewall)
nativeFirewall = NativePacketFirewall(rules: firewall)
ipc = IPCServer(path: socketPath ?? baseURL.appendingPathComponent("umnd.sock").path)
let wallClockBase = UInt64(Date().timeIntervalSince1970 * 1000)
lsaSequence = max((config.lsaSequence ?? 0) &+ 1_000_000, wallClockBase)
@@ -63,6 +72,8 @@ final class MeshDaemon {
try startNetwork()
ipc.onMessage = { [weak self] client, message in self?.queue.async { self?.handleIPC(client, message) } }
try ipc.start()
startDNS()
attemptInterface()
publishLocalState()
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + 1, repeating: 1)
@@ -74,6 +85,7 @@ final class MeshDaemon {
func stop() {
queue.sync {
timer?.cancel(); browser?.cancel(); listener?.cancel()
nativeInterface?.disconnect(); dns?.stop()
allSessions.values.forEach { $0.stop() }; ipc.stop()
}
}
@@ -185,7 +197,8 @@ final class MeshDaemon {
@discardableResult private func send(_ inner: InnerFrame, to remote: NodeRecord) -> Bool {
guard remote.address != address,
let sealed = try? identity.seal(inner, to: remote) else { return false }
let packet = RoutedPacket(source: address, destination: remote.address, sealed: sealed)
let packet = RoutedPacket(source: address, destination: remote.address, sealed: sealed,
trafficClass: inner.kind == .ipv6Packet ? .nativeIPv6 : .overlay)
guard router.routes()[remote.address] != nil || peers[remote.address] != nil else { return false }
forward(packet); return true
}
@@ -214,6 +227,12 @@ final class MeshDaemon {
case .streamData: receiveStreamData(inner)
case .streamAck: receiveStreamAck(inner)
case .streamClose, .streamReset: receiveStreamClose(inner)
case .ipv6Packet:
guard let replayID = inner.replayID, markReplay(replayID),
let info = try? IPv6PacketParser.parse(inner.payload),
info.source == packet.source, info.destination == address,
nativeFirewall.allowInbound(info, packet: inner.payload) else { return }
_ = nativeInterface?.inject(inner.payload)
case .error: receiveStreamClose(inner)
}
}
@@ -304,9 +323,23 @@ final class MeshDaemon {
response.ok = ok; response.message = text; response.values = values; response.streamID = streamID
client.send(response)
}
guard message.version == 2 else { reply(false, "incompatible CLI protocol version"); return }
switch message.operation {
case .status:
reply(true, "umnd running as \(address); \(peers.count) direct peer(s), \(router.routes().count) route(s)")
let state = nativeInterface?.status
reply(true, "umnd running as \(address); \(peers.count) direct peer(s), \(router.routes().count) route(s); " +
"interface \(state?.name ?? "unavailable") MTU 1280, helper \(state?.helperState ?? "disconnected"), " +
"\(state?.routeCount ?? 0) route(s) synchronized, DNS \(dnsState)")
case .interfaceStatus:
let state = nativeInterface?.status
let name = state?.name ?? "unavailable"
var response = IPCMessage(operation: message.operation, requestID: message.requestID)
response.ok = true; response.interfaceName = state?.name; response.interfaceAddress = address.description
response.interfaceMTU = 1280; response.helperState = state?.helperState ?? "disconnected"
response.synchronizedRouteCount = state?.routeCount ?? 0; response.dnsState = dnsState
response.message = "interface: \(name)\naddress: \(address)\nMTU: 1280\nhelper: \(response.helperState!)\n" +
"routes synchronized: \(response.synchronizedRouteCount!)\nDNS: \(dnsState)"
client.send(response)
case .address: reply(true, address.description)
case .peers: reply(true, values: peers.keys.sorted().map(\.description))
case .routes:
@@ -323,14 +356,16 @@ final class MeshDaemon {
guard let name = message.name?.lowercased() else { reply(false, "missing alias"); return }
config.aliases.removeValue(forKey: name); saveConfig(); reply(true)
case .firewallList:
let values = firewall.allRules().map { "allow \($0.port) from \($0.source?.description ?? "any")" }
let values = firewall.allRules().map { "allow \($0.protocolKind.rawValue) \($0.port) from \($0.source?.description ?? "any")" }
reply(true, values: values)
case .firewallAllow, .firewallRevoke:
guard let port = message.port, let sourceText = message.source else { reply(false, "missing port or source"); return }
guard let protocolKind = message.firewallProtocol, let port = message.port, let sourceText = message.source else {
reply(false, "new firewall rules require overlay, tcp, or udp"); return
}
let source: MeshAddress?
if sourceText == "any" { source = nil } else if let parsed = resolve(sourceText) { source = parsed } else { reply(false, "invalid source"); return }
if message.operation == .firewallAllow { firewall.allow(port: port, source: source) }
else { firewall.revoke(port: port, source: source) }
if message.operation == .firewallAllow { firewall.allow(protocol: protocolKind, port: port, source: source) }
else { firewall.revoke(protocol: protocolKind, port: port, source: source) }
config.firewall = Set(firewall.allRules()); saveConfig(); reply(true)
case .ping:
guard let target = message.target, let remote = record(for: target) else { reply(false, "no route to destination"); return }
@@ -410,6 +445,51 @@ final class MeshDaemon {
return true
}
private func markReplay(_ id: UUID) -> Bool {
guard !replayPackets.contains(id) else { return false }
replayPackets.insert(id); replayOrder.append(id)
if replayOrder.count > 4096 { replayPackets.remove(replayOrder.removeFirst()) }
return true
}
private func startDNS() {
let server = MeshDNSServer { [weak self] in
guard let self else { return [:] }
return self.queue.sync { self.config.aliases }
}
do { try server.start(); dns = server; dnsState = "listening on 127.0.0.1:53535" }
catch { dnsState = "unavailable (\(error.localizedDescription))"; log("DNS unavailable: \(error)") }
}
private func attemptInterface() {
guard nativeInterface == nil, Date() >= nextInterfaceAttempt else { return }
nextInterfaceAttempt = Date().addingTimeInterval(5)
let socket = ProcessInfo.processInfo.environment["UMN_HELPER_SOCKET"] ?? "/var/run/ultramesh-helper.sock"
let client = NativeInterfaceClient(local: address, socketPath: socket, queue: queue)
client.onPacket = { [weak self] packet in self?.receiveOutboundIPv6(packet) }
client.onDisconnect = { [weak self] in self?.nativeInterface = nil; self?.synchronizedRoutes.removeAll() }
do {
try client.connect(); nativeInterface = client; synchronizeNativeRoutes(force: true)
if nativeInterface != nil { log("native interface connected") }
}
catch { client.disconnect(); log("native interface unavailable: \(error.localizedDescription)") }
}
private func synchronizeNativeRoutes(force: Bool = false) {
guard let nativeInterface else { return }
let desired = Set(router.routes().keys.filter { router.record(for: $0) != nil })
guard force || desired != synchronizedRoutes else { return }
do { try nativeInterface.setRoutes(desired); synchronizedRoutes = desired }
catch { log("route synchronization failed: \(error.localizedDescription)"); nativeInterface.disconnect() }
}
private func receiveOutboundIPv6(_ packet: Data) {
guard let info = try? IPv6PacketParser.parse(packet), info.source == address,
let remote = router.record(for: info.destination), nativeFirewall.allowOutbound(info) else { return }
let frame = InnerFrame(kind: .ipv6Packet, payload: packet, sourceRecord: identity.record, replayID: UUID())
_ = send(frame, to: remote)
}
private func allowPing(from source: MeshAddress) -> Bool {
let now = Date()
if let current = pingWindows[source], now.timeIntervalSince(current.0) < 1 {
@@ -427,6 +507,7 @@ final class MeshDaemon {
if Int(now.timeIntervalSince1970) % 5 == 0 { broadcast(.keepalive, excluding: nil) }
if Int(now.timeIntervalSince1970) % 10 == 0 { publishLocalState() }
router.expire(now: now)
attemptInterface(); synchronizeNativeRoutes()
pingWindows = pingWindows.filter { now.timeIntervalSince($0.value.0) < 60 }
let expiredPings = pendingPings.filter { now.timeIntervalSince($0.value.1) > 10 }
for (id, pending) in expiredPings {