app: long runs that watch, and a relay for the port that keeps moving
Long mode starts listeners at t=0 and keeps them running past the battery: a network-change watcher that finally fills networks[].changes[] (defined since the schema's first draft, never populated), an RSSI log, a ping series giving loss and jitter over minutes, and mDNS listening for the whole window. This is the class of fault a short run cannot see - a link that drops for four seconds between two probes is reported healthy by both of them. run.mode records which question was asked, because silence means different things in the two modes. The adb relay replaces the retired beacon: AdbRelay watches adbd's own mDNS with the resolve-once discipline the beacon learned the hard way (resolving re-arms adbd and pops a notification), a foreground service keeps it alive with the screen off, and the heartbeat re-posts the cached endpoint rather than re-resolving. It exists because mDNS does not cross subnets and the wireless-debug port rotates every few minutes. Also records why LLDP/CDP cannot follow SSDP into long mode: both are raw L2 frames, so they need CAP_NET_RAW - root tier, not app, and Shizuku's shell user does not have it either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ae63bd7c7f
commit
0071e00003
@@ -1379,6 +1379,26 @@ 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.
|
||||
|
||||
## LLDP and CDP are root-tier, and that is a hard boundary (2026-08-02)
|
||||
|
||||
Asked for alongside SSDP in long mode; they belong to a different tier and no amount of app-side
|
||||
cleverness moves them. LLDP is an EtherType `0x88CC` frame to `01:80:C2:00:00:0E`; CDP is an
|
||||
LLC/SNAP frame to `01:00:0C:CC:CC:CC`. Neither is IP, so neither is ever delivered to a socket an
|
||||
app can open — receiving them needs `AF_PACKET` with `CAP_NET_RAW`, which is root. Shizuku does
|
||||
not bridge this either: the ADB shell user (uid 2000) has no `CAP_NET_RAW`, and stock devices do
|
||||
not ship `tcpdump`. Android's unprivileged ICMP sockets are what make `icmp.ping4` work without
|
||||
root; there is no equivalent back door for raw L2 receive.
|
||||
|
||||
Worth building in the root module when it lands, because the payoff is large: LLDP names the
|
||||
switch, the port and the VLAN a device is attached to, which is the best available answer to
|
||||
"where in this building am I actually plugged in", and CDP does the same on Cisco gear. Until
|
||||
then they are recorded as absent capabilities rather than left to look unimplemented.
|
||||
|
||||
What IS reachable at app tier, and what long mode now listens for instead: SSDP (passive NOTIFY
|
||||
plus periodic M-SEARCH), LLMNR, NetBIOS-NS and WS-Discovery — all IP multicast/broadcast, all
|
||||
sockets an app may open. The security reading matters as much as the inventory: LLMNR and
|
||||
NetBIOS-NS being live on a segment is a finding in itself, since both are trivially spoofable.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -50,6 +50,13 @@ because it looks authoritative.
|
||||
| `connectivity.downstream_reorder` | low | Downstream packets arrive in a different order than they were sent. | — |
|
||||
| `connectivity.captive_portal` | medium | A captive portal is intercepting connectivity checks. | — |
|
||||
| `connectivity.no_internet` | high | Android's own connectivity checks fail on this network. | — |
|
||||
| `connectivity.link_flapping` | medium | A network dropped and came back one or more times during the run. | A momentary probe failure: the drop was watched happening, not inferred from silence. |
|
||||
|
||||
`connectivity.link_flapping` is only reachable from a **long run** (`run.mode: "long"`,
|
||||
measurement-schema §3). It is derived from `networks[].changes[]` rather than from any test's
|
||||
evidence, because no one-shot probe can produce it: the probes before and after a four-second drop
|
||||
both succeed. The emitter escalates to *high* from three completed drop-and-return cycles, and
|
||||
requires the cycle to complete — a network switched off partway through a run is not flapping.
|
||||
|
||||
### mtu
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ Export encoding: UTF-8 JSON, gzip for files (`.echolot.json.gz`), share intent u
|
||||
{
|
||||
"id": "0198c5f2-...-uuidv7",
|
||||
"trigger": "manual | scheduled | monitor | peer",
|
||||
"mode": "short | long",
|
||||
"started_at": "2026-07-29T14:03:21.114Z",
|
||||
"ended_at": "2026-07-29T14:07:44.902Z",
|
||||
"clock": {
|
||||
@@ -63,6 +64,27 @@ Export encoding: UTF-8 JSON, gzip for files (`.echolot.json.gz`), share intent u
|
||||
|
||||
`tiers` records what was *available*; each test records what it *used*.
|
||||
|
||||
`mode` records how long the run watched, and it exists because **it changes what a reader may
|
||||
conclude from absence**. A `short` run is a sequence of one-shot probes — each looks at the network
|
||||
for a second or two and moves on — which characterises the network's *configuration* well and is
|
||||
structurally blind to anything intermittent. A `long` run starts continuous listeners at t=0, runs
|
||||
the same battery beside them, and keeps sampling until its window closes; the window's length is
|
||||
recorded in the `params` of the tests the listeners produce, not here.
|
||||
|
||||
The consequence is asymmetric and matters more than the field looks. A finding is worth the same in
|
||||
either mode: a drop that was observed, was observed. Silence is not. "No link changes were seen" is
|
||||
evidence of a stable link after five minutes of watching and is evidence of nothing at all after a
|
||||
thirty-second run, in which a link could drop and return between two consecutive probes without
|
||||
leaving a mark anywhere in the document. Consumers — a diff between two runs, a dashboard counting
|
||||
how often a fault occurs, a person reading one report — must therefore not treat the absence of a
|
||||
time-dependent finding in a `short` run as its refutation, and must not compare the two modes as if
|
||||
they had asked the same question. `connectivity.link_flapping` is the first finding that only a
|
||||
`long` run can reach; `networks[].changes[]` (§4) is likewise populated only by a long run's
|
||||
listener, and an empty `changes[]` in a short run means "not watched", never "nothing happened".
|
||||
|
||||
Absent `mode` means `short`: it was added after the first documents were written, and every one of
|
||||
them was a battery of one-shot probes.
|
||||
|
||||
`constraints` records what was *prevented*. A constrained run is neither a failed run nor a normal
|
||||
one, and the distinction has to survive into the data: a run taken through a VPN has the same shape
|
||||
and the same green verdict as a clean run of a healthy network, so without this a reader — or a
|
||||
|
||||
Reference in New Issue
Block a user