aboutsummaryrefslogtreecommitdiffstats
path: root/src/state
diff options
context:
space:
mode:
Diffstat (limited to 'src/state')
-rw-r--r--src/state/peer.rs56
1 files changed, 56 insertions, 0 deletions
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<Instant>,
/// 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<u32> {
+ 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() {