docs(offsite): close §7 CGNAT open-validation; record mobile-path MTU finding

S3 appendix CGNAT smoke test run live on felhom-pve (agent v0.64.0) with a
USB-tethered phone. Tunnel established + held its NAT mapping across a 32-min
idle soak on PersistentKeepalive=25 alone (0 stalls, final ping no-bounce).

- §7: open-validation CLOSED; two honest caveats kept (SIM had a public mobile
  IPv4 so true CGNAT 100.64/10 not reproduced — retest-when-able; + MTU).
- §4.3: mobile outer path MTU ~1400 black-holed bulk data at the shipped 1420;
  MTU 1340 restored the PBS page. OPEN DECISION: permanent vs per-connection.
- §4.4: keepalive 32-min mobile-NAT hold datapoint.
- §4.2: dual-stack v6 trap (wg-quick prefers endpoint AAAA, rides un-NATed v6).
- §8: S6 row CGNAT closure marked done.
- REPORT.md: overwritten with this operational-validation summary.

Runbook: RUNBOOK-s3-cgnat-smoke. Docs-only; no agent/hub/controller code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-04 13:34:00 +02:00
parent 76ee25e5e8
commit 2028785f5e
2 changed files with 65 additions and 24 deletions
+30 -16
View File
@@ -4,22 +4,36 @@
---
# REPORT — S3 wrap-up (docs-only in this repo; the slice lives in felhom-agent v0.64.0) (2026-07-04)
# REPORT — S3 appendix: CGNAT smoke test (operational validation, docs-only in this repo) (2026-07-04)
felhom.eu carried **no code** in S3 — hub v0.33.0 (S2) already served the registration endpoint
+ merged `wireguard` block. This commit records the S3 outcome in the design of record:
Ran the deferred S3 appendix: the one open transport caveat in `06-offsite-connectivity.md` §7 —
does plain-WireGuard (box dials out + `PersistentKeepalive=25`) establish and **hold** a mapping
through a mobile-carrier NAT? Executed live on **felhom-pve** (`demo-felhom`, agent v0.64.0) with a
USB-tethered Xiaomi Mi 11i. No code touched; the only host changes were runtime `ip`/`dhclient`
one-shots, all reverted to the Phase-0 baseline at the end.
- `documentation/architecture/06-offsite-connectivity.md`: roadmap row **S3 → SHIPPED**
(agent v0.64.0; `wg_tunnel.enabled` defaults FALSE until the production endpoint exists);
§3.5 gains the **agent-side revocation semantics** (register-once marker; block-absent-from-
present-state = disable + marker kept + never self-re-register; re-add via
`POST /hosts/{id}/wg` with the global key — the raw registry add does NOT bump the host
generation, found live) and the rollout gate.
- `CONTEXT.md`: S3 entry (end-to-end handshake live on demo: keygen → register → block →
wg-quick → PBS page in 3 s; revocation drill + 30-min soak + reboot persistence).
**Method (surgical, SSH-safe):** kept the box on its wired default; steered **only** ep0's `/32`
out the phone so just the tunnel's outer UDP traversed the carrier NAT. Neutralized the phone's
RA-provided IPv6 + pinned ep0→v4 so the tunnel took the **v4/NAT** path (not un-NATed v6). 32-min
idle soak sampling handshake age every 30 s. Full restore verified against the captured baseline.
Full evidence: **felhom-agent REPORT.md** (journal excerpts, five red-proofs, the
hub-poll-constant gotcha, the re-add-path insight). Still open: CGNAT/mobile-hotspot smoke
(operator-assisted appendix), S4 PBS-over-tunnel, S5 DR consume, S6 tunnel-health alerting +
the two S6 backlog notes (make `defaultHostPollSeconds` configurable + log first-cycle adoption;
make the admin registry-add bump the bound owner or mark it registry-plumbing in the UI).
**Findings:**
- **Uplink:** phone got a **public mobile IPv4** (`84.225.188.131`, Magyar Telekom) — a real
single-NAT traversal (phone tether NAT), **not** true CGNAT `100.64/10`. Recorded as such.
- **Handshake/PBS:** established over the mobile NAT path (endpoint v4 `167.233.158.164:443`);
in-tunnel ping 4/4 ~57 ms; PBS login page reachable **after** the MTU fix.
- **MTU (actionable):** outer path MTU ~**1400 B** → shipped `wg-felhom` MTU 1420 **black-holed
bulk data** (ping/handshake healthy, PBS page empty). MTU **1340** (= 1400 60) restored it;
1360 failed. Recorded in §4.3 with an **OPEN DECISION**: permanent fleet-wide lower MTU vs
connection-type-dependent (1420 fixed-line / ≤1340 mobile, or MSS clamp).
- **Soak:** 64 samples, max handshake age 125 s (normal rekey), **zero stalls**, final ping after
32 min idle with no bounce → **mapping held on keepalive alone.**
- **v6 trap:** a dual-stack mobile uplink made `wg-quick` prefer the endpoint AAAA and ride
un-NATed IPv6 until v4 was forced (recorded in §4.2).
**Doc edits (this commit):** `06-offsite-connectivity.md` — §7 open-validation **CLOSED**;
§4.3 MTU mobile finding + open decision; §4.4 keepalive 32-min mobile hold; §4.2 dual-stack v6
trap; §8 S6 row updated. Runbook: `RUNBOOK-s3-cgnat-smoke`.
**Still open:** true-CGNAT-SIM retest (deferred, low risk — "when able"); the §4.3 MTU
permanent-vs-per-connection decision; S4 PBS-over-tunnel; S5 DR consume; S6 tunnel-health alerting.