You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
daemon: inbound-path watchdog — auto-recover the long-uptime NAT wedge (#369)
* daemon: add inbound-path watchdog — auto-recover the long-uptime NAT 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>
* go.mod: bump go directive 1.25.11 -> 1.25.12 (GO-2026-5856)
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>
* rxwatchdog: suppress gosec G404 on startup jitter (scheduling, not crypto)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Teodor Calin <teodor@vulturelabs.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: cmd/daemon/main.go
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -94,6 +94,7 @@ func main() {
94
94
networks:=flag.String("networks", "", "comma-separated network IDs to auto-join at startup")
95
95
trustAutoApprove:=flag.Bool("trust-auto-approve", false, "automatically approve all incoming trust handshakes")
96
96
beaconRTTProbe:=flag.Bool("beacon-rtt-probe", false, "probe beacon RTT before selection; override hash pick when >2× slower than best (ablation test, default off)")
97
+
noRxWatchdog:=flag.Bool("no-rx-watchdog", false, "disable the inbound-path watchdog that soft-recovers (beacon+registry re-registration) and, on a persistent wedge, exits non-zero for supervisor respawn")
97
98
transportMode:=flag.String("transport", "udp", "tunnel transport: 'udp' (default) or 'compat' (WSS to beacon, opt-in, for UDP-blocked environments)")
98
99
compatBeacon:=flag.String("compat-beacon", "wss://beacon.pilotprotocol.network/v1/compat", "beacon WSS URL for -transport=compat")
99
100
tlsTrust:=flag.String("tls-trust", "system", "TLS trust store for -transport=compat: 'system' (OS trust store; current default while compat mode uses Let's Encrypt certs on beacon.pilotprotocol.network) or 'pinned' (Pilot CA root embedded in the daemon binary; will become the default in a future release once production root ships)")
0 commit comments