app: net.captive_portal probe — reproduce Android's internet/portal checks
Mirrors NetworkMonitor: per active network, fetch the AOSP default generate_204 endpoints and check for HTTP 204 No Content. - HTTPS https://www.google.com/generate_204 == 204 -> validated internet - HTTP http://connectivitycheck.gstatic.com/generate_204: 204 -> clean; an unfollowed 3xx or a 200-with-body -> captive portal (Location captured) - both fail -> no_internet Per-network verdicts (bound via Network.openConnection), redirects not followed (the 3xx IS the evidence). Findings: captive_portal (medium) and no_internet (high). New test type net.captive_portal (net family -> connectivity category). App gains usesCleartextTraffic (a network diagnostic that intentionally probes plain HTTP). Builds; measurement verdict tests still green. On-device verification deferred with the rest (flaky test devices). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
aaed22dd3f
commit
6e269d424b
@@ -15,6 +15,7 @@
|
||||
android:allowBackup="false"
|
||||
android:label="Echolot"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@style/Theme.Echolot">
|
||||
|
||||
<activity
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.echo_lot.measurement.*
|
||||
import app.echo_lot.probe.CaptivePortalProbe
|
||||
import app.echo_lot.probe.IcmpProbe
|
||||
import app.echo_lot.probe.LinkSnapshotProbe
|
||||
import app.echo_lot.probe.NetworkInventory
|
||||
@@ -69,6 +70,7 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
||||
LinkSnapshotProbe(entries),
|
||||
IcmpProbe(entries, v6 = false),
|
||||
IcmpProbe(entries, v6 = true),
|
||||
CaptivePortalProbe(entries),
|
||||
)
|
||||
|
||||
val tests = ArrayList<Test>()
|
||||
@@ -132,6 +134,29 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
||||
val out = ArrayList<Finding>()
|
||||
val ids = RunIds()
|
||||
for (t in tests) {
|
||||
if (t.type == TestType.NET_CAPTIVE_PORTAL) {
|
||||
val ev = t.evidence?.toString() ?: ""
|
||||
when {
|
||||
ev.contains("\"captive_portal\"") -> out.add(
|
||||
Finding(
|
||||
id = ids.uuid(), code = "connectivity.captive_portal", category = Category.CONNECTIVITY,
|
||||
severity = Severity.MEDIUM, confidence = Confidence.HIGH,
|
||||
title = "Captive portal intercepting connections",
|
||||
description = "The generate_204 check returned a redirect or a page instead of HTTP 204 — a captive portal (login/splash page) is intercepting traffic on this network.",
|
||||
evidenceRefs = listOf(EvidenceRef(t.id)),
|
||||
)
|
||||
)
|
||||
t.status == TestStatus.FAILED -> out.add(
|
||||
Finding(
|
||||
id = ids.uuid(), code = "connectivity.no_internet", category = Category.CONNECTIVITY,
|
||||
severity = Severity.HIGH, confidence = Confidence.HIGH,
|
||||
title = "No working internet on any network",
|
||||
description = "Android's own generate_204 connectivity checks failed on every active network (no HTTP 204) — this device has no validated internet path.",
|
||||
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