Commit Graph
83 Commits
Author SHA1 Message Date
mrambossekandClaude Opus 5 4ae744aae5 server: self-test — sysctl audit + egress-MTU self-proof ("server proven good")
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 27s
server-release / release (push) Successful in 28s
A measurement server must prove its own host isn't distorting results:
- sysctl audit (/proc/sys): flags accept_ra on a static host, ICMP
  redirects, ICMP rate-limiting of the server's own errors, and disabled
  TCP options — each a measurement-fidelity hazard, with the "why".
- egress-MTU self-proof: DF PMTUD probe (IP_MTU_DISCOVER + getsockopt
  IP_MTU, no root — Linux-only, stub elsewhere) to external anchors. If the
  server's own uplink is below 1500, client MTU tests measure THIS server,
  so we say so.
Exposed at GET /admin/selftest (full report) and as server_selftest
{mtu_ok, sysctl_ok} in the profile so clients can trust or skip MTU tests.
Recommended deploy/99-echolot-sysctl.conf + README section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
server-v0.3.4
2026-07-31 20:44:38 +02:00
mrambossekandClaude Opus 5 c9e0d06ea2 server: MTU probe (MTU_PROBE/MTU_ACK) — path-MTU / black-hole measurement
server-release / image (push) Successful in 14s
server-test / test (push) Successful in 27s
server-release / release (push) Successful in 27s
Server ACKs each DF-flagged probe with a tiny MTU_ACK carrying the size it
received; the client binary-searches the path MTU. Non-amplifying by
construction. Tested.

Also records: v0.3.2 (http-echo + tls-reference) verified live on fmr, and
the finding that upstream trains are already observable via the
observations API (dedicated TRAIN_REPORT deferred — needs an
anti-amplification grant + columnar encoding).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
server-v0.3.3
2026-07-31 20:37:20 +02:00
mrambossekandClaude Opus 5 38fb73c34e server: HTTP echo + TLS reference (control-plane security measurements)
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 27s
server-release / release (push) Successful in 28s
- POST /v1/echo: returns the received request head + body (base64) and the
  observed TLS parameters (version, cipher, SNI, ALPN, resumed). The client
  diffs against what it sent to detect header injection/stripping,
  transparent proxying, or TLS interception (sec.http_echo). http-echo
  added to the capability set.
- GET /v1/tls-reference: the served leaf-first DER chain + pin, so the app
  can compare an out-of-band copy against its own handshake (sec.tls_reference).
  Always available, no auth — public handshake info.
- Optional CLEARTEXT http-echo listener (ECHOLOT_HTTP_ECHO_LISTEN, default
  off) exposing only /v1/echo for the plaintext-path tampering test.

Live-smoke-tested (HTTPS echo reflected an injected header + observed
TLS1.3; cleartext variant reports tls:none); httptest unit tests added.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
server-v0.3.2
2026-07-31 20:34:03 +02:00
mrambossekandClaude Opus 5 379153219e build-status: canary DNS live on fmr — session attribution + 0x20 finding
Zone delegated + authoritative, verified via public recursion; per-session
nonce queries attributed in the observations API. First test caught
Google's 0x20 case randomization vs Cloudflare's plain case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 20:18:50 +02:00
mrambossekandClaude Opus 5 35baf70cdb server: canary DNS — authoritative zone with frozen §6.1 reference records
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 26s
server-release / release (push) Successful in 27s
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>
server-v0.3.1
2026-07-31 20:08:30 +02:00
mrambossekandClaude Opus 5 4f5499198b build-status: server v0.3.0 live on fmr — STUN/TCP-echo/observations/actions verified
Deployed via self-update (first real run). External checks: stun-5780
advertised, STUN binding OK v4+v6 with OTHER-ADDRESS, TCP echo mss=1440
over IPv6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 19:56:22 +02:00
mrambossekandClaude Opus 5 7b676e666e server: STUN, TCP echo, observations API, delayed-echo + connect-back actions
server-test / test (push) Successful in 27s
server-release / image (push) Successful in 14s
server-release / release (push) Successful in 27s
- stun: RFC 5389 binding responder + RFC 5780 attributes (OTHER-ADDRESS,
  RESPONSE-ORIGIN, CHANGE-REQUEST) on a primary/alt-port socket grid per
  address; advertises stun-5780 with >=2 same-family addrs, else
  stun-basic. Unmodified framing for tooling interop. Tested.
