app: name what a VPN blocked, and prove v6 broken before saying so
Constraints are detected up front (one throwaway bind per network) and land in run.constraints, a measurement.vpn_constrained finding, the $7.3 verdict (INCONCLUSIVE outright) and a banner on the run screen - a VPN'd run looked exactly like a clean run of a healthy network before this. v6.broken returns to the registry now that it can be earned: V6ConnectProbe (v6.brokenness) makes a real TCP connection over IPv6 to the enrolled server, and only both transports failing on a network that advertises IPv6 justifies the claim. TCP succeeding turns the finding into 'ICMPv6 is filtered, IPv6 works' at high confidence instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
987b2ceb47
commit
20cfecf566
@@ -95,14 +95,22 @@ rolled up under *connectivity* instead — the third occurrence of rule 1 being
|
||||
| `v6.no_default_route` | medium | The device has a global IPv6 address but no IPv6 default route. | Guesswork: this is read from the routing table, not inferred from silence. |
|
||||
| `v6.route_without_address` | medium | The network advertises an IPv6 default route but the device has no global IPv6 address. | A working IPv6 setup: SLAAC did not produce a usable address on this link. |
|
||||
| `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.broken` | high | IPv6 is advertised on this network but carries no traffic. | ICMP filtering as the benign explanation: a TCP connection over IPv6 failed too. |
|
||||
| `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.
|
||||
Discovery.
|
||||
|
||||
`v6.broken` returned once that corroboration existed: the `v6.brokenness` test attempts a real TCP
|
||||
connection over IPv6 to the configured server, and only when *both* transports fail on a network
|
||||
that advertises IPv6 is the brokenness claim made — at high severity, because every dual-stack
|
||||
destination pays a timeout before falling back to IPv4. When the TCP connect *succeeds*,
|
||||
`v6.no_icmp_reply` is emitted at high confidence instead, now able to say plainly that ICMPv6 is
|
||||
filtered while IPv6 works. With no server configured there is no corroboration target and the
|
||||
two-explanation `v6.no_icmp_reply` stands unchanged.
|
||||
|
||||
`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
|
||||
|
||||
@@ -52,12 +52,26 @@ Export encoding: UTF-8 JSON, gzip for files (`.echolot.json.gz`), share intent u
|
||||
},
|
||||
"tiers": { "app": true, "shizuku": true, "root": false },
|
||||
"profiles_used": ["profile-uuid", ...],
|
||||
"constraints": {
|
||||
"vpn_active": true,
|
||||
"per_network_blocked": true,
|
||||
"unmeasured_networks": ["net-0", "net-1"]
|
||||
},
|
||||
"notes": "free-text user annotation"
|
||||
}
|
||||
```
|
||||
|
||||
`tiers` records what was *available*; each test records what it *used*.
|
||||
|
||||
`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
|
||||
server aggregating thousands of them — cannot tell that almost nothing was measured. The known case
|
||||
is `per_network_blocked`: Android refuses `Network.bindSocket()` on the underlying networks while a
|
||||
VPN holds the default route, so every per-network test measures the tunnel or nothing at all, and
|
||||
any conclusion about the link underneath is unfounded. Consumers should treat findings from a
|
||||
constrained run as scoped to what was actually reachable, and `unmeasured_networks` names the rest.
|
||||
|
||||
## 4. `networks[]` — one entry per Android `Network` in play
|
||||
|
||||
A run may exercise several networks simultaneously (Wi-Fi + cellular + USB ethernet). Everything is a snapshot at run start; a `changes[]` list captures mid-run deltas.
|
||||
|
||||
Reference in New Issue
Block a user