From 6ee43b664d03752aab3095d020eaa90cf96d7ba0 Mon Sep 17 00:00:00 2001 From: tv Date: Fri, 6 Nov 2026 07:02:57 +0100 Subject: handshakes counted --- src/state/peer.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) (limited to 'src/state') diff --git a/src/state/peer.rs b/src/state/peer.rs index 2f027ba..7000386 100644 --- a/src/state/peer.rs +++ b/src/state/peer.rs @@ -188,6 +188,10 @@ pub(crate) struct PeerState { /// What went wrong with this peer so far (counted only, for /peers and /admin/stats). pub(crate) incidents: Incidents, + /// The handshakes it started within the current `HANDSHAKES_WINDOW`, from when it + /// began; and when we last warned of them. + handshakes_lately: Option<(Instant, u32)>, + handshakes_warned_at: Option, /// Changes it asked for that we had counted as its, as another parent in their /// writer's tree brings them (`SyncSession::resend_if_needed`): per asker, for the /// bench (`simtest::bench`). @@ -254,6 +258,9 @@ pub(crate) struct Incidents { /// Time spent dormant, in milliseconds, up to its last return (the current /// dormancy, if any, isn't included). pub(crate) dormant_ms: u64, + /// Handshakes the peer started with us (its HELLO, not a retry of one): each resets + /// our session with it (`PeerState::handshake_started`). + pub(crate) handshakes: u64, } impl Incidents { @@ -263,6 +270,7 @@ impl Incidents { self.asked_again += other.asked_again; self.dormancies += other.dormancies; self.dormant_ms += other.dormant_ms; + self.handshakes += other.handshakes; } } @@ -278,6 +286,11 @@ pub(crate) enum Keepalive { /// Unanswered HELLOs after which a peer counts as unreachable (1 + 2 + 4 s apart). const UNREACHABLE_AFTER_HELLOS: u32 = 3; +/// Handshakes a peer may start with us within `HANDSHAKES_WINDOW` before we warn +/// (`PeerState::handshake_started`): a restart now and then is fine, a loop isn't. +const HANDSHAKES_WARN: u32 = 4; +const HANDSHAKES_WINDOW: Duration = Duration::from_secs(60); + impl PeerState { pub(crate) fn new(leads: bool, timing: &Timing) -> Self { Self { @@ -319,11 +332,36 @@ impl PeerState { last_hello_at: None, unreachable_since: None, incidents: Incidents::default(), + handshakes_lately: None, + handshakes_warned_at: None, asked_known: 0, whole_failed: None, } } + /// The peer started a handshake with us (`swarm::peer::handshake::establish_peer`): + /// counted (`Incidents::handshakes`). Returns how many it started within the + /// current window when more than `HANDSHAKES_WARN`, at most once a window: a peer + /// in a restart loop, or one whose handshakes keep failing on its side. + pub(crate) fn handshake_started(&mut self, now: Instant) -> Option { + self.incidents.handshakes += 1; + let (since, count) = match self.handshakes_lately { + Some((since, count)) if now.saturating_duration_since(since) < HANDSHAKES_WINDOW => { + (since, count + 1) + } + _ => (now, 1), + }; + self.handshakes_lately = Some((since, count)); + let warned_lately = self + .handshakes_warned_at + .is_some_and(|at| now.saturating_duration_since(at) < HANDSHAKES_WINDOW); + if count > HANDSHAKES_WARN && !warned_lately { + self.handshakes_warned_at = Some(now); + return Some(count); + } + None + } + /// Start Automerge sync with this peer from scratch. A message still being handed to /// the transport was generated against the old state; its outcome is then ignored. pub(crate) fn reset_sync(&mut self) { @@ -742,6 +780,24 @@ impl PeerState { mod tests { use super::*; + /// Handshakes are counted; more than `HANDSHAKES_WARN` within a window are warned + /// of, once a window. + #[test] + fn handshakes_are_counted_and_a_loop_warned_of() { + let timing = Timing::from_keepalive(Duration::from_secs(60), Duration::from_secs(300)); + let mut peer = PeerState::new(true, &timing); + let start = peer.added_at; + let at = |secs: u64| start + Duration::from_secs(secs); + for i in 0..HANDSHAKES_WARN { + assert_eq!(peer.handshake_started(at(u64::from(i))), None); + } + assert_eq!(peer.handshake_started(at(10)), Some(HANDSHAKES_WARN + 1)); + assert_eq!(peer.handshake_started(at(11)), None, "once a window"); + // A new window: counted from one again. + assert_eq!(peer.handshake_started(at(70)), None); + assert_eq!(peer.incidents.handshakes, u64::from(HANDSHAKES_WARN) + 3); + } + /// Unknown until heard from; silent long enough, dormant like an active peer. #[test] fn unknown_until_heard_from() { -- cgit v1.3.1