- tcpecho: JSON greeting with observed src + TCP_INFO MSS/options
  (Linux getsockopt; zeroed elsewhere via build tags), then byte echo.
- session: per-packet UDP observations + connect-back results, ByID lookup.
- control: GET /v1/sessions/{id}/observations, POST .../actions
  (delayed_echo → DELAYED_ECHO at the observed data-plane source;
  connect_back → dial the control-plane source, record connected/refused/
  timeout+rtt). Capabilities computed from what is actually wired.
- config/main: comma-separated STUN listeners; all planes bind explicit
  addresses; graceful shutdown of the new listeners.

Full flow smoke-tested; go test green (stun binding/change-port,
dataplane wire format).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
server-v0.3.0
2026-07-31 19:53:36 +02:00
mrambossekandClaude Opus 5 507a8bfc1f build-status: production server v0.2.0 live on the fmr VM
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 19:42:56 +02:00
mrambossekandClaude Opus 5 43e1ba778a server: multi-address listeners, env-file config, self-update timer + checksums
server-test / test (push) Successful in 24s
server-release / image (push) Successful in 5s
server-release / release (push) Successful in 26s
- Comma-separated ECHOLOT_{CONTROL,UDP,TCP}_LISTEN; one listener/socket per
  address. Explicit binds matter on multi-IP hosts (a wildcard would also
  claim the SSH-only management address) and per-address UDP sockets are
  the substrate stun-5780 needs.
- systemd unit reads /etc/echolot-server.env (seeded once, never
  overwritten); --install-systemd with --self-update-api also installs a
  daily randomized update timer that try-restarts the service.
- selfupdate: SHA256SUMS verification is now mandatory before the atomic
  replace (integrity, not authenticity — signing still TODO).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
server-v0.2.0
2026-07-31 19:40:36 +02:00
Markus RambossekandClaude Opus 5 4de3064f71 reports: build-5 runs on both devices — new toolchain is behaviour-neutral
TB330FU (A15/SDK35) and CPH2747 (A16/SDK36) both re-run on the AGP 9.2 /
Gradle 9.6 / Kotlin 2.2.10 / JDK 25 / targetSdk 36 toolchain. Every probe on
both devices keeps the verdict it had on the previous build (tablet vs build-4,
phone vs build-3), so the upgrade changed no observed capability.

Dual-path Shizuku executor now confirmed on the current toolchain on both:
phone exec_path=UserService, tablet exec_path=newProcess fallback, both 7/7.

Collection-loop gotchas recorded in build-status.md, found while driving the
phone over USB:

- icmp.ping6's verdict is topology-dependent, not build-dependent. A run with
  only 2 networks up reported UNSUPPORTED; with cellular present it is the
  expected "echo reply on cellular only". Always read it against
  link.snapshot's network_count from the same report.
- The Shizuku permission dialog times out after 30 s and the probe logs
  "permission not granted" at ~30027 ms. Grant it before starting a run.
- uiautomator dump can report a stale idle state right after a tap; the
  exported JSON's durationMs values are the ground truth for completion.

CLAUDE.md's collection loop is updated — over USB the run is fully driveable
via am start / input tap / run-as cat, no manual export needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:35:53 +02:00
Markus RambossekandClaude Opus 5 edcad3e539 prober: AGP 9.2 / Gradle 9.6 / Kotlin 2.2.10, targetSdk 36, build 5
Lifts the build off the JDK-21 ceiling — the old AGP 8.7.3 pin could not run
on JDK 25. Two AGP-9 migration edits were required:

