Initial commit: NetworkCity — network traffic as a neon city

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>
This commit is contained in:
2026-06-13 08:43:32 -07:00
commit c08fc277a9
33 changed files with 2499 additions and 0 deletions
@@ -0,0 +1,49 @@
import XCTest
@testable import NetworkCityCore
final class ExpansionPolicyTests: XCTestCase {
typealias D = ExpansionPolicy.District
func testBusyMultiEndpointOrgExpands() {
let policy = ExpansionPolicy()
let expanded = policy.update([D(key: "onedrive", bytes: 500_000, endpointCount: 15)])
XCTAssertTrue(expanded.contains("onedrive"))
}
func testSingleEndpointNeverExpandsEvenIfHuge() {
// The exact case we saw live: Google at 126 MB but only one active IP.
let policy = ExpansionPolicy()
let expanded = policy.update([D(key: "google", bytes: 126_000_000, endpointCount: 1)])
XCTAssertTrue(expanded.isEmpty)
}
func testQuietOrgDoesNotExpand() {
let policy = ExpansionPolicy(floorBytes: 8 * 1024)
let expanded = policy.update([D(key: "apple", bytes: 100, endpointCount: 5)])
XCTAssertTrue(expanded.isEmpty)
}
func testOnlyTopKExpand() {
let policy = ExpansionPolicy(topK: 2)
let districts = (0..<5).map { D(key: "org\($0)", bytes: UInt64(1000 - $0) * 1024, endpointCount: 3) }
let expanded = policy.update(districts)
XCTAssertEqual(expanded, ["org0", "org1"]) // two busiest only
}
func testHysteresisKeepsExpandedThenCollapses() {
let policy = ExpansionPolicy(ttlTicks: 3)
// Tick 1: busy -> expands.
XCTAssertTrue(policy.update([D(key: "x", bytes: 1_000_000, endpointCount: 4)]).contains("x"))
// Now idle, but still has endpoints: stays up for the TTL tail.
XCTAssertTrue(policy.update([D(key: "x", bytes: 0, endpointCount: 4)]).contains("x")) // ttl 2
XCTAssertTrue(policy.update([D(key: "x", bytes: 0, endpointCount: 4)]).contains("x")) // ttl 1
XCTAssertFalse(policy.update([D(key: "x", bytes: 0, endpointCount: 4)]).contains("x")) // ttl 0 -> collapsed
}
func testExpiredTimerWithLostEndpointsStaysCollapsed() {
let policy = ExpansionPolicy(ttlTicks: 2)
_ = policy.update([D(key: "x", bytes: 1_000_000, endpointCount: 4)])
// Endpoints drop to 1 while timer still alive -> nothing to fan out.
XCTAssertFalse(policy.update([D(key: "x", bytes: 0, endpointCount: 1)]).contains("x"))
}
}
@@ -0,0 +1,42 @@
import XCTest
@testable import NetworkCityCore
final class IPRangesTests: XCTestCase {
func testAppleEightSlashEight() {
XCTAssertEqual(IPRanges.match("17.248.242.19")?.key, "apple")
XCTAssertEqual(IPRanges.match("17.42.251.69")?.name, "Apple")
}
func testTelegram() {
XCTAssertEqual(IPRanges.match("149.154.175.54")?.key, "telegram")
}
func testPrivateRangesAreLocalNetwork() {
XCTAssertEqual(IPRanges.match("192.168.0.1")?.name, "Local Network")
XCTAssertEqual(IPRanges.match("10.1.2.3")?.key, "lan")
XCTAssertEqual(IPRanges.match("172.20.0.5")?.key, "lan")
}
func testUnknownIPReturnsNil() {
XCTAssertNil(IPRanges.match("8.8.8.8"))
}
func testInvalidIPReturnsNil() {
XCTAssertNil(IPRanges.match("not.an.ip"))
XCTAssertNil(IPRanges.match("999.1.1.1"))
}
func testClassifyUsesRangeFallbackWhenNoPTR() {
// Apple publishes no PTR for 17/8, so the range table must catch it.
let org = OrgClassifier.classify(ip: "17.248.242.19", ptr: nil)
XCTAssertEqual(org.key, "apple")
XCTAssertEqual(org.name, "Apple")
}
func testPTRStillWinsOverRange() {
// A real registrable domain in the PTR should take precedence.
let org = OrgClassifier.classify(ip: "17.248.242.19", ptr: "host.example.com")
XCTAssertEqual(org.key, "example.com")
}
}
@@ -0,0 +1,35 @@
import XCTest
import CoreGraphics
@testable import NetworkCityCore
final class LatencyLayoutTests: XCTestCase {
func testZeroRTTSitsAtInnerRadius() {
XCTAssertEqual(LatencyLayout.radius(forRTT: 0), LatencyLayout.minRadius, accuracy: 0.001)
}
func testCeilingSitsAtOuterEdge() {
XCTAssertEqual(LatencyLayout.radius(forRTT: 250), LatencyLayout.maxRadius, accuracy: 0.001)
}
func testClampsBeyondCeiling() {
XCTAssertEqual(LatencyLayout.radius(forRTT: 5000), LatencyLayout.maxRadius, accuracy: 0.001)
}
func testMonotonicAndWithinBounds() {
var last = LatencyLayout.radius(forRTT: 0)
for rtt in stride(from: 5.0, through: 250.0, by: 5) {
let r = LatencyLayout.radius(forRTT: rtt)
XCTAssertGreaterThan(r, last) // farther RTT -> farther out
XCTAssertLessThanOrEqual(r, LatencyLayout.maxRadius)
last = r
}
}
func testNearRangeSpreadsOut() {
// The log curve should give the 150ms band real separation, not a clump.
let r10 = LatencyLayout.radius(forRTT: 10)
let r50 = LatencyLayout.radius(forRTT: 50)
XCTAssertGreaterThan(r50 - r10, 40) // meaningfully far apart on screen
}
}
@@ -0,0 +1,45 @@
import XCTest
import CoreGraphics
@testable import NetworkCityCore
final class LayoutSolverTests: XCTestCase {
// A label box centered under the node: ~160 wide, sitting below the dot.
private let label = CGRect(x: -80, y: -34, width: 160, height: 30)
private func worldBox(at c: CGPoint) -> CGRect { label.offsetBy(dx: c.x, dy: c.y) }
func testEmptyMapReturnsSeed() {
let p = LayoutSolver.placeNonOverlapping(baseAngle: 0, baseRadius: 200, localRect: label, existing: [])
XCTAssertEqual(p.x, 200, accuracy: 0.001) // attempt 0 == seed
XCTAssertEqual(p.y, 0, accuracy: 0.001)
}
func testAvoidsOverlapWithExisting() {
// Pre-place a box exactly where the seed would land.
let seed = CGPoint(x: cos(0.0) * 200, y: sin(0.0) * 200)
let existing = [worldBox(at: seed).insetBy(dx: -16, dy: -16)]
let p = LayoutSolver.placeNonOverlapping(baseAngle: 0, baseRadius: 200, localRect: label, existing: existing)
XCTAssertNotEqual(p, seed) // had to move
// The chosen spot's padded box must clear the existing one.
let placed = worldBox(at: p).insetBy(dx: -16, dy: -16)
XCTAssertFalse(placed.intersects(existing[0]))
}
func testResultClearsAllOfManyNeighbours() {
// Seed eight districts and confirm none of their boxes overlap.
var placed: [CGRect] = []
for i in 0..<8 {
let angle = Double(i) / 8 * 2 * .pi
let c = LayoutSolver.placeNonOverlapping(baseAngle: angle, baseRadius: 180, localRect: label, existing: placed)
placed.append(worldBox(at: c))
}
for a in 0..<placed.count {
for b in (a + 1)..<placed.count {
XCTAssertFalse(placed[a].intersects(placed[b]), "districts \(a) and \(b) overlap")
}
}
}
}
@@ -0,0 +1,67 @@
import XCTest
@testable import NetworkCityCore
/// Fixtures are taken verbatim from real `nettop -L 1 -x -n -J time,bytes_in,bytes_out`
/// output captured on macOS 26, so the parser is pinned to reality.
final class NettopParserTests: XCTestCase {
let sample = """
time,,bytes_in,bytes_out,
23:49:56.574393,launchd.1,0,0,
23:49:56.573243,tcp4 127.0.0.1:8021<->*:*,,,
23:49:56.574395,apsd.571,15671580,11326466,
23:49:56.573044,tcp4 192.168.10.194:57359<->17.57.144.184:5223,15671580,11326466,
23:49:56.574397,trustd.609,10032,18469,
23:49:56.569491,quic4 192.168.10.194:51698<->17.248.242.102:443,5017,9232,
23:49:56.574410,mDNSResponder.647,974941537,77402609,
23:49:56.569906,udp6 *.5353<->*.*,176848739,34857088,
23:49:56.574316,tcp6 fe80::d08d:6eb7:1dcd:6466%utun4.1024<->fe80::9ed2:d4f7:aa69:9e47%utun4.1024,0,0,
"""
func testParsesProcessAndConnectionRows() {
let conns = NettopParser.parseConnections(sample)
// 4 connection rows present (launchd listener, apsd, trustd, mDNS, utun6)
XCTAssertEqual(conns.count, 5)
}
func testConnectionInheritsPrecedingProcess() {
let conns = NettopParser.parseConnections(sample)
let apsd = conns.first { $0.remote.host == "17.57.144.184" }
XCTAssertEqual(apsd?.processName, "apsd")
XCTAssertEqual(apsd?.pid, 571)
XCTAssertEqual(apsd?.proto, .tcp4)
XCTAssertEqual(apsd?.remote.port, 5223)
XCTAssertEqual(apsd?.bytesIn, 15671580)
XCTAssertEqual(apsd?.bytesOut, 11326466)
}
func testIPv4EndpointParsing() {
let ep = NettopParser.parseEndpoint("192.168.10.194:57359", isV6: false)
XCTAssertEqual(ep.host, "192.168.10.194")
XCTAssertEqual(ep.port, 57359)
}
func testIPv6EndpointParsingStripsZone() {
let ep = NettopParser.parseEndpoint("fe80::d08d:6eb7:1dcd:6466%utun4.1024", isV6: true)
XCTAssertEqual(ep.host, "fe80::d08d:6eb7:1dcd:6466")
XCTAssertEqual(ep.port, 1024)
}
func testWildcardEndpointHasNoPort() {
XCTAssertNil(NettopParser.parseEndpoint("*:*", isV6: false).port)
XCTAssertNil(NettopParser.parseEndpoint("*.*", isV6: true).port)
}
func testExternalClassification() {
let conns = NettopParser.parseConnections(sample)
let external = conns.filter(\.isExternal)
// Only apsd (17.57.x) and quic (17.248.x) are real off-box destinations.
// Loopback listener, mDNS wildcard, and fe80 link-local are excluded.
XCTAssertEqual(Set(external.map(\.remote.host)), ["17.57.144.184", "17.248.242.102"])
}
func testQuicProtocolRecognised() {
let conns = NettopParser.parseConnections(sample)
XCTAssertTrue(conns.contains { $0.proto == .quic4 })
}
}
@@ -0,0 +1,46 @@
import XCTest
@testable import NetworkCityCore
final class OrgClassifierTests: XCTestCase {
func testRegistrableDomainSimple() {
XCTAssertEqual(OrgClassifier.registrableDomain("cdn.gstatic.com"), "gstatic.com")
XCTAssertEqual(OrgClassifier.registrableDomain("a.b.c.example.org"), "example.org")
}
func testRegistrableDomainMultiPartSuffix() {
XCTAssertEqual(OrgClassifier.registrableDomain("www.bbc.co.uk"), "bbc.co.uk")
XCTAssertEqual(OrgClassifier.registrableDomain("shop.foo.com.au"), "foo.com.au")
}
func testRegistrableDomainHandlesTrailingDot() {
XCTAssertEqual(OrgClassifier.registrableDomain("host.apple.com."), "apple.com")
}
func testKnownOrgsCollapseToOneDistrict() {
// Different registrable domains, same owner -> same key.
let a = OrgClassifier.classify(ip: "1", ptr: "cdn.gstatic.com")
let b = OrgClassifier.classify(ip: "2", ptr: "lb.1e100.net")
XCTAssertEqual(a.key, "google")
XCTAssertEqual(b.key, "google")
XCTAssertEqual(a.name, "Google")
}
func testAppleFamilyCollapses() {
XCTAssertEqual(OrgClassifier.classify(ip: "1", ptr: "x.icloud.com").key, "apple")
XCTAssertEqual(OrgClassifier.classify(ip: "2", ptr: "y.aaplimg.com").key, "apple")
}
func testUnknownDomainGetsPrettyName() {
let org = OrgClassifier.classify(ip: "9.9.9.9", ptr: "resolver.quad9.net")
XCTAssertEqual(org.key, "quad9.net")
XCTAssertEqual(org.name, "Quad9")
}
func testNoPTRFallsBackToIP() {
let org = OrgClassifier.classify(ip: "203.0.113.7", ptr: nil)
XCTAssertEqual(org.key, "203.0.113.7")
XCTAssertEqual(org.name, "203.0.113.7")
XCTAssertNil(org.resolvedHost)
}
}
@@ -0,0 +1,34 @@
import XCTest
@testable import NetworkCityCore
final class TrafficClassTests: XCTestCase {
func testQuicByProtocol() {
XCTAssertEqual(TrafficClass.classify(proto: .quic4, port: 443), .quic)
XCTAssertEqual(TrafficClass.classify(proto: .quic6, port: 12345), .quic)
}
func testHTTPSOverTCP() {
XCTAssertEqual(TrafficClass.classify(proto: .tcp4, port: 443), .https)
}
func testUDP443IsQuic() {
XCTAssertEqual(TrafficClass.classify(proto: .udp4, port: 443), .quic)
}
func testDNS() {
XCTAssertEqual(TrafficClass.classify(proto: .udp4, port: 53), .dns)
XCTAssertEqual(TrafficClass.classify(proto: .udp6, port: 5353), .dns)
XCTAssertEqual(TrafficClass.classify(proto: .tcp4, port: 53), .dns)
}
func testHTTP() {
XCTAssertEqual(TrafficClass.classify(proto: .tcp4, port: 80), .http)
XCTAssertEqual(TrafficClass.classify(proto: .tcp4, port: 8080), .http)
}
func testOther() {
XCTAssertEqual(TrafficClass.classify(proto: .tcp4, port: 22), .other)
XCTAssertEqual(TrafficClass.classify(proto: .tcp4, port: nil), .other)
}
}
@@ -0,0 +1,44 @@
import XCTest
@testable import NetworkCityCore
final class TrafficDifferTests: XCTestCase {
private func conn(_ host: String, in bIn: UInt64, out bOut: UInt64) -> Connection {
Connection(
proto: .tcp4,
local: Endpoint(host: "192.168.1.2", port: 5000),
remote: Endpoint(host: host, port: 443),
bytesIn: bIn, bytesOut: bOut,
processName: "test", pid: 1
)
}
func testFirstSnapshotYieldsNoDeltas() {
let differ = TrafficDiffer()
let t0 = Date(timeIntervalSince1970: 1000)
let deltas = differ.ingest(ConnectionSnapshot(timestamp: t0, connections: [conn("1.1.1.1", in: 100, out: 50)]))
XCTAssertTrue(deltas.isEmpty) // need two readings to know a rate
}
func testComputesDeltaAndRate() {
let differ = TrafficDiffer()
let t0 = Date(timeIntervalSince1970: 1000)
let t1 = Date(timeIntervalSince1970: 1002) // +2s
_ = differ.ingest(ConnectionSnapshot(timestamp: t0, connections: [conn("1.1.1.1", in: 100, out: 50)]))
let deltas = differ.ingest(ConnectionSnapshot(timestamp: t1, connections: [conn("1.1.1.1", in: 1124, out: 50)]))
XCTAssertEqual(deltas.count, 1)
XCTAssertEqual(deltas[0].bytesInDelta, 1024)
XCTAssertEqual(deltas[0].bytesOutDelta, 0)
XCTAssertEqual(deltas[0].inBytesPerSec, 512, accuracy: 0.001) // 1024 / 2s
}
func testCounterResetTreatedAsZero() {
let differ = TrafficDiffer()
let t0 = Date(timeIntervalSince1970: 1000)
let t1 = Date(timeIntervalSince1970: 1002)
_ = differ.ingest(ConnectionSnapshot(timestamp: t0, connections: [conn("1.1.1.1", in: 9999, out: 0)]))
let deltas = differ.ingest(ConnectionSnapshot(timestamp: t1, connections: [conn("1.1.1.1", in: 10, out: 0)]))
XCTAssertTrue(deltas.isEmpty) // reused tuple, counter dropped -> no phantom delta
}
}