server: relay a test device's adb endpoint, because mDNS does not cross subnets

The beacon this replaces was a separate service wildcard-bound to
0.0.0.0:443 - it silently occupied port 443 on the reserved measurement
addresses, voiding the IPv4 interception proof for as long as it ran, and
it accepted a port report from anyone who could reach it. So this lives
where the repo's own post-mortem said it belongs: POST on the control
plane authenticated by the device credential, GET on the admin UI behind
the existing apiAdmin helper. No new listener, no new port, no wildcard.

Entries expire after 24h (ECHOLOT_ADB_ENDPOINT_RETENTION_H) on both write
and read - a LAN address is a breadcrumb for driving a test device, not
measurement data worth keeping.

Also records the BLE peer-comparison design: the case for it is that BLE
is out-of-band, which is what makes client isolation measurable at all -
silence over IP cannot distinguish an isolating AP from an absent peer,
and a peer confirming out-of-band that it was listening turns that
silence into proof.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-08-02 14:31:01 +02:00
co-authored by Claude Opus 5
parent ab6e278272
commit ae63bd7c7f
15 changed files with 918 additions and 14 deletions
+54
View File
@@ -1379,6 +1379,60 @@ poisons `/releases/latest` for the string-comparing updater the moment anyone ta
The stale `server-v0.9.2` release (same code lineage, wrong number, created during the confusion)
remains in Gitea but is harmless now that v0.11.3 outranks it as latest.
## Design note: what BLE between two devices is actually for (2026-08-02, not built)
Two or more phones running Echolot, talking over Bluetooth LE. The schema already anticipates
this — `Trigger.PEER` and the whole `peer.*` test family (`peer.reachability`, `peer.isolation`,
`peer.multicast`, `peer.lan_train`, `peer.lease_diff`) are in the registry, unused — and the
prober measured `peer.ble_advertise` **SUPPORTED on both known devices**, so the mechanism is
proven; what has been missing is a reason that beats "use the server".
**The reason is that BLE is out-of-band.** Everything else this app does depends on the network
under test being at least partly functional. A second device reachable over a radio that shares
nothing with the wifi turns several measurements from ambiguous into conclusive:
1. **Client isolation becomes measurable at all.** Today, "I sent a packet to the peer and heard
nothing" cannot distinguish AP client isolation from the peer being asleep, gone, or on a
different VLAN — the failure mode is silence, and silence has too many parents. With BLE the
peer confirms out-of-band that it was listening on address X at time T, so silence over IP
becomes *proof* of isolation rather than a guess. This is the single strongest argument for
the feature, and it mirrors the rule this project keeps rediscovering: a measurement that
cannot separate "nothing happened" from "nothing was tried" is not a measurement.
2. **Differential diagnosis: the network or this phone?** Two devices on the same SSID, one
resolving DNS and one not, settles in seconds what a single device cannot settle at all —
and it is the same distinction `system_verdict` exists to draw, only with a second opinion
instead of Android's. Natural finding: *this device fails where a peer on the same link
succeeds* → look at the device (private DNS, ad blocker, per-client router rule, MAC
randomization), not the router.
3. **Two DHCP servers on one L2**, the classic invisible fault: peers compare lease source,
subnet and gateway (`peer.lease_diff`). Disagreement is conclusive and needs no server.
4. **Coverage and roaming**, later: several devices sampling RSSI in different rooms, exchanging
summaries over BLE, gives a picture no single device standing in one place can produce.
**What crosses the link is a summary, never the document.** A measurement document describes
someone's home network in detail; broadcasting it to whoever is nearby would betray the whole
posture of §8. The peer payload should be: a *hashed* network identity (so two devices can agree
they are on the same L2 without either putting the SSID/BSSID on the air in the clear), the §7.3
category verdicts, the finding codes, and an IP endpoint plus a one-shot nonce for the LAN tests.
Findings and verdicts are already the interpretation layer — exactly the right granularity to
share.
**Privacy constraints, which are not optional here.** A BLE advertiser is a tracking beacon: it
must be user-initiated, time-boxed to the run, carry no identifier that is stable across runs
(the resolvable-private-address default plus a per-session ephemeral id), and pair by a code the
two humans can see. "Discoverable by default" would make this app a worse citizen than the
networks it audits.
**Deliberately not doing:** clock synchronisation over BLE. GATT latency is jitter measured in
tens of milliseconds, which is the same order as the one-way delays worth measuring; peers should
sync against the server's `time.server_offset` and use BLE only to correlate run ids. Nor should
BLE become a transport for uploads — it is a *comparison* channel.
Staging when it happens: `peer.isolation` first (highest value, needs only advertise + connect +
a nonce exchange), then `peer.lease_diff` (pure summary comparison, no extra plumbing), then the
rest. Needs `BLUETOOTH_ADVERTISE/CONNECT/SCAN` in the manifest, which the app does not yet
request.
## v0.11.3 live on fmr; trains validated end to end (2026-08-02)
Deployed via `--self-update` (the pre-signing v0.11.2 updater accepted the first signed release,