Files
echolot/CLAUDE.md
T
mrambossekandClaude Opus 5 35baf70cdb
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 26s
server-release / release (push) Successful in 27s
server: canary DNS — authoritative zone with frozen §6.1 reference records
Stdlib DNS responder (no external deps): parses single-question queries
with EDNS OPT (bufsize, DO, ECS), serves the spec's frozen reference
records (ttl-{5,60,3600,86400} A/AAAA/TXT, many-rr 8×A in order, big-txt
~1800B), and per-query <nonce>.<session>.<zone> answers in 192.0.2.0/24.
UDP truncation sets TC past 512 (or the EDNS bufsize); TCP never
truncates — the EDNS-bufsize / TCP-fallback test. Every query is logged
(qname, resolver, transport, EDNS, ECS, case) and surfaced per session
prefix in GET /v1/sessions/{id}/observations as dns_canary. Profile gains
canary_zone + the canary-dns capability when configured.

Wire format validated against an independent client (correct rcodes,
answer counts, TC behavior, full EDNS response); unit tests cover
references, truncation-vs-EDNS, logging, NXDOMAIN.

Versioning: patch-first convention recorded in CLAUDE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 20:08:30 +02:00

5.6 KiB

Echolot — context for Claude Code

This repo is the capability prober for the Echolot project: an F/OSS Android app for detecting and debugging local network issues (target audience: network engineers). The prober validates the no-root feasibility matrix on real hardware before the production app is built.

Project facts

  • Name: Echolot. Domain echo-lot.app. URI scheme echolot://. Namespace app.echo_lot.* (hyphen→underscore; appIds/packages can't contain hyphens). Prober appId: app.echo_lot.prober.
  • Stack: native Kotlin + Jetpack Compose. No Flutter (all value is in platform APIs; Flutter would be a Dart skin over a full Kotlin app + F-Droid build friction).
  • Tiers: app (no root), shizuku (ADB-shell privileges via wireless pairing, shipped in v1), root (future module). Every result records which tier produced it.
  • License: code is GPL-3.0-or-later (LICENSE), the specs in docs/ are CC-BY-4.0 (docs/LICENSE). New source files get the two-line SPDX header the existing ones carry. The server, when it lands, is GPL too — not AGPL (see docs/build-status.md for the reasoning).

Design docs (source of truth, in docs/)

  • docs/feature-catalog-and-feasibility.md — full feature list + no-root feasibility matrix.
  • docs/measurement-schema.md — the archived/exportable measurement JSON format (observation vs finding separation, two-clock rule, columnar trains, anonymization types).
  • docs/probe-protocol.md — client↔server wire protocol (pinned-TLS control plane, binary UDP data plane with anti-amplification HMAC, STUN, canary DNS reference records).
  • docs/build-status.md — running log of decisions, what is delivered, and the next steps.

The three specs are draft-complete and user-reviewed; treat them as the contract. build-status.md is the mutable one — update it as work lands.

Keep prober result IDs aligned with the measurement-schema test-type registry.

Versioning

Prefer patch bumps (server-v0.3.1) for additive/incremental work; reserve minor bumps for real milestones. Don't burn through minor versions. Tags are namespaced: server-v* for the Go server, v* for the app. Pushing a server-v* tag runs CI → binaries + Gitea release + registry image; the server on fmr can --self-update from those releases.

Layout

Monorepo. The prober is one deliverable; the Go server and the production app land as siblings.

docs/                          design docs (above) — apply repo-wide, not just to the prober
echolot-prober/                the capability prober (self-contained Gradle build)
  app/src/main/java/app/echo_lot/prober/
    MainActivity.kt            Compose host, runs probes sequentially
    probe/                     Probe interface + all app-tier probes + OS ABI helper
    shizuku/                   AIDL UserService + ShizukuRunner (shell command exec)
    export/Report.kt           JSON report + share intent
    ui/ProberScreen.kt         result cards colored by verdict

Conventions

  • Every probe returns a ProbeResult — never throws to the caller (MainActivity wraps anyway).
  • Uncertain OS API paths (getsockoptInt, recvmsg, nat64Prefix) are reached via reflection/runCatching and reported, not assumed — the prober's purpose is to discover what exists.
  • Hardcoded Linux sockopt ABI numbers live in OsAbi.kt with rationale; do not "fix" them to OsConstants names that don't exist.

Build

From echolot-prober/: ./gradlew :app:assembleDebug (needs local Android SDK; set sdk.dir in echolot-prober/local.properties). The Gradle build is rooted there, not at the repo root. First build downloads AGP/Compose/Shizuku from Google Maven + Maven Central.

Working with test devices (hard-won lessons)

  • Prefer USB for adb. Wireless debugging dies constantly: probe runs churn the wifi the debug link rides on, the Lenovo tablet's ZUI power management kills the listener anyway, and the port rotates on every restart. Never drive a probe run over wireless adb.
  • Collection loop over USB, fully driveable by Claude: install → am start → tap "Run all probes" → poll uiautomator dump until the button label returns to "Run all probes" → tap "Export JSON" → the report lands in cache/reports/ and comes back via adb shell run-as app.echo_lot.prober cat … (no need to drive the share sheet; dismiss it with BACK). Archive under echolot-prober/reports/, record findings in docs/build-status.md.
  • Bash-tool adb shell calls with absolute device paths get mangled by Git Bash path conversion (/data/…C:/Program Files/Git/data/…); use the PowerShell tool for those.
  • Bump versionCode on every deployed prober change — it shows on screen and as proberBuild in the report; that's how a report is matched to a build.
  • Known devices: OnePlus 15 (CPH2747, A16) — Shizuku UserService works; Lenovo TB330FU (A15, multi-user) — UserService never binds, the newProcess fallback carries it. Full history in build-status.md.
  • As of the AGP 9.2.0 / Gradle 9.6.0 / Kotlin 2.2.10 bump, JDK 17+ (including 25) works — AGP 9 requires Gradle 9.1.0+ and Kotlin 2.2.10+ as its minimum KGP version. On machines with Android Studio, its jbr directory works as JAVA_HOME.

Likely next steps

  1. Run on physical devices; collect JSON reports across Android versions/vendors.
  2. C-over-JNI errqueue shim — not needed; trace.errqueue_reachable and traceroute.udp4 are SUPPORTED on both known devices via Os.recvmsg + StructMsghdr reflection.
  3. Fold the confirmed capabilities + Shizuku dump-format samples back into the production core-probe / core-shizuku modules.