mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-10 00:26:59 +00:00
fmp: fix jitter × XX rekey session divergence, re-enable rekey jitter
Re-enables the rekey timer jitter on the XX FMP rekey path (REKEY_JITTER_SECS 0 -> 15), which had been disabled because it produced reproducible post-rekey routing loss (~50% Phase-5 ping failure) with no crypto errors. The failures were session divergence: under jitter the two directions of a link rekey close together in time, and three distinct defects in the FMP rekey state machine could leave the two endpoints committed to different Noise sessions, starving the receiver until the 30s heartbeat dead-timer tore the link down (tree parent loss -> routing failure). All three are fixed here. 1. Promote on authenticated decrypt, not the bare K-bit. The K-bit-flip handler promoted whatever pending session existed the instant the header bit flipped; under interleaved rekeys that could be a stale pending from an earlier epoch. Trial-decrypt the inbound frame against the pending session and promote only if it authenticates, mirroring the FSP cutover discipline; deliver that plaintext through the canonical path and leave the pending untouched otherwise. 2. Retransmit FMP rekey msg3 until confirmed. FMP sent msg3 once; a lost datagram left the responder without the new session. Retain the msg3 payload and resend over the existing link until a peer frame authenticates against the pending or post-cutover current session, abandoning after the configured handshake-resend budget (mirrors FSP). Also serialize per-link rekeys: do not start a new rekey while one awaits cutover or is still retransmitting msg3. 3. Partition the handle_msg3 paths by rekey age. An inbound msg3 on a different link took the initial-handshake cross-connection tie-breaker when the session was under a fixed 30s old, otherwise the rekey responder. A rekey resets the session-age clock, so under jitter a rekey-aged session is frequently under 30s and its concurrent rekey msg3 was swallowed by the cross-connection branch, which discarded the peer's rekey session with no pending slot while the peer cut over to it anyway. Bound the cross-connection branch by the same jitter-aware age floor the responder uses, so the two paths partition with no overlap. Verified at jitter=15: rekey integration suite 70/70 across repeated runs locally and on GitHub CI, rekey-accept-off 71/71, rekey-outbound-only 75/75; lib 1369/0, clippy and fmt clean. At zero jitter the acceptance floor equals the previous 30s constant, so default-cadence behavior is unchanged.
This commit is contained in:
+13
-8
@@ -40,14 +40,19 @@ use self::routing_error_rate_limit::RoutingErrorRateLimiter;
|
||||
/// dual-initiation in symmetric-start meshes; the configured
|
||||
/// `node.rekey.after_secs` remains the nominal interval (mean preserved).
|
||||
///
|
||||
/// Disabled (set to 0) on next pending investigation: the jitter mechanism
|
||||
/// was authored against the IK FMP rekey path on maint/master and works
|
||||
/// cleanly there, but on next's XX FMP rekey path it produces reproducible
|
||||
/// post-cutover routing-convergence failures (~50% Phase 5 ping loss in
|
||||
/// the `rekey` integration suite). Restoring jitter on next requires
|
||||
/// understanding why the XX cutover state cleanup doesn't absorb
|
||||
/// variable-interval rekeys the way the IK path does. See CHANGELOG.
|
||||
pub(crate) const REKEY_JITTER_SECS: i64 = 0;
|
||||
/// Re-enabled on next after the jitter × XX rekey interaction was closed.
|
||||
/// The jitter was previously disabled (set to 0) on next because applying it
|
||||
/// to the XX FMP rekey path produced reproducible post-cutover
|
||||
/// routing-convergence failures (~50% Phase 5 ping loss in the `rekey`
|
||||
/// integration suite). Root cause: under jitter a recent cutover resets the
|
||||
/// session-age clock, so a concurrent rekey msg3 (always on a temp link) was
|
||||
/// caught by the initial-handshake cross-connection tie-breaker in
|
||||
/// `handle_msg3` and, on the "our outbound wins" side, the peer's rekey
|
||||
/// session was discarded with no `pending` slot — yet the peer cut over to it
|
||||
/// regardless, starving the discarding node until the 30s dead-timer. Fixed by
|
||||
/// bounding that cross-connection branch below the rekey age floor so a
|
||||
/// rekey-aged msg3 falls through to the rekey-responder path. See CHANGELOG.
|
||||
pub(crate) const REKEY_JITTER_SECS: i64 = 15;
|
||||
use self::wire::{
|
||||
ESTABLISHED_HEADER_SIZE, FLAG_CE, FLAG_KEY_EPOCH, build_encrypted, build_established_header,
|
||||
prepend_inner_header,
|
||||
|
||||
Reference in New Issue
Block a user