Commit ed93632
committed
fix(daemon): in-window replay recovery, rekey relay fallback, FIN-not-RST keepalive reap
Three independent fleet-wedge fixes that compose into the rc7 release.
1. In-window replay-collision recovery (`pkg/daemon/keyexchange/`,
`pkg/daemon/envelope/`, `pkg/daemon/tunnel.go`).
When a peer restarts but its persisted X25519 identity carries
over, the new daemon's send counter resets to 1 while our
MaxRecvNonce still sits at the pre-restart watermark. Every
subsequent frame from the peer lands at an already-marked counter
in the replay bitmap and is dropped BEFORE AEAD-Open runs, so
neither DecryptFailCount nor OutsideWindowCount increment. Pre-
rc7, no recovery path engaged and the session wedged
indefinitely (the 2026-05-11 list-agents bug).
Fix: track ReplayCount per Crypto, gate via ShouldDropOnReplay
(threshold=30, grace=3s, CompareAndDrop-guarded). On trip, drop
the wedged Crypto + request rekey. Storm-safe — the grace window
prevents the v1.9.1 rekey→fresh-session→relay-replay→rekey loop.
2. Rekey relay-fallback (`pkg/daemon/keyexchange/loop.go`,
`pkg/daemon/tunnel.go`).
When the local NAT remaps our external port across a daemon
restart, peers' cached endpoints go stale; rekey PILA goes out
fine, peer's PILA reply lands in a black hole. The blackhole
heuristic needs BlackholeMissesRequired=3 observations × 4s = 12s
to engage, but rekey gives up at 24s — too late for the existing
routing flip to kick in. Fix: PreRetransmitHook fires once per
retransmit; tunnel.go installs a hook that flips the peer to
relay after RekeyRelayFallbackAfter=2 attempts. Existing
ResetPendingRekeyAttempts handles the budget reset.
3. FIN-not-RST keepalive reap (`pkg/daemon/daemon.go`).
When KeepaliveUnacked reached 3, the reaper constructed a raw RST
inline and forced StateClosed. The RST poisoned the peer's
session table for that conn_id, cascading into "connection
refused" on the peer's next outbound. Observed fleet-wide on
2026-05-11 as bursts of N successful sends followed by total
loss of reachability. Fix: extract keepaliveSweep; replace the
raw RST with CloseConnection() (proper FIN with retx tracking,
transitions to StateFinWait).
Tests:
- 5 envelope unit tests (bookkeeping, isolation, gate semantics,
end-to-end, storm-safety) in zz_replay_recovery_test.go
- 1 daemon unit test (TestSustainedReplayTriggersRekey) pinning the
tunnel.go ErrReplay→drop+rekey wiring, plus updated
TestReplayNeverTriggersRekey comment acknowledging the
persistent-identity case
- 3 daemon unit tests for the rekey relay-fallback hook
(threshold flip, end-to-end via tick, below-threshold no-flip)
- 3 daemon unit tests for the FIN-not-RST keepalive reap
(FIN-not-RST regression, probe path unaffected, non-established
conns skipped)
- 1 real end-to-end test (`tests/zz_rc6_peer_restart_recovery_test.go`)
spinning up beacon + registry + two daemons on loopback,
exercising the full peer-restart recovery cycle through the
driver API — no internal hooks, no white-box manipulation1 parent 2efe049 commit ed93632
12 files changed
Lines changed: 1319 additions & 80 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4339 | 4339 | | |
4340 | 4340 | | |
4341 | 4341 | | |
4342 | | - | |
4343 | | - | |
4344 | | - | |
4345 | | - | |
4346 | | - | |
4347 | | - | |
4348 | | - | |
4349 | | - | |
4350 | | - | |
4351 | | - | |
4352 | | - | |
4353 | | - | |
4354 | | - | |
4355 | | - | |
4356 | | - | |
4357 | | - | |
4358 | | - | |
4359 | | - | |
4360 | | - | |
4361 | | - | |
4362 | | - | |
4363 | | - | |
4364 | | - | |
4365 | | - | |
4366 | | - | |
4367 | | - | |
4368 | | - | |
4369 | | - | |
4370 | | - | |
4371 | | - | |
4372 | | - | |
4373 | | - | |
4374 | | - | |
4375 | | - | |
4376 | | - | |
4377 | | - | |
4378 | | - | |
4379 | | - | |
4380 | | - | |
4381 | | - | |
4382 | | - | |
4383 | | - | |
4384 | | - | |
4385 | | - | |
4386 | | - | |
4387 | | - | |
4388 | | - | |
4389 | | - | |
4390 | | - | |
4391 | | - | |
4392 | | - | |
4393 | | - | |
4394 | | - | |
4395 | | - | |
| 4342 | + | |
| 4343 | + | |
| 4344 | + | |
| 4345 | + | |
| 4346 | + | |
| 4347 | + | |
| 4348 | + | |
| 4349 | + | |
| 4350 | + | |
| 4351 | + | |
| 4352 | + | |
| 4353 | + | |
| 4354 | + | |
| 4355 | + | |
| 4356 | + | |
| 4357 | + | |
| 4358 | + | |
| 4359 | + | |
| 4360 | + | |
| 4361 | + | |
| 4362 | + | |
| 4363 | + | |
| 4364 | + | |
| 4365 | + | |
| 4366 | + | |
| 4367 | + | |
| 4368 | + | |
| 4369 | + | |
| 4370 | + | |
| 4371 | + | |
| 4372 | + | |
| 4373 | + | |
| 4374 | + | |
| 4375 | + | |
| 4376 | + | |
| 4377 | + | |
| 4378 | + | |
| 4379 | + | |
| 4380 | + | |
| 4381 | + | |
| 4382 | + | |
| 4383 | + | |
| 4384 | + | |
| 4385 | + | |
| 4386 | + | |
| 4387 | + | |
| 4388 | + | |
| 4389 | + | |
| 4390 | + | |
| 4391 | + | |
| 4392 | + | |
| 4393 | + | |
| 4394 | + | |
| 4395 | + | |
| 4396 | + | |
| 4397 | + | |
| 4398 | + | |
| 4399 | + | |
| 4400 | + | |
| 4401 | + | |
| 4402 | + | |
| 4403 | + | |
| 4404 | + | |
| 4405 | + | |
| 4406 | + | |
| 4407 | + | |
| 4408 | + | |
| 4409 | + | |
| 4410 | + | |
| 4411 | + | |
4396 | 4412 | | |
| 4413 | + | |
4397 | 4414 | | |
4398 | 4415 | | |
4399 | 4416 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
167 | 181 | | |
168 | 182 | | |
169 | 183 | | |
| |||
192 | 206 | | |
193 | 207 | | |
194 | 208 | | |
195 | | - | |
| 209 | + | |
196 | 210 | | |
197 | 211 | | |
198 | 212 | | |
199 | 213 | | |
200 | 214 | | |
201 | 215 | | |
202 | 216 | | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
203 | 220 | | |
204 | 221 | | |
205 | 222 | | |
| |||
0 commit comments