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>
94 lines
2.9 KiB
Swift
94 lines
2.9 KiB
Swift
import Foundation
|
|
|
|
/// Transport flavour as reported by `nettop` (the trailing 4/6 is the IP version).
|
|
public enum NetProtocol: String, Sendable, Hashable {
|
|
case tcp4, tcp6, udp4, udp6, quic4, quic6, other
|
|
|
|
public init(_ raw: String) {
|
|
self = NetProtocol(rawValue: raw) ?? .other
|
|
}
|
|
}
|
|
|
|
/// One end of a connection. `port` is nil for a wildcard (`*`) — i.e. a listener.
|
|
public struct Endpoint: Sendable, Hashable {
|
|
public let host: String
|
|
public let port: Int?
|
|
|
|
public init(host: String, port: Int?) {
|
|
self.host = host
|
|
self.port = port
|
|
}
|
|
|
|
public var isWildcard: Bool { host == "*" || host.isEmpty }
|
|
|
|
public var display: String {
|
|
guard let port else { return host }
|
|
return "\(host):\(port)"
|
|
}
|
|
}
|
|
|
|
/// A single connection sampled from `nettop`. Byte counts are cumulative
|
|
/// (lifetime of the connection), so traffic *rates* come from diffing snapshots.
|
|
public struct Connection: Sendable, Hashable {
|
|
public let proto: NetProtocol
|
|
public let local: Endpoint
|
|
public let remote: Endpoint
|
|
public let bytesIn: UInt64
|
|
public let bytesOut: UInt64
|
|
public let processName: String
|
|
public let pid: Int32
|
|
|
|
public init(
|
|
proto: NetProtocol,
|
|
local: Endpoint,
|
|
remote: Endpoint,
|
|
bytesIn: UInt64,
|
|
bytesOut: UInt64,
|
|
processName: String,
|
|
pid: Int32
|
|
) {
|
|
self.proto = proto
|
|
self.local = local
|
|
self.remote = remote
|
|
self.bytesIn = bytesIn
|
|
self.bytesOut = bytesOut
|
|
self.processName = processName
|
|
self.pid = pid
|
|
}
|
|
|
|
/// Stable identity for diffing across snapshots (ignores byte counts).
|
|
public var key: String {
|
|
"\(proto.rawValue) \(local.display)<->\(remote.display)"
|
|
}
|
|
|
|
/// True when the remote is a real off-box destination — the only kind that
|
|
/// becomes a "building" in the city. Filters listeners, loopback, link-local.
|
|
public var isExternal: Bool {
|
|
let h = remote.host
|
|
if remote.isWildcard { return false }
|
|
if h == "::1" || h.hasPrefix("127.") { return false } // loopback
|
|
if h.hasPrefix("fe80") || h == "::" { return false } // link-local / unspecified
|
|
return true
|
|
}
|
|
|
|
/// True when the remote is on the local network (a "neighbourhood" district).
|
|
public var isPrivateLAN: Bool {
|
|
let h = remote.host
|
|
return h.hasPrefix("10.")
|
|
|| h.hasPrefix("192.168.")
|
|
|| h.hasPrefix("169.254.")
|
|
|| (h.hasPrefix("172.") && (16...31).contains(Int(h.split(separator: ".").dropFirst().first ?? "") ?? -1))
|
|
}
|
|
}
|
|
|
|
/// All connections observed at one instant.
|
|
public struct ConnectionSnapshot: Sendable {
|
|
public let timestamp: Date
|
|
public let connections: [Connection]
|
|
|
|
public init(timestamp: Date, connections: [Connection]) {
|
|
self.timestamp = timestamp
|
|
self.connections = connections
|
|
}
|
|
}
|