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
+99
View File
@@ -0,0 +1,99 @@
import Foundation
/// A grouping of destinations under one real-world owner. `key` is the stable
/// grouping id (a registrable domain, or a bare IP when unresolved); `name` is
/// what the district is labelled.
public struct Org: Sendable, Hashable {
public let key: String
public let name: String
public let resolvedHost: String?
public init(key: String, name: String, resolvedHost: String?) {
self.key = key
self.name = name
self.resolvedHost = resolvedHost
}
}
/// Maps a reverse-DNS hostname to an `Org`. Pure and deterministic so it can be
/// unit-tested without touching the network.
public enum OrgClassifier {
private struct Rule { let key: String; let name: String; let domains: Set<String> }
/// Well-known owners whose traffic spreads across many registrable domains.
/// Anything matched here collapses to a single district.
private static let rules: [Rule] = [
Rule(key: "apple", name: "Apple",
domains: ["apple.com", "icloud.com", "aaplimg.com", "mzstatic.com", "cdn-apple.com", "apple-dns.net"]),
Rule(key: "google", name: "Google",
domains: ["google.com", "googleusercontent.com", "gstatic.com", "googleapis.com",
"1e100.net", "ggpht.com", "youtube.com", "ytimg.com", "google-analytics.com"]),
Rule(key: "amazon", name: "Amazon / AWS",
domains: ["amazonaws.com", "amazon.com", "cloudfront.net", "aws.dev", "awsglobalaccelerator.com"]),
Rule(key: "microsoft", name: "Microsoft",
domains: ["microsoft.com", "windows.com", "windowsupdate.com", "azure.com",
"azureedge.net", "office.com", "live.com", "msftncsi.com"]),
Rule(key: "meta", name: "Meta",
domains: ["facebook.com", "fbcdn.net", "instagram.com", "whatsapp.net", "fb.com"]),
Rule(key: "cloudflare", name: "Cloudflare",
domains: ["cloudflare.com", "cloudflare-dns.com", "cf-dns.com"]),
Rule(key: "akamai", name: "Akamai",
domains: ["akamai.net", "akamaiedge.net", "akamaitechnologies.com", "akadns.net"]),
Rule(key: "fastly", name: "Fastly",
domains: ["fastly.net", "fastlylb.net"]),
Rule(key: "github", name: "GitHub",
domains: ["github.com", "githubusercontent.com", "githubassets.com"]),
Rule(key: "spotify", name: "Spotify",
domains: ["spotify.com", "scdn.co", "spotifycdn.com"]),
Rule(key: "netflix", name: "Netflix",
domains: ["netflix.com", "nflxvideo.net", "nflxso.net", "nflximg.net"]),
Rule(key: "telegram", name: "Telegram",
domains: ["telegram.org", "t.me", "telegram.me"]),
Rule(key: "anthropic", name: "Anthropic",
domains: ["anthropic.com", "claude.ai"]),
]
/// Registrable suffixes that span two labels, so the registrable domain is
/// the last *three* labels (e.g. `bbc.co.uk`, not `co.uk`).
private static let multiPartSuffixes: Set<String> = [
"co.uk", "org.uk", "gov.uk", "ac.uk", "co.jp", "co.nz", "co.in",
"com.au", "net.au", "org.au", "com.br", "com.cn", "com.mx", "co.kr", "co.za",
]
public static func classify(ip: String, ptr: String?) -> Org {
// 1. A PTR with a real registrable domain is the most accurate signal.
if let ptr, let reg = registrableDomain(ptr) {
if let rule = rules.first(where: { $0.domains.contains(reg) }) {
return Org(key: rule.key, name: rule.name, resolvedHost: ptr)
}
return Org(key: reg, name: prettify(reg), resolvedHost: ptr)
}
// 2. No usable PTR fall back to the curated CIDR table (Apple's 17/8,
// Telegram, private LAN, ).
if let match = IPRanges.match(ip) {
return Org(key: match.key, name: match.name, resolvedHost: ptr)
}
// 3. Unknown: the IP is its own (unlabelled) district.
return Org(key: ip, name: ip, resolvedHost: ptr)
}
/// eTLD+1 with a small built-in multi-part-suffix table. `a.b.example.co.uk`
/// -> `example.co.uk`; `cdn.gstatic.com` -> `gstatic.com`.
public static func registrableDomain(_ host: String) -> String? {
let trimmed = host.lowercased().trimmingCharacters(in: CharacterSet(charactersIn: "."))
let labels = trimmed.split(separator: ".").map(String.init)
guard labels.count >= 2 else { return nil }
let lastTwo = labels.suffix(2).joined(separator: ".")
if labels.count >= 3 && multiPartSuffixes.contains(lastTwo) {
return labels.suffix(3).joined(separator: ".")
}
return lastTwo
}
/// `example.co.uk` -> `Example`.
public static func prettify(_ registrable: String) -> String {
guard let sld = registrable.split(separator: ".").first else { return registrable }
return sld.prefix(1).uppercased() + sld.dropFirst()
}
}