server: reserve measurement addresses; serve the UI on both families
fmr keeps .151/::151 for measurement. Their diagnostic value is entirely in their listening state being known: a TLS handshake that completes on a port nothing listens on proves interception, with no competing explanation. One stray bind turns that proof into a shrug, and nothing about the failure is visible — the run still says the network is clean. CheckReserved refuses to start when a listener would take one. Wildcards are refused outright, because that is how this actually happens: every listener defaults to ":port" and the next one added gets copied from an existing default, claiming every address without anyone deciding to. Reserved is not silent, though. The first version of the guard would have refused the live config's UDP and canary-DNS binds on .151, which are deliberate — as is STUN's RFC 5780 alternate. Reserving an address and then forbidding the measurements that need it defeats the purpose. The rule is narrower: no services, and never ports 80 or 443. The adb-beacon receiver was wildcard-bound to 0.0.0.0:443, holding port 443 on every IPv4 address including the reserved one, so the IPv4 interception test had been compromised for as long as it had run. It is disabled; restore with systemctl enable --now echolot-adb-beacon. This also marks the guard's limit: it governs this server's listeners, and a process outside its config can still pollute a reserved address. The admin UI and ACME responder were single-address, which is why the UI could only live on ::2 and why the server was reachable over IPv6 alone — the thing that made it look nonexistent from a phone without working IPv6. Both now take address lists like every other listener. Verified from outside: .150/::150/::2 answer on 443 with a valid cert for fmr.echo-lot.app, .151/::151 are closed on 80 and 443, and canary DNS is still up on .151. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d9ee8bc2ae
commit
5b5a54db7d
@@ -1127,3 +1127,39 @@ active"), to measure the tunnel itself as the network under test, or both. Not y
|
||||
Related: `icmp.ping6` now records `attempted` alongside `ok` per network, because collapsing them
|
||||
made the app report "IPv6 is configured, but ICMPv6 gets no reply" about an interface it had never
|
||||
succeeded in sending on — a claim about the user's carrier with no evidence behind it.
|
||||
|
||||
## Reserved measurement addresses, and the web UI on both families (2026-08-01)
|
||||
|
||||
fmr has two IPv4 (.150/.151) and three IPv6 (::150/::151/::2) addresses. `.150`/`::150` now carry
|
||||
the services; `.151`/`::151` are reserved for measurement, declared in `ECHOLOT_RESERVED_ADDRS`.
|
||||
|
||||
Reserved does **not** mean silent. The UDP data plane, the canary DNS and STUN's RFC 5780 alternate
|
||||
all belong there — reserving an address and then forbidding the measurements that need it would
|
||||
defeat the purpose. What must never appear is a service, and above all not ports 80 or 443: a
|
||||
handshake completing on a port known not to be listening is what proves interception, and that
|
||||
proof survives exactly as long as nothing binds those ports. `config.CheckReserved` enforces it at
|
||||
startup, refusing wildcard binds outright (every listener defaults to `:port`, so the next one added
|
||||
will claim reserved addresses without anyone deciding to).
|
||||
|
||||
The first version of the guard was too strict and the live config caught it: it would have refused
|
||||
the existing UDP and DNS binds on `.151`. The rule is about services and web ports, not about
|
||||
listening at all.
|
||||
|
||||
**The adb-beacon receiver was wildcard-bound to `0.0.0.0:443`**, occupying port 443 on every IPv4
|
||||
address including the reserved one — so the IPv4 interception test had been compromised for as long
|
||||
as it had been running, silently. It is now `systemctl disable --now echolot-adb-beacon`; restore
|
||||
with `systemctl enable --now`. Note what this implies: the guard covers this server's own listeners,
|
||||
and a stray process outside its config can still pollute a reserved address. A startup probe that
|
||||
*verifies* 80/443 are actually free on the reserved addresses would be a stronger guarantee than
|
||||
checking our own configuration, and is not yet built.
|
||||
|
||||
The admin UI and the ACME responder now take comma-separated addresses like every other listener;
|
||||
they were single-address, which is why the UI could only ever live on `::2`. It serves on
|
||||
`.150:443`, `[::150]:443` and `[::2]:443` — `::2` retained until `fmr.echo-lot.app` becomes a CNAME
|
||||
to `fmr-1` (`.150`/`::150`), since dropping it first would break both the UI and ACME renewal for
|
||||
the very name the certificate is issued to. sshd likewise now listens on `.150`, `::150` **and**
|
||||
`::2`, added rather than moved for the same reason.
|
||||
|
||||
The point of all this: `fmr.echo-lot.app` gains an A record, so the server stops being reachable
|
||||
only over IPv6 — which is what made it unreachable from a phone with no working IPv6, presenting as
|
||||
"this host does not exist" in two different browsers.
|
||||
|
||||
Reference in New Issue
Block a user