8.2 KiB
FIPS Debug Notes: .fips HTTP reachability incident
Summary
We investigated why this URL failed locally but worked from the remote host:
http://npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud.fips/index.html
Outcome:
- Initial local failure was due to a stale
fd00::/8route pointing to an unreachable next-hop. - After route recovery, TCP connected but larger HTTP responses stalled.
- Root cause for the stall was path/MTU behavior (large payload transfer blackholing/retransmission).
- Setting local UDP transport MTU to
1280resolved the issue; full page download succeeded.
What we observed
1) DNS was correct
.fipsname resolved to the expected IPv6 address.- Name resolution was not the issue.
2) Endpoints were alive
- On remote host (
ubuntu@laantungir.net), the same URL returnedHTTP/1.1 200 OKwith full content. - Nginx was listening and serving properly.
3) Local routing was initially broken
- Local route had
fd00::/8via a link-local next-hop that was unreachable. - Neighbor state for that gateway showed
FAILED. - This produced local
No route to hosterrors.
4) After local route fix, only large responses failed
- Local curl connected to remote
:80and sent GET. - Small responses worked:
- direct IPv6 host request without vhost Host header got
410small body, - ranged request got
206with a small payload.
- direct IPv6 host request without vhost Host header got
- Full
index.html(10KB+) timed out with0 bytes receivedlocally. - Remote socket stats indicated retransmissions and congestion-window collapse, consistent with MTU/path fragmentation loss behavior.
5) Mitigation validated
- Local config changed:
transports:
udp:
bind_addr: 0.0.0.0:2121
mtu: 1280
- Restarted local
fips.service. - Retested URL:
HTTP 200, full10156bytes downloaded.
Lessons learned
-
Treat
.fipsfailures as a pipeline problem- Validate in this order: DNS → route → reachability → app response size behavior.
-
“Connect succeeds” does not prove path health
- A successful TCP handshake can still hide payload delivery failures.
- Always test both tiny and realistic/full payload responses.
-
Stale
fd00::/8routes can silently break TUN startup- We saw TUN init warnings when route insertion conflicted with existing state.
- Route hygiene matters before/after service restarts.
-
MTU defaults are not always safe on every real path
- Especially over mixed internet routes and overlays.
1280is often the practical “known-good” baseline for IPv6 paths.
-
Use control-plane telemetry early
fipsctl show status|peers|sessions|routingquickly distinguishes control-plane up/down from data-plane degradation.
-
Host-header/vhost behavior can mislead debugging
- Explicit
Host:header to the same IPv6 returned 200 remotely. - Direct IP without host header returned a different vhost response (410).
- This is expected and should be factored into tests.
- Explicit
Recommended runbook for future incidents
-
Reproduce quickly
curl -v --max-time 10 http://<npub>.fips/index.html
-
Check resolution and route
getent ahosts <npub>.fipsip -6 route get <resolved_ipv6>ip -6 neigh show
-
Check FIPS health
sudo /usr/local/bin/fipsctl show statussudo /usr/local/bin/fipsctl show peerssudo /usr/local/bin/fipsctl show sessions
-
Differentiate small vs large payload behavior
- Small: direct IP / tiny endpoint / range request
- Large: full page/object transfer
-
If large payloads fail but small succeed, test MTU mitigation
- Set
transports.udp.mtu: 1280locally - Restart
fips.service - Retest full payload
- Set
-
Persist fix and monitor
- Keep 1280 unless measured path supports higher values reliably.
- Track retransmission symptoms and transfer success over time.
Config change made during this debug
Local /etc/fips/fips.yaml was updated to include:
transports:
udp:
mtu: 1280
A backup of the prior local file was created before modification.
Final status
The target .fips web page is reachable from this local node after lowering local UDP MTU to 1280 and restarting fips.service.
Additional root cause discovered later (boot-time route collision)
A subsequent incident showed another failure mode that can present as the same curl: (7) Failed to connect symptom:
- A stale kernel route existed:
fd00::/8 via fe80::... dev eth0. - During startup,
fips.serviceattempted to installfd00::/8 dev fips0. - Kernel returned
EEXIST, so TUN initialization failed (tun_state: failed, nofips0). - Without TUN,
.fipsreachability failed even if service looked partially alive.
Key diagnostic indicators
sudo fipsctl show statusreports:tun_state: failed- missing/empty
tun_name
ip -6 route show fd00::/8points to non-fips0device (for exampleeth0)ip -o link show dev fips0fails
Immediate runtime recovery
sudo ip -6 route del fd00::/8
sudo systemctl restart fips.service
After recovery, verify:
tun_state: activefips0exists and is UPip -6 route show fd00::/8->dev fips0
Persistence/hardening
To make this robust across reboots and service restarts:
- Keep config MTU safeguards:
tun.mtu: 1280transports.udp.mtu: 1280
- Ensure
fips.serviceis enabled:sudo systemctl enable fips.service
- Add a systemd drop-in
ExecStartPrehook that removes stale non-fips0fd00::/8routes before daemon start.
This repository now includes deployment-script updates to install that hook automatically.
Latest incident addendum (Qubes inter-qube .fips debug)
New findings
-
Mesh + DNS can be healthy while app check still fails
- We observed successful
.fipsDNS resolution and ICMP reachability to the remote FIPS IPv6. - Yet HTTP checks still failed due to listener/bind mismatch.
- We observed successful
-
Qubes resolver path must include local dnsmasq for
.fipsdig @127.0.0.1 -p 5354 <npub>.fips AAAAworked whilegetent ahosts <npub>.fipsinitially failed.- Root cause:
/etc/resolv.conflackednameserver 127.0.0.1on the local qube. - Adding
127.0.0.1restored system resolver.fipslookups.
-
Persistence in Qubes requires
/rw/config/rc.localhygiene- Runtime fixes to
/etc/resolv.confare not enough. - Persisting the resolver injection in
/rw/config/rc.localis required for reboot durability. - We also discovered/remediated a stale legacy block (
# === AI FIPS ROUTE START ===) that forcedfd00::/8viaeth0; this must be removed to avoid breaking TUN routing on boot.
- Runtime fixes to
-
Remote service bind must match transport family used by
.fipspath- Remote python server bound to
0.0.0.0:80(IPv4) was not reachable via.fipsIPv6 destination. - Rebinding server to remote FIPS IPv6 (
fd56:...:982f) fixed HTTP connectivity immediately.
- Remote python server bound to
Practical runbook update for inter-qube checks
Add these checks before concluding “FIPS is broken”:
-
Resolver path sanity
getent ahosts <npub>.fips- If fail, check
/etc/resolv.confincludesnameserver 127.0.0.1 - Confirm dnsmasq forwards
/fips/to local FIPS DNS (127.0.0.1#5354or::1#5354)
-
Control/data plane split checks
- DNS AAAA success +
ping6 <npub>.fipssuccess proves overlay path exists - If HTTP fails, test TCP port explicitly (
/dev/tcporcurl -v -6)
- DNS AAAA success +
-
Listener-family verification on remote
ss -lntp | grep ':80 '- Ensure remote listener is IPv6-capable for
.fipsaccess
Script updates from this incident
test_qube_pair.sh- Added resolver hints when
.fipsname does not resolve - Added IPv6 curl (
curl -6) and configurable HTTP port viaREMOTE_HTTP_PORT - Added port probe diagnostics on HTTP failure
- Added resolver hints when
test_fips.sh- Added
/etc/resolv.confsanity reporting fornameserver 127.0.0.1
- Added
update_and_deploy_fips.sh- Persisted rc.local network overrides
- Added cleanup of legacy
# === AI FIPS ROUTE START/END ===block when rewriting rc.local
start_py_http80.sh- Binds to detected local FIPS IPv6 by default (instead of IPv4-only bind)