- AGP 9 ships built-in Kotlin support, so applying org.jetbrains.kotlin.android
  alongside it fails with "extension with name kotlin already registered";
  the alias is dropped (kotlin.compose / kotlin.serialization stay, they are
  separate compiler plugins).
- kotlinOptions { jvmTarget } came from that plugin and no longer resolves;
  AGP derives jvmTarget from compileOptions instead.

compileSdk/targetSdk 35 -> 36, since Android Studio ships API 36.1 by default
and installing 35 was avoidable churn. versionCode 5 so the reports from this
toolchain are attributable (build 4 was already archived from the old one).

Also fixes a missing comma that left .claude/settings.json invalid JSON.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:34:54 +02:00
mrambossekandClaude Opus 5 cae3c00990 server: document deployment requirements — no proxy, no 80/443, and why
server-test / test (push) Successful in 24s
Ports table, host-IP/second-IP/canary-zone requirements, and the two
protocol properties (SPKI pinning, observed-source fidelity) that a
reverse proxy would break. Coexists with traefik by never touching its
ports.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:54:52 +02:00
mrambossekandClaude Opus 5 b7658a6777 server: selfupdate must normalize namespaced tags; build-status: pipeline green
server-test / test (push) Successful in 24s
Release tags are server-v1.2.3 but binaries are stamped v1.2.3 — the raw
comparison would re-download the current version on every check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:38:08 +02:00
mrambossekandClaude Opus 5 ebcb012450 ci: registry login via REGISTRY_TOKEN secret — Actions token is rejected
server-release / image (push) Successful in 25s
server-release / release (push) Successful in 26s
docker was present on the echolot runner; the login failed because
Gitea's container registry does not accept the ephemeral Actions token.
Requires a PAT with package read/write scope as the REGISTRY_TOKEN repo
secret (REGISTRY_USER optional, defaults to the actor); fails with a
self-explanatory error when missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
server-v0.1.0
2026-07-30 16:33:01 +02:00
mrambossekandClaude Opus 5 234622549c ci: image job on the dedicated 'echolot' runner; public registry host default
server-release / image (push) Failing after 4s
server-release / release (push) Successful in 26s
compilesau-echolot (repo runner, label echolot) handles the docker image
build. Registry host defaults to git.rambossek.at (runner-internal
GITHUB_SERVER_URL is http://app:3000). Asset re-uploads on tag re-runs
warn instead of failing the completed release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:29:27 +02:00
mrambossekandClaude Opus 5 5f258a38b7 build-status: server-v0.1.0 shipped with assets; only the image job remains
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:17:16 +02:00
mrambossekandClaude Opus 5 33db502a76 ci: fix release-id extraction (greedy sed grabbed the wrong id) + re-run safety
server-release / release (push) Successful in 57s
server-release / image (push) Failing after 5s
The job log showed uploads 404ing: sed's greedy .* matched the LAST "id"
in the release JSON (a nested user id), not the release's. First-match
grep now. Release creation falls back to GET-by-tag on re-runs, and the
image job strips any scheme from the internal server URL (http://app:3000)
with an ECHOLOT_REGISTRY_HOST override for the public registry host.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:15:46 +02:00
mrambossekandClaude Opus 5 725443c72c build-status: record pipeline shakeout state for the next session
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:02:21 +02:00
mrambossekandClaude Opus 5 8d15a318fb ci: split server release into independent binaries and image jobs
server-release / release (push) Failing after 40s
server-release / image (push) Failing after 5s
First tag run failed as one monolithic job. Binaries + Gitea release now
need only Go and curl (jq dropped too) and succeed on any runner; the
docker image build is a separate job that can fail without killing the
release self-update depends on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:00:23 +02:00
mrambossekandClaude Opus 5 9d4b41da7f CLAUDE.md: device-testing lessons for future sessions; ignore web/.wrangler
server-release / release (push) Failing after 41s
The wireless-adb findings, report-collection workflow, build-number
convention, per-device Shizuku matrix, and the JDK constraint now live in
the repo so a session on any machine starts with them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 15:43:32 +02:00
mrambossekandClaude Opus 5 0c988df7e8 ci: split server test and release workflows — mixed paths+tags never fired
server-test / test (push) Successful in 1m3s
A push trigger that declares tags: stops matching branch pushes, and the
paths filter doesn't apply to tag pushes — so the combined workflow could
effectively never run. test-server.yml now covers branch pushes touching
server/; build-server.yml is tags-only (server-v*).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 15:41:25 +02:00
mrambossekandClaude Opus 5 676a4d1eb8 prober: build-4 tablet report — newProcess fallback works where UserService won't
exec_path confirms the Lenovo/A15 ran the whole battery via the legacy
Shizuku.newProcess path: 6/7 commands with real data, incl. Lenovo's
IpClient dump format (distinct from OnePlus's — the per-vendor parser
sample the prober exists to collect). Design consequence recorded:
core-shizuku needs a dual-path executor. ip_monitor times out under
newProcess; bind-wait shortening noted as future tuning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 15:34:10 +02:00
mrambossekandClaude Opus 5 60989b67ef prober build 4: newProcess fallback when the Shizuku UserService won't bind
Build 3 settled it: the Lenovo TB330FU/A15 never spawns the UserService
(two 25s bind attempts, binder alive, permission granted). Build 4 falls
back to the legacy Shizuku.newProcess remote-process API via reflection
and records exec_path in the evidence — whether that path works per
device is itself the capability question core-shizuku needs answered.

Also archives both build-3 reports: phone 7/7 incl. provoked NEIGH
transitions in ip_monitor; traceroute.udp4 handled a silent hop ("*")
correctly on both devices.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 15:10:58 +02:00
mrambossekandClaude Opus 5 0ed36d9103 prober build 3: raise Shizuku bind window for slow first spawn; archive build-2 reports
Build-2 matrix (OnePlus 15/A16 + Lenovo TB330FU/A15) delivered the big
answer: traceroute.udp4 reads real hops via pure-Kotlin MSG_ERRQUEUE on
BOTH devices — the C-over-JNI shim is dead for Android 15+. Per-network
ICMP works as designed (phone ping6: cellular-only with topology
evidence). mDNS meta-query confirmed broken on both devices while
concrete types find real services — production must enumerate types.
Network finding: hudeWLAN RAs advertise a v6 default route but no global
prefix — the LAN's IPv6 is half-broken (nice dogfood case).

Tablet Shizuku bound 0/7 (binder alive, permission granted): first-spawn
dex extraction on slow storage suspected — bind window now 25s + one
retry. versionCode 3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 13:32:11 +02:00
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
mrambossekandClaude Opus 5 ee66648e3c prober: on-screen + in-report build number (versionCode, now 2)
Ascending integer bumped per deployed change; shown in the header and as
proberBuild in the JSON report so a report is attributable to an exact
build. buildConfig feature enabled for BuildConfig.VERSION_CODE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 13:03:50 +02:00
mrambossekandClaude Opus 5 e695fe7aee prober: per-network ICMP, real errqueue traceroute, sharper mDNS/ip-monitor
- IcmpProbe: attempt the echo on the default network AND each active
  network (Network.bindSocket + per-network DNS). Run 1's ping6 EAGAIN was
  topology (v6 only on cellular), not capability — now the report shows
  which networks carry which family instead of a bare ERROR.
- New traceroute.udp4 (TracerouteProbe): actual UDP traceroute reading
  ICMP time-exceeded via Os.recvmsg(MSG_ERRQUEUE) with cmsg parsing —
  reflection per repo convention (API surface exists ~34+). If this
  returns hops, the C-over-JNI shim is unnecessary on that device.
  sock_extended_err layout constants documented in OsAbi.
- MulticastProbe: 10 s window; meta-query PLUS concrete types
  (_http._tcp, _googlecast._tcp) — run 1 showed the meta-query alone
  returning 0 on a network with live services; capture names + failure
  codes as evidence.
- ShizukuProbe: ip monitor window 2s -> 5s, gateway ping in background to
  provoke a NEIGH transition instead of hoping for ambient churn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 12:59:43 +02:00
mrambossekandClaude Opus 5 81d13ebb08 prober: archive run 2 — all three report-driven fixes verified on device
multinetwork now SUPPORTED (wifi+cellular bound concurrently; the cellular
request surfaced a second dual-stack PDN), Shizuku battery 7/7 in 2.9s
(was 4/7 in 34s), rtt_ms locale-clean. Remaining probe improvements
(per-network ping6, longer mDNS/ip-monitor windows) noted in build-status.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 12:50:13 +02:00
mrambossekandClaude Opus 5 4c61c9bba8 prober: fix three bugs exposed by the first device report (OnePlus 15/A16)
- AndroidManifest: add CHANGE_NETWORK_STATE — requestNetwork threw
  SecurityException, multinetwork.request_and_bind could never run.
- IcmpProbe: format rtt_ms with Locale.ROOT — Austrian locale produced
  "38,1" in the JSON report.
- ShizukuRunner/ShizukuProbe: bind the UserService once per battery
  (execBatch) instead of per command; the per-command bind/unbind raced
  Shizuku and 3/7 commands died on SHIZUKU_BIND_TIMEOUT.

Archive the report at echolot-prober/reports/, record findings in
build-status.md. Notable: errqueue path fully reachable on Android 16 —
the native shim may be unnecessary on modern devices.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:19:14 +02:00
mrambossekandClaude Opus 5 6c29d0c039 prober: survive configuration changes (rotation wiped a finished run)
Found on the OnePlus 15 (CPH2747): rotating after a run emptied the result
list — results lived in a MainActivity field and the run in lifecycleScope,
so a config change dropped collected results and cancelled remaining probes.
Both now live in ProberViewModel (viewModelScope + application context; no
probe needs an Activity). A lost run means a lost export, so this matters
for a collection tool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:55:32 +02:00
mrambossekandClaude Opus 5 d3e35ecead Add branding: Focus mark, wordmark, banner, social preview
- assets/branding/: icon (SVG + 512px PNG for Gitea avatar), adaptive-icon
  foreground/background layers, path-only wordmark for dark/light grounds,
  1200x300 README banner, 1280x640 social preview (SVG + PNG)
- README: banner hero
- build-status: branding decision log (includes the pending website section
  from the parallel web/ session, interleaved in the same file)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 10:52:12 +02:00
mrambossekandClaude Opus 5 6ddf013dfe License the project; add root README, website, and parked release CI
- Code: GPL-3.0-or-later (LICENSE, SPDX headers on all .kt/.aidl).
  Specs in docs/: CC-BY-4.0 (docs/LICENSE). Rationale in build-status.md;
  server decided GPL (not AGPL).
- Root README for the public repo.
- web/: Cloudflare Worker site for echo-lot.app. /apk resolves the newest
  APK from the Gitea latest-release API at request time (edge-cached 5 min),
  so tagging a release is the only publish step. /fdroid, /source, and a
  manual DOWNLOAD_URL fallback are wrangler vars.
- .gitea/workflows/release.yml: tag-driven (v*) signed semver APK builds for
  the future production app in echolot-app/. Parked; the prober is
  deliberately not CI-built.
- Fix UserService.kt: drop the explicit secondary constructor that
  conflicted with the implicit primary (never compiled before — first
  local build caught it). Prober now builds: :app:assembleDebug OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:15:35 +02:00
mrambossekandClaude Opus 5 e3840f54fe Initial commit: capability prober + design docs
Monorepo root for Echolot. Contains the no-root capability prober
(Kotlin/Compose, app.echo_lot.prober) and the four design docs that act
as the contract for the production app and the Go server.

LICENSE is deliberately absent — still undecided, see docs/build-status.md.

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