mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Suppress repeated replay detection log messages during link transitions
Add per-peer replay suppression counter to ActivePeer. Log the first 3 replay detections at DEBUG, then suppress with a one-time notice. Emit a summary count on session replacement or peer removal. Non-replay decryption errors continue to be logged unconditionally.
This commit is contained in:
@@ -98,6 +98,16 @@ impl Node {
|
||||
}
|
||||
};
|
||||
|
||||
// Log suppressed replay detection summary before teardown
|
||||
let suppressed = peer.replay_suppressed_count();
|
||||
if suppressed > 0 {
|
||||
debug!(
|
||||
peer = %self.peer_display_name(node_addr),
|
||||
count = suppressed,
|
||||
"Suppressed replay detections during link transition"
|
||||
);
|
||||
}
|
||||
|
||||
// MMP teardown log (before we drop the peer)
|
||||
if let Some(mmp) = peer.mmp() {
|
||||
let name = self.peer_aliases.get(node_addr)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Encrypted frame handling (hot path).
|
||||
|
||||
use crate::noise::NoiseError;
|
||||
use crate::node::Node;
|
||||
use crate::node::wire::{EncryptedHeader, strip_inner_header, FLAG_CE, FLAG_SP};
|
||||
use crate::transport::ReceivedPacket;
|
||||
@@ -63,12 +64,41 @@ impl Node {
|
||||
) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
debug!(
|
||||
peer = %self.peer_display_name(&node_addr),
|
||||
counter = header.counter,
|
||||
error = %e,
|
||||
"Decryption failed"
|
||||
);
|
||||
if matches!(e, NoiseError::ReplayDetected(_)) {
|
||||
// Suppress repeated replay detections during link transitions.
|
||||
// Re-borrow peer mutably for suppression counter update.
|
||||
if let Some(peer) = self.peers.get_mut(&node_addr) {
|
||||
let count = peer.increment_replay_suppressed();
|
||||
if count <= 3 {
|
||||
debug!(
|
||||
peer = %self.peer_display_name(&node_addr),
|
||||
counter = header.counter,
|
||||
error = %e,
|
||||
"Decryption failed"
|
||||
);
|
||||
} else if count == 4 {
|
||||
debug!(
|
||||
peer = %self.peer_display_name(&node_addr),
|
||||
"Suppressing further replay detection messages"
|
||||
);
|
||||
}
|
||||
// count > 4: silently suppress
|
||||
} else {
|
||||
debug!(
|
||||
peer = %self.peer_display_name(&node_addr),
|
||||
counter = header.counter,
|
||||
error = %e,
|
||||
"Decryption failed"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
peer = %self.peer_display_name(&node_addr),
|
||||
counter = header.counter,
|
||||
error = %e,
|
||||
"Decryption failed"
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -450,6 +450,7 @@ impl Node {
|
||||
};
|
||||
|
||||
if let Some(peer) = self.peers.get_mut(&peer_node_addr) {
|
||||
let suppressed = peer.replay_suppressed_count();
|
||||
let old_our_index = peer.replace_session(
|
||||
outbound_session,
|
||||
outbound_our_index,
|
||||
@@ -467,6 +468,14 @@ impl Node {
|
||||
peer_node_addr,
|
||||
);
|
||||
|
||||
if suppressed > 0 {
|
||||
debug!(
|
||||
peer = %self.peer_display_name(&peer_node_addr),
|
||||
count = suppressed,
|
||||
"Suppressed replay detections during link transition"
|
||||
);
|
||||
}
|
||||
|
||||
info!(
|
||||
peer = %self.peer_display_name(&peer_node_addr),
|
||||
new_our_index = %outbound_our_index,
|
||||
|
||||
@@ -143,6 +143,10 @@ pub struct ActivePeer {
|
||||
/// Wire-format msg2 for resend on duplicate msg1 (responder only).
|
||||
/// Cleared after the handshake timeout window.
|
||||
handshake_msg2: Option<Vec<u8>>,
|
||||
|
||||
// === Replay Detection Suppression ===
|
||||
/// Number of replay detections suppressed since last session reset.
|
||||
replay_suppressed_count: u32,
|
||||
}
|
||||
|
||||
impl ActivePeer {
|
||||
@@ -177,6 +181,7 @@ impl ActivePeer {
|
||||
mmp: None,
|
||||
last_heartbeat_sent: None,
|
||||
handshake_msg2: None,
|
||||
replay_suppressed_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +245,7 @@ impl ActivePeer {
|
||||
mmp: Some(MmpPeerState::new(mmp_config, is_initiator)),
|
||||
last_heartbeat_sent: None,
|
||||
handshake_msg2: None,
|
||||
replay_suppressed_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,12 +345,14 @@ impl ActivePeer {
|
||||
/// This replaces the entire session so both nodes use matching keys.
|
||||
///
|
||||
/// Returns the old our_index so the caller can update peers_by_index.
|
||||
/// Also resets the replay suppression counter since the session changed.
|
||||
pub fn replace_session(
|
||||
&mut self,
|
||||
new_session: NoiseSession,
|
||||
new_our_index: SessionIndex,
|
||||
new_their_index: SessionIndex,
|
||||
) -> Option<SessionIndex> {
|
||||
self.reset_replay_suppressed();
|
||||
let old_our_index = self.our_index;
|
||||
self.noise_session = Some(new_session);
|
||||
self.our_index = Some(new_our_index);
|
||||
@@ -387,6 +395,26 @@ impl ActivePeer {
|
||||
self.handshake_msg2 = None;
|
||||
}
|
||||
|
||||
// === Replay Detection Suppression ===
|
||||
|
||||
/// Increment replay suppression counter. Returns the new count.
|
||||
pub fn increment_replay_suppressed(&mut self) -> u32 {
|
||||
self.replay_suppressed_count += 1;
|
||||
self.replay_suppressed_count
|
||||
}
|
||||
|
||||
/// Reset replay suppression counter, returning previous count.
|
||||
pub fn reset_replay_suppressed(&mut self) -> u32 {
|
||||
let count = self.replay_suppressed_count;
|
||||
self.replay_suppressed_count = 0;
|
||||
count
|
||||
}
|
||||
|
||||
/// Current replay suppression count.
|
||||
pub fn replay_suppressed_count(&self) -> u32 {
|
||||
self.replay_suppressed_count
|
||||
}
|
||||
|
||||
// === Epoch Accessors ===
|
||||
|
||||
/// Get the remote peer's startup epoch (from handshake).
|
||||
@@ -794,4 +822,31 @@ mod tests {
|
||||
assert_eq!(peer.link_stats().packets_sent, 1);
|
||||
assert_eq!(peer.link_stats().packets_recv, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replay_suppression_counter() {
|
||||
let identity = make_peer_identity();
|
||||
let mut peer = ActivePeer::new(identity, LinkId::new(1), 1000);
|
||||
|
||||
// Initial count is zero
|
||||
assert_eq!(peer.replay_suppressed_count(), 0);
|
||||
|
||||
// Increment returns new count
|
||||
assert_eq!(peer.increment_replay_suppressed(), 1);
|
||||
assert_eq!(peer.increment_replay_suppressed(), 2);
|
||||
assert_eq!(peer.increment_replay_suppressed(), 3);
|
||||
assert_eq!(peer.replay_suppressed_count(), 3);
|
||||
|
||||
// Reset returns previous count and zeroes it
|
||||
assert_eq!(peer.reset_replay_suppressed(), 3);
|
||||
assert_eq!(peer.replay_suppressed_count(), 0);
|
||||
|
||||
// Can increment again after reset
|
||||
assert_eq!(peer.increment_replay_suppressed(), 1);
|
||||
assert_eq!(peer.replay_suppressed_count(), 1);
|
||||
|
||||
// Reset when zero returns zero
|
||||
peer.reset_replay_suppressed();
|
||||
assert_eq!(peer.reset_replay_suppressed(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user