app: dns.canary probe — client half of the canary measurement, verified live
Resolves the server's canary zone through the platform resolver and compares against the spec-frozen ground truth (probe-protocol §6.1): reference records detect answers rewritten in flight, and a per-run nonce name (uncacheable) proves the query reached the authoritative server. Findings: dns.answer_rewritten (high), dns.authoritative_unreachable (medium). Verified on the OnePlus against the deployed fmr zone: 4/4 reference records matched exactly, nonce name answered 192.0.2.21 with reached_authoritative=true. First full client<->server measurement loop on real hardware; report archived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cf5cd2dc68
commit
59ba1c16bc
@@ -12,6 +12,7 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.echo_lot.measurement.*
|
||||
import app.echo_lot.probe.CaptivePortalProbe
|
||||
import app.echo_lot.probe.DnsCanaryProbe
|
||||
import app.echo_lot.probe.IcmpProbe
|
||||
import app.echo_lot.probe.LinkSnapshotProbe
|
||||
import app.echo_lot.probe.NetworkInventory
|
||||
@@ -71,6 +72,9 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
||||
IcmpProbe(entries, v6 = false),
|
||||
IcmpProbe(entries, v6 = true),
|
||||
CaptivePortalProbe(entries),
|
||||
// Canary zone served by the Echolot probe server (probe-protocol §6.1). Hardcoded to
|
||||
// the reference deployment until profiles/enrollment land in the UI.
|
||||
DnsCanaryProbe(canaryZone = "c.echo-lot.app", sessionPrefix = "adhoc"),
|
||||
)
|
||||
|
||||
val tests = ArrayList<Test>()
|
||||
@@ -157,6 +161,30 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
||||
)
|
||||
}
|
||||
}
|
||||
if (t.type == TestType.DNS_CANARY) {
|
||||
val ev = t.evidence?.toString() ?: ""
|
||||
if (ev.contains("MISMATCH")) {
|
||||
out.add(
|
||||
Finding(
|
||||
id = ids.uuid(), code = "dns.answer_rewritten", category = Category.DNS,
|
||||
severity = Severity.HIGH, confidence = Confidence.HIGH,
|
||||
title = "DNS answers are being rewritten",
|
||||
description = "A canary reference record returned different RDATA than the spec-defined ground truth — something on the path is rewriting DNS answers (interception, filtering, or a middlebox).",
|
||||
evidenceRefs = listOf(EvidenceRef(t.id)),
|
||||
)
|
||||
)
|
||||
} else if (ev.contains("\"reached_authoritative\":false")) {
|
||||
out.add(
|
||||
Finding(
|
||||
id = ids.uuid(), code = "dns.authoritative_unreachable", category = Category.DNS,
|
||||
severity = Severity.MEDIUM, confidence = Confidence.MEDIUM,
|
||||
title = "Canary queries don't reach the authoritative server",
|
||||
description = "A per-run nonce name (which cannot be cached) was not answered by the canary server — the resolver is intercepting or failing to reach it.",
|
||||
evidenceRefs = listOf(EvidenceRef(t.id)),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (t.type == TestType.ICMP_PING6 && t.status == TestStatus.FAILED) {
|
||||
out.add(
|
||||
Finding(
|
||||
|
||||
Reference in New Issue
Block a user