Files
echolot/docs/build-status.md
T
mrambossekandClaude Opus 5 8a80026d49 server: Go skeleton — control plane, UDP data plane, Docker + systemd modes
Pure stdlib. Implements the spec's core: enrollment (single-use tokens),
profile (SPKI pin, only real capabilities advertised), sessions with the
§2.4 HKDF-SHA256 key schedule; UDP data plane with the 32-byte ELT1
header, 4-byte HMAC gate, 1024-wide anti-replay window, ECHO_RESP with
observation block, TIMESYNC, and the §3.4 anti-amplification cap. Wire
format has tests (roundtrip + silent-drop cases); enroll→profile→session
smoke-tested live.

Modes: container (autodetect /.dockerenv|/run/.containerenv|cgroup, or
--docker/ECHOLOT_DOCKER=1; config via ECHOLOT_* env; distroless image;
network_mode host required — Docker NAT would falsify observed sources)
and native (--install-systemd/--uninstall-systemd with a hardened unit,
opt-in --self-update from Gitea releases; refused in containers).

CI: tests on any server/ push; server-v* tags build+push the image to the
Gitea registry and attach linux amd64/arm64 binaries + SHA256SUMS to a
release — the artifact self-update consumes.

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

126 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Echolot — build status & next steps
Last updated: 2026-07-29.
## Decided
- Name **Echolot**; domain echo-lot.app; 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.
- Tiers: `app` (no root), **`shizuku` in v1** (wireless-ADB pairing), `root` future.
- **License (decided 2026-07-30):** all code **GPL-3.0-or-later**; the specs in `docs/` **CC-BY-4.0**.
Rationale: the moat is the no-root platform research, which is trivially liftable into a
proprietary repackage — copyleft is the only option that prevents that, and the F-Droid /
network-engineer audience reads GPL as a trust signal rather than friction. The specs go
permissive on purpose: a wire protocol only becomes a standard if anyone can implement it.
**Not AGPL for the server** — the ASP loophole it closes is speculative here, while blanket
corporate AGPL bans would hit exactly the enterprise network teams most likely to self-host.
Sole copyright holder, so relicensing the server to AGPL later stays possible.
Open: substitute a real legal copyright holder for "Echolot contributors" in the SPDX headers.
## Specs (in `docs/`, alongside this file)
- `feature-catalog-and-feasibility.md`, `measurement-schema.md`, `probe-protocol.md`. Considered draft-complete and reviewed by the user.
## Capability prober — DELIVERED (as zip, 2026-07-29)
Full Kotlin/Compose project scaffolded: `app.echo_lot.prober`, minSdk 26 / target+compile 35,
AGP 8.7.3, Kotlin 2.0.21, Compose BOM 2024.10, Shizuku api+provider 13.1.5, kotlinx-serialization.
Probes implemented: `link.snapshot`, `icmp.ping4/6` (unprivileged ICMP datagram), `sockopt.matrix`
(TTL/TOS/RECVERR/MTU_DISCOVER), `trace.errqueue_reachable`, `multinetwork.request_and_bind`,
`local.mdns_discover`, `peer.ble_advertise`, `shizuku.command_battery` (ip neigh / ip -6 route /
ip addr / ip monitor / dumpsys network_stack DHCP+IpClient / dumpsys wifi). JSON export via share
intent; results carry verdict + raw evidence.
Could NOT be compiled in the cloud sandbox: dl.google.com (Google Maven) and services.gradle.org
are proxy-blocked, and no device is reachable for on-device runs. Build + iterate locally
(Android Studio / Claude Code). Wrapper is pinned to Gradle 8.14.3.
## Website — `web/` (2026-07-30, LIVE at https://echo-lot.app)
Single-page site for echo-lot.app on Cloudflare Workers: static `public/` from the edge, a small
Worker for the stable short URLs (`/apk`, `/apk.sha256`, `/fdroid`, `/source`) and `/api/latest`.
Version info is resolved from the Gitea releases API at request time (edge-cached 5 min), so
tagging a release is the only publish step — the homepage shows the latest version/date/size via
progressive-enhancement JS and falls back to pre-release copy otherwise. Auto light/dark via
`prefers-color-scheme`: dark = sonar display, light = chart-recorder printout, same amber-accent
token system. Deploy: `npx wrangler deploy` from `web/`, or CI via
`.gitea/workflows/deploy-site.yml` (needs `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`
secrets); full story in `web/README.md`. Branding applied (2026-07-30): real favicon/wordmark
copied into `web/public/assets/` (copies — `assets/branding/` is the source of truth), palette
switched to abyss/teal/amber with teal-as-instrument semantics, sonar-sweep hero replaced by a
Focus-lattice panel (rogue-DHCP finding vignette), tagline "measure, don't guess".
## Branding — `assets/branding/` (2026-07-30)
Identity chosen: the **Focus** mark — a quiet lattice of nodes with exactly one under examination
(amber) inside teal viewfinder brackets. Tone is surgical measurement, not scanning (sonar-sweep
visuals and the "sound out your network" tagline were explicitly rejected); tagline is
**"measure, don't guess"**. Palette: abyss `#071522`, tile `#0E2433`, instrument teal `#35E0C4`,
finding amber `#FFB454`, foam `#E8F4F2` — teal is always the instrument, the single amber point is
the finding. The wordmark is hand-drawn monoline SVG paths (zero font dependency); the second "o"
is the signature teal ring + amber ping. Assets: `icon.svg` (+ `icon.png` 512² — Gitea avatars reject SVG), `icon-adaptive-{foreground,background}.svg`
(108dp adaptive-icon layers, art inside the 66dp safe circle), `wordmark-on-{dark,light}.svg`,
`banner.svg` (1200×300, wired into the repo README), `social-preview.svg`/`.png` (1280×640, for the
Gitea/GitHub social-preview slot). The website's placeholder favicon/wordmark can now be replaced
from these.
## First device report — OnePlus 15, Android 16/SDK 36 (2026-07-30)
Archived at `echolot-prober/reports/CPH2747-android16-sdk36.json`. Collection workflow that works:
deploy over (wireless) adb, but the **user** starts the run and exports the JSON manually — probe
runs reliably kill the adb-over-wifi session (multinetwork/multicast churn the link adb rides on).
OS findings:
- `link.snapshot`, `icmp.ping4` (38 ms), `sockopt.matrix` 4/4, `peer.ble_advertise`: SUPPORTED.
- **`trace.errqueue_reachable` SUPPORTED** — full Os-API errqueue path exists on Android 16;
the C-over-JNI shim (next-steps #2) may be unnecessary on modern devices. Still needs a real
errqueue round-trip test, and older devices may differ.
- `icmp.ping6` EAGAIN — topology, not capability: wifi was v4-only, v6 only on cellular, socket
used the default network. Probe should bind per-network (also covers the ping4 case properly).
- `shizuku.command_battery` SUPPORTED, shell(2000): real `ip -6 route` RA data (lifetimes,
per-table), `ip monitor` NEIGH events, wifi dump with full DhcpResults. Vendor formats look
parseable.
- `local.mdns_discover`: lock acquired, discovery ran, 0 services in the 4 s window — needs a
longer window / known-good reference network before calling it more than formally SUPPORTED.
Prober bugs the run exposed (all fixed same day):
- rotation wiped results + cancelled runs → state moved to a ViewModel;
- `multinetwork.request_and_bind` ERROR → missing `CHANGE_NETWORK_STATE` in the manifest;
- `rtt_ms: "38,1"` → device locale leaked into report numbers, now `Locale.ROOT`;
- 3/7 Shizuku commands `SHIZUKU_BIND_TIMEOUT` → bind/unbind-per-command raced Shizuku; now one
bind per battery (`execBatch`).
Run 2 (same device, fixed APK — `…-run2.json`) confirmed all three fixes:
- `multinetwork.request_and_bind` **SUPPORTED** — wifi + cellular acquired and bound concurrently
(downKbps 39094 / 13231); requesting cellular even brought up a second, dual-stack PDN
(`rmnet_data2`, private v4 /30 + carrier-NAT DNS) next to the IPv6-only default PDN — exactly
the per-network behavior the production app needs.
- Shizuku battery **7/7** in 2.9 s (was 4/7 in 34 s): full ARP/NDP neighbor table, `ip addr`,
IpClient ProvisioningConfiguration incl. SSID/BSSID and APF caps (v6000, 4096 B).
- `rtt_ms: "32.0"` — locale fix holds.
Still open (probe improvements, not blockers): ping6 should bind per-network (still EAGAIN via
v4-only default wifi); mDNS window too short to see real services (0 found on a network that has
them); `ip monitor` 2 s window caught nothing on a quiet net — consider longer/event-triggered
capture; IP_MTU read via getsockoptInt reflection unavailable — reader belongs in the errqueue
shim if that lands.
## Go server skeleton — `server/` (2026-07-30)
Pure-Go stdlib implementation of the spec's core: control plane (enroll with single-use tokens,
profile with SPKI pin, sessions with the §2.4 HKDF key schedule) + UDP data plane (ELT1 header,
HMAC gate, 1024-wide anti-replay, ECHO_RESP with observation block, TIMESYNC, §3.4
anti-amplification). Wire format covered by `go test` (roundtrip, replay/bad-HMAC/unknown-prefix
silent drops); full enroll→profile→session flow smoke-tested live. Two run modes: **container**
(autodetected via /.dockerenv etc. or `--docker`; config from `ECHOLOT_*` env; distroless image,
`network_mode: host` required — Docker NAT would falsify the observed sources the protocol
measures) and **native** (`--install-systemd`/`--uninstall-systemd`, opt-in `--self-update`
against a Gitea releases API). CI: `.gitea/workflows/build-server.yml` — tests on push;
`server-v*` tags push the image to the Gitea registry + attach linux amd64/arm64 binaries that
self-update consumes. Not yet: TCP/TLS echo, STUN, canary DNS, actions, observations API,
real admin UI, self-update signature verification.
Prober build numbering: ascending `versionCode` shown on screen + as `proberBuild` in the JSON
report; current deployed build = **2**. Bump on every deployed change.
## Next steps
1. Build locally, run on several physical devices (varied Android versions/vendors), collect the
JSON reports — especially the real per-device Shizuku dump formats.
2. If `trace.errqueue_reachable` = PARTIAL, add a C-over-JNI errqueue shim (`recvmsg`+cmsg parse)
as a `:native` module and a real `traceroute.udp4` probe.
3. Start the Go server skeleton (enrollment + profile + sessions + UDP echo with observation
blocks + canary-DNS reference records) per probe-protocol.md.
4. Fold confirmed capabilities into the production `core-probe` / `core-shizuku` modules.