app: listen to what the segment says unprompted
Four passive collectors join long mode: SSDP (passive NOTIFY plus paced M-SEARCH from the capture socket, so unicast replies land in the same capture), LLMNR, NetBIOS-NS and WS-Discovery. All are periodic and sparse by nature, which is exactly why they belong to a window rather than a probe - a thirty-second run mostly hears silence and would report an empty network as confidently as a quiet one. NetBIOS reports unsupported on the app tier and says why: UDP 137 is privileged. The decoder and evidence shape are tested and waiting for the Shizuku tier; a recorded reason beats a missing test. Silence without a multicast lock or a group join is PARTIAL, never OK - that case is a fact about this app, not about the network. Hostnames, banners and device UUIDs are classified in core-privacy so the anonymizer treats them like every other identifier rather than letting a neighbour's device model ride out in an upload. No active WSD Probe and no NBSTAT sweep: the app listens to what a network broadcasts, it does not announce itself to strangers or interrogate its neighbours. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
93125a4b1d
commit
9ee7d554a6
@@ -40,6 +40,10 @@ object Classification {
|
||||
listOf(
|
||||
"addr", "address", "gateway", "dst", "src", "servers", "server", "resolver",
|
||||
"next_hop", "via", "public_ip", "observed_ip",
|
||||
// Who sent each passive-discovery announcement (SSDP / LLMNR / NetBIOS / WS-Discovery).
|
||||
// Family-agnostic on purpose: the same field carries a dotted quad from a v4 group and
|
||||
// a link-local from ff02::c, and the v6 transform hands dotted quads to the v4 path.
|
||||
"source_ip",
|
||||
).forEach { put(it, LogicalType.IP6) }
|
||||
|
||||
listOf("mac", "hw_addr", "gateway_mac", "router_mac", "sender_mac", "peer_mac")
|
||||
@@ -52,13 +56,28 @@ object Classification {
|
||||
"friendly_name", "server_name", "sni", "cname", "search_domain", "device_name",
|
||||
// Plural and prefixed variants the models actually use.
|
||||
"search_domains", "private_dns_hostname", "domains", "hostnames",
|
||||
// Names a device shouts at the whole segment. An LLMNR question and a NetBIOS
|
||||
// registration are hostnames in every sense that matters here — they name a machine on
|
||||
// somebody's home network — so they get the same per-label treatment as any other.
|
||||
"netbios_name",
|
||||
).forEach { put(it, LogicalType.FQDN) }
|
||||
|
||||
listOf("session_id", "credential", "token", "device_id", "android_id", "serial", "imsi", "iccid")
|
||||
.forEach { put(it, LogicalType.OPAQUE_ID) }
|
||||
listOf(
|
||||
"session_id", "credential", "token", "device_id", "android_id", "serial", "imsi", "iccid",
|
||||
// Discovery identities. A UPnP USN and a WS-Discovery endpoint UUID are stable,
|
||||
// globally unique per device and frequently derived from a serial number — exactly the
|
||||
// thing that lets two uploads be recognised as the same household.
|
||||
"usn", "device_uuid",
|
||||
).forEach { put(it, LogicalType.OPAQUE_ID) }
|
||||
|
||||
listOf("notes", "detail", "raw", "excerpt", "location", "model_description")
|
||||
.forEach { put(it, LogicalType.FREETEXT) }
|
||||
listOf(
|
||||
"notes", "detail", "raw", "excerpt", "location", "model_description",
|
||||
// The make/model a device volunteers, and the URLs it points at. `server_banner` is
|
||||
// deliberately not named `server`, which the family-agnostic address block already
|
||||
// claims — a SERVER header run through the address transform would be mangled into
|
||||
// nonsense while protecting nothing.
|
||||
"server_banner", "product_hint", "wsd_types", "wsd_xaddrs",
|
||||
).forEach { put(it, LogicalType.FREETEXT) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,6 +98,13 @@ object Classification {
|
||||
private val droppedKeys = setOf(
|
||||
"ssdp_responders", "upnp", "neighbors", "arp_table", "scan_results",
|
||||
"nearby_networks", "peers", "raw_dump", "dumpsys",
|
||||
// The long run's passive-discovery inventories. Same argument as `ssdp_responders`, only
|
||||
// more so: these are minutes of everything the segment said about itself — device models,
|
||||
// hostnames, printers, who is looking for whom. Even fully pseudonymized the *shape* of a
|
||||
// household is a fingerprint, no metric depends on the list (the counts live in `metrics`,
|
||||
// which survives), and the collectors' own status fields stay behind to say the capture
|
||||
// worked. Dropping beats mangling.
|
||||
"ssdp_devices", "llmnr_queries", "netbios_names", "wsd_devices",
|
||||
)
|
||||
|
||||
fun typeOf(key: String, path: List<String>): LogicalType? {
|
||||
|
||||
Reference in New Issue
Block a user