A phone reported "IPv6 is configured but not working" while loading an IPv6-only site over TCP perfectly well. The finding fired on one signal - ICMPv6 echo getting no reply - at HIGH confidence. ICMPv6 echo is widely filtered on networks where IPv6 works, so the two cases are indistinguishable from where the app stands, and it was picking one. Same class of error as the multi-homed downstream-loss bug: a confident measurement of something that was not happening. Now v6.no_icmp_reply, low severity, medium confidence, naming both explanations. Still reported, because filtered ICMPv6 breaks Path MTU Discovery - large packets vanish instead of being reported as too big - which is a fault in its own right. Corroborating with a real IPv6 connection would separate the two properly, but needs a target, which runs into the hardcoded-deployment issue already open. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.3 KiB
Echolot findings registry
Closes open item 1 of measurement-schema.md §9.
A finding code is the stable, machine-readable half of a result. The prose around it changes freely; the code is what a dashboard groups by, what a diff between two runs keys on, and what someone greps a year of archived runs for. That only works if a code means exactly one thing, forever.
This document is the contract. It is kept in step with
echolot-app/core-measurement/.../FindingRegistry.kt by a test that fails when either side has a
code the other does not — a registry that drifts from its documentation is worse than none,
because it looks authoritative.
Rules
- The prefix determines the category, and the category determines which verdict light the
finding rolls up into (§7.3). A
nat.*code appearing under connectivity is not a naming quibble; it changes which light turns red. Two codes were renamed fromnat.*toconnectivity.*for exactly this reason. - One code per concept. Two emitters independently produced
connectivity.downstream_lossandconnectivity.loss_downstreamfor the same claim before this registry existed. Anyone aggregating either would have silently seen half their data. - Codes are declared, not typed. Emitters reference a
FindingSpec, so a typo is a compile error and no two call sites can disagree about a finding's category or default severity. - Severity in the registry is the default. An emitter may escalate for a specific run; it may not quietly reclassify the finding in general.
- Say what is ruled out, where that is the useful half. "Loss upstream" is worth far more when it also states that the return path is clean, because that halves where to look next.
- Renaming a code is a breaking change once runs are archived at scale. Before 1.0 it is cheap; after, it needs an alias and a deprecation window.
Registry
connectivity
| code | severity | means | rules out |
|---|---|---|---|
connectivity.udp_unreachable |
high | No UDP echo replies came back from the server at all. | — |
connectivity.udp_unreachable_upstream |
high | The server received none of the probes, so traffic is dropped on the way out. | The return path: nothing arrived to be replied to. |
connectivity.udp_loss |
medium | A large fraction of round-trip probes were lost, direction unknown. | — |
connectivity.loss_upstream |
medium | Probes were lost on the way to the server. | The return path: replies came back for everything that arrived. |
connectivity.loss_downstream |
medium | Packets were lost on the way back from the server. | The outbound path: the server received what it was answering. |
connectivity.downstream_blocked |
high | Server-initiated packets never arrive, although round trips work. | Basic reachability: the path forwards replies, just not unsolicited traffic. |
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. | — |
mtu
| code | severity | means | rules out |
|---|---|---|---|
mtu.reduced_downstream |
low | The downstream path MTU is below the usual 1500 bytes. | — |
mtu.downstream_blackhole |
medium | Datagrams above the path MTU are dropped downstream, fragmented or not. | — |
mtu.fragments_blocked |
medium | IP fragments do not reach this device even when sent in order. | — |
mtu.fragment_reorder_sensitive |
low | Fragments are delivered in order but dropped when reordered or delayed. | Fragmentation itself: in-order fragments arrive fine. |
nat
| code | severity | means | rules out |
|---|---|---|---|
nat.udp_rebinding |
medium | A NAT remapped the UDP source port mid-flow. | — |
nat.symmetric |
medium | The NAT assigns a different external port per destination. | — |
perf
| code | severity | means | rules out |
|---|---|---|---|
perf.throughput_no_delivery |
high | No throughput traffic arrived, although the server sent it. | — |
perf.throughput_below_offered |
low | Less throughput arrived than the server sent for the whole run. | — |
dns
| code | severity | means | rules out |
|---|---|---|---|
dns.answer_rewritten |
high | A resolver returned an answer that differs from the authoritative record. | — |
dns.authoritative_unreachable |
medium | The canary zone's authoritative server could not be reached. | — |
v6
The prefix is v6., matching the test-type registry (v6.brokenness, v6.happy_eyeballs, …).
These were ipv6.* while declaring Category.IPV6; since the prefix map only knows v6, they
rolled up under connectivity instead — the third occurrence of rule 1 being broken.
| code | severity | means | rules out |
|---|---|---|---|
v6.no_icmp_reply |
low | IPv6 is configured but ICMPv6 echo gets no reply. | Nothing on its own: IPv6 may work fine with ICMP filtered. |
v6.not_offered |
info | This network does not offer IPv6. | — |
v6.no_icmp_reply was v6.broken until a phone reported it while loading an IPv6-only site over
TCP perfectly well. The only evidence behind it is ICMPv6 echo, which is widely filtered on
networks where IPv6 works — so the finding now states what was observed and names both
explanations instead of choosing one. It is still worth reporting: filtered ICMPv6 breaks Path MTU
Discovery. Corroborating it with a real IPv6 connection would let the two cases be separated, and
is the proper fix.
v6.not_offered is info and must stay info. Most networks still do not offer IPv6 and that is
not a fault; reporting it as a warning lights a yellow verdict on a healthy network, which teaches
people to ignore the light — the one thing a diagnostic must never do.
Adding a finding
- Add a
FindingSpectoFindingRegistry, and to itsalllist. - Add the row here, under the section its prefix names.
- Emit it with
finding(FindingRegistry.YOUR_CODE, …).
The registry test checks 1 and 2 agree, that every prefix maps to the category it claims, and that no two entries share a code.