app: tell a broken device resolver apart from a broken network
Diagnosing a tablet that claimed "no internet" took twenty adb commands to establish something the app should have said in one run: ping to 1.1.1.1 worked, the configured DNS server answered a raw UDP query in 65 bytes, and Android still could not resolve a hostname. The network was fine; netd had wedged. Those two failures look identical to a user and want opposite responses — "look at your router" against "toggle your wifi" — so dns.resolver asks the network's own servers directly and compares the answer against what the platform returns for the same name. The query is hand-rolled over a plain DatagramSocket on purpose: anything routed through a resolver API would inherit the very fault being looked for. dns.system_resolver_broken fires only on the pairing that is otherwise unattributable: server answered, platform did not. Per network, because a phone can have wedged wifi and working cellular at once. Also records Android's own verdict per network — validated, captive portal, partial connectivity — which the app reproduced with its own HTTP probes but never stored. It is free, it is what the user sees in the status bar, and its disagreement with our measurements is exactly what identified the tablet. NET_CAPABILITY_PARTIAL_CONNECTIVITY is @SystemApi so the constant is inlined with its rationale, in the manner of OsAbi.kt, and read defensively enough to report unknown rather than false. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
40e76c52ca
commit
d65dbbc75a
@@ -397,6 +397,9 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
||||
// app happened to be developed against. With no server configured they get blank
|
||||
// strings and report themselves skipped, which is the honest outcome — the
|
||||
// alternative measures someone else's infrastructure and calls it your network.
|
||||
// Before the canary: "can this device resolve at all" has to be answered before
|
||||
// "are the answers being tampered with" means anything.
|
||||
app.echo_lot.probe.DnsResolverProbe(entries),
|
||||
DnsCanaryProbe(canaryZone = settings.canaryZone, sessionPrefix = "adhoc"),
|
||||
StunProbe(serverHost = settings.serverHost()),
|
||||
)
|
||||
@@ -656,6 +659,39 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
||||
)
|
||||
}
|
||||
}
|
||||
if (t.type == TestType.DNS_RESOLVER && t.status == TestStatus.OK) {
|
||||
// One finding per network: on a phone the wifi resolver can be wedged while
|
||||
// cellular is fine, and "DNS is broken" would be wrong about half the device.
|
||||
val ev = t.evidence
|
||||
if (ev != null) {
|
||||
for ((_, v) in ev) {
|
||||
val o = v as? kotlinx.serialization.json.JsonObject ?: continue
|
||||
fun str(k: String) =
|
||||
(o[k] as? kotlinx.serialization.json.JsonPrimitive)?.content
|
||||
if (str("verdict") != "server answers, device resolver does not") continue
|
||||
val ref = str("network_ref")
|
||||
val where = networks.firstOrNull { it.id == ref }?.iface
|
||||
?.takeIf { it.isNotBlank() } ?: "this network"
|
||||
out.add(
|
||||
Finding(
|
||||
id = ids.uuid(),
|
||||
code = FindingRegistry.DNS_SYSTEM_RESOLVER_BROKEN.code,
|
||||
category = FindingRegistry.DNS_SYSTEM_RESOLVER_BROKEN.category,
|
||||
severity = FindingRegistry.DNS_SYSTEM_RESOLVER_BROKEN.severity,
|
||||
confidence = Confidence.HIGH,
|
||||
title = "This device cannot resolve names, but the DNS server is fine ($where)",
|
||||
description = "A DNS query sent straight from this device was " +
|
||||
"answered by ${str("servers") ?: "the configured server"}, yet " +
|
||||
"asking Android to resolve the same name fails. The network is " +
|
||||
"working; this device's resolver is not. Turning wifi off and on " +
|
||||
"again, or rejoining the network, usually clears it. If it " +
|
||||
"returns after a restart, look at the network instead.",
|
||||
evidenceRefs = listOf(EvidenceRef(t.id)),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (t.type == TestType.ICMP_PING6) {
|
||||
// A network with no IPv6 at all is NORMAL — most networks are still IPv4-only, and
|
||||
// that is not a defect. What IS a defect is IPv6 the network claims to provide (a
|
||||
|
||||
Reference in New Issue
Block a user