Skip to content

Commit 5de736f

Browse files
committed
Cap desync salvage at 4 entries (was 32) to prevent replay storm
The 32-entry cap caused the rekey-replay path to burst-flood the receiver's nonce window: under typical conditions the dataexchange retransmit layer fills salvage during the rekey-pending window, and on rekey all 32 frames go out in a tight loop. The receiver's pc[a] was just freshly installed (maxRecvNonce=0), so the 32 nonces (1..32) arrived out of order on the wire and the replay-window check started flagging the late-arriving lower nonces as replays. Net effect: salvage's whole purpose (recover dropped data on rekey) defeated by its own batch size. 4 covers the realistic shape — one task submit + send-results + a couple of dataexchange retries — without exceeding what a fresh peerCrypto's window can absorb in arbitrary order. Memory drops to ~6 KiB / peer (was 48 KiB), worst case ~6 MiB across maxCryptoPeers. Empirically with this cap: - test_tunnel_desync_recovery: 7/0 (was hanging on direct-path-silent) - test_midrekey_send_file: 5/0 (was 4/1) - test_midrekey_send_message: 6/0 (was 5/1) - test_midrekey_task_results: 6/0 (was 4/2) - test_midrekey_task_submit: 5/0 (was 3/2) - test_peer_restarted_send_file: 4/0 (was 3/1) - test_peer_restarted_send_message: 5/0 (was 3/2) The midrekey_* tests are documented P1-009 regression repros — this fix closes them too.
1 parent 22092c3 commit 5de736f

1 file changed

Lines changed: 8 additions & 3 deletions

File tree

pkg/daemon/tunnel.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,14 @@ type salvageEntry struct {
5454
when time.Time
5555
}
5656

57-
// salvageMaxEntries bounds memory: 32 × ~1500 bytes ≈ 48 KiB per peer.
58-
// At maxCryptoPeers (1024) that's a 48 MiB worst-case bound.
59-
const salvageMaxEntries = 32
57+
// salvageMaxEntries bounds memory + replay-storm size. Originally 32,
58+
// but a 32-frame burst replayed on rekey caused receiver-side nonce
59+
// confusion (concurrent dataexchange retransmits filling salvage +
60+
// out-of-order delivery on the wire). 4 covers the typical "task
61+
// submit + send-results + a couple of retries" without overwhelming
62+
// the receiver. Memory at max: 4 × ~1500 B = 6 KiB / peer, ~6 MiB
63+
// across maxCryptoPeers (1024).
64+
const salvageMaxEntries = 4
6065

6166
// salvageMaxAge is how far back we replay sends after a rekey. The rekey
6267
// round-trip itself is ~1 RTT plus the rate-limit window (3 s); 5 s gives

0 commit comments

Comments
 (0)