c08fc277a9
A native macOS network monitor that renders live traffic as a top-down neon city: your Mac is downtown, remote orgs are glowing districts, and traffic is cars of light driving the roads. Architecture: - NetworkCityCore: swappable data layer behind a ConnectionSource protocol (nettop-backed today), plus pure/tested logic — traffic diffing, org classification (reverse-DNS + CIDR), traffic classes, hub-and-spoke expansion policy, label anti-overlap, and latency→distance layout. - NetworkCityApp: SwiftUI + SpriteKit city — auto-expanding districts, protocol-coloured cars, click-to-inspect panel, and latency-as-distance (districts glide to their measured RTT). - nettop-probe: CLI proof of the data layer. 44 tests passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2.5 KiB
Swift
70 lines
2.5 KiB
Swift
import Foundation
|
|
import Network
|
|
|
|
/// Measures round-trip time to an endpoint with an unprivileged TCP connect
|
|
/// (time from start to `.ready` ≈ one handshake RTT). Results are cached.
|
|
///
|
|
/// We probe a port we've already seen traffic on, so we're not knocking on
|
|
/// closed doors — and the connection carries no data, just the handshake.
|
|
public actor LatencyProber {
|
|
private var cache: [String: Double] = [:]
|
|
|
|
public init() {}
|
|
|
|
public func cached(_ host: String) -> Double? { cache[host] }
|
|
|
|
/// Measure (or refresh) RTT in milliseconds. Returns nil if unreachable.
|
|
public func measure(host: String, port: UInt16) async -> Double? {
|
|
let rtt = await Self.tcpHandshakeRTT(host: host, port: port)
|
|
if let rtt { cache[host] = rtt }
|
|
return rtt
|
|
}
|
|
|
|
private static func tcpHandshakeRTT(host: String, port: UInt16) async -> Double? {
|
|
guard let nwPort = NWEndpoint.Port(rawValue: port) else { return nil }
|
|
|
|
return await withCheckedContinuation { (cont: CheckedContinuation<Double?, Never>) in
|
|
let conn = NWConnection(host: NWEndpoint.Host(host), port: nwPort, using: .tcp)
|
|
let queue = DispatchQueue(label: "latency.probe")
|
|
let start = DispatchTime.now()
|
|
let state = ProbeState(conn: conn, cont: cont)
|
|
|
|
conn.stateUpdateHandler = { st in
|
|
switch st {
|
|
case .ready:
|
|
let ns = DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds
|
|
state.finish(Double(ns) / 1_000_000)
|
|
case .failed, .cancelled:
|
|
state.finish(nil)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
conn.start(queue: queue)
|
|
queue.asyncAfter(deadline: .now() + 2) { state.finish(nil) } // timeout
|
|
}
|
|
}
|
|
|
|
/// Resumes the continuation exactly once, whichever callback fires first.
|
|
private final class ProbeState: @unchecked Sendable {
|
|
private let lock = NSLock()
|
|
private var done = false
|
|
private let conn: NWConnection
|
|
private let cont: CheckedContinuation<Double?, Never>
|
|
|
|
init(conn: NWConnection, cont: CheckedContinuation<Double?, Never>) {
|
|
self.conn = conn
|
|
self.cont = cont
|
|
}
|
|
|
|
func finish(_ value: Double?) {
|
|
lock.lock()
|
|
if done { lock.unlock(); return }
|
|
done = true
|
|
lock.unlock()
|
|
conn.cancel()
|
|
cont.resume(returning: value)
|
|
}
|
|
}
|
|
}
|