Skip to content

daemon: inbound-path watchdog — auto-recover the long-uptime NAT wedge#369

Merged
TeoSlayer merged 3 commits into
mainfrom
feat/rx-watchdog
Jul 13, 2026
Merged

daemon: inbound-path watchdog — auto-recover the long-uptime NAT wedge#369
TeoSlayer merged 3 commits into
mainfrom
feat/rx-watchdog

Conversation

@TeoSlayer

Copy link
Copy Markdown
Collaborator

Problem

A daemon can run for days transmitting into a stale NAT/relay mapping while receiving nothing. 2026-07-13 incident on cascade-test: 43.5 MB / 572K pkts sent vs 102 KB / 1,443 pkts received over 2d19h, every send-message failing with cannot connect (data exchange port 1001). The registry heartbeat is TCP and kept succeeding, so trustRepublishLoop never noticed — only a manual restart cleared it.

Fix

New inbound-path watchdog (pkg/daemon/rxwatchdog.go), on by default:

  • Detect: samples PktsRecv/PktsSent every 30s; wedge = 3 min of delivered-packet silence while ≥30 packets went out. New LastRecvNano raw-datagram timestamp distinguishes dead-socket from dead-session-layer in logs.
  • Soft recover (×3): RegisterWithBeacon (the discover reply doubles as an active inbound probe) + registry reRegister.
  • Hard recover: exit code 86 → launchd (KeepAlive.SuccessfulExit=false) / systemd (Restart=always) respawn with a fresh transport — the remedy that demonstrably clears the wedge.
  • Flap guards: exit withheld when the registry is also unreachable (machine offline), when inbound never progressed this process (boot-loop guard), or within 30 min of start.
  • Emits tunnel.rx_silence / tunnel.rx_recovered / tunnel.rx_wedged_exit. Opt out: -no-rx-watchdog.

Also: pilotctl daemon start launchd readiness wait 10s→30s (app-store apps spawn before IPC; the old timeout reported a false failure for boots that succeed moments later).

Testing

  • 4 tick-driven unit tests (healthy paths, soft-recovery cycle, exit requires fresh registry, exit withheld for never-progressed/young process) — pass with -race -count=5.
  • Full pkg/daemon suite: only the two pre-existing failures (TestWriteLoopExitsOnWriteDeadline, TestIPCServer_MaxClientsCap — confirmed pre-existing via git stash).
  • Docs synced (configuration flags table, webhooks events table, plain mirrors, CHANGELOG); website builds clean.

🤖 Generated with Claude Code

…wedge

A daemon could run for days transmitting into a stale NAT/relay mapping
while receiving nothing (2026-07-13 incident: 43.5 MB sent vs 102 KB
received over 2d19h, every send-message failing with "cannot connect
(data exchange port 1001)"). The registry heartbeat is TCP and kept
succeeding, so trustRepublishLoop never noticed and only a manual
restart cleared it.

The new watchdog (pkg/daemon/rxwatchdog.go) samples PktsRecv/PktsSent
every 30s. On 3 min of delivered-packet silence with active transmit it
soft-recovers: RegisterWithBeacon (the discover reply doubles as an
active inbound probe) plus registry reRegister. If the wedge survives
3 soft attempts it exits with code 86 so launchd (KeepAlive
SuccessfulExit=false) / systemd (Restart=always) respawn the daemon
with a fresh transport — the remedy that demonstrably clears the wedge.

Flap guards on the hard exit: never fires when the registry is also
unreachable (machine offline — restart fixes nothing), when inbound
never progressed this process (would boot-loop), or within 30 min of
start. Emits tunnel.rx_silence / tunnel.rx_recovered /
tunnel.rx_wedged_exit. Disable with -no-rx-watchdog.

Supporting changes:
- tunnel.go: LastRecvNano stamped on every raw datagram + LastRecvTime()
  accessor, so logs separate dead-socket from dead-session-layer wedges.
- daemon.go: lastRegistryOKNano stamped on registration/heartbeat/
  re-registration successes; startTime now set at the top of Start()
  so watchdog goroutine reads never race the historical late assignment.
- pilotctl daemon start: launchd readiness wait 10s -> 30s — app-store
  apps spawn before IPC comes up, and the old timeout reported a false
  failure for boots that succeed moments later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread pkg/daemon/rxwatchdog.go Fixed
teovl and others added 2 commits July 13, 2026 08:46
govulncheck flags GO-2026-5856 (crypto/tls Encrypted Client Hello privacy
leak), fixed in the standard library at go1.25.12. CI installs the toolchain
via go-version-file: go.mod, so bumping the go directive pulls in the fixed
stdlib and clears the vuln repo-wide. Build verified under go1.25.12.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ypto)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TeoSlayer
TeoSlayer merged commit 3846f6f into main Jul 13, 2026
15 checks passed
@TeoSlayer
TeoSlayer deleted the feat/rx-watchdog branch July 13, 2026 06:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants