Meter established-link msg1 separately from stranger admission

The msg1 rate limiter ran one bucket for everything, so a rekey or
restart msg1 from an established peer competed for admission with
strangers. Under inbound pressure the maintenance traffic lost, and an
established session could not re-handshake until the stranger flood
eased.

Classify the source before the rate-limit decision and give established
links their own bucket. Classification costs two map lookups and no
crypto, so it runs first.

The predicate cannot be the one the master line uses. There, IK has
learned the peer identity by the time msg1 is handled, so a hit in the
address-to-link map is enough to prove an established peer. Under XX no
identity is known at msg1, and an in-flight handshake populates that map
before the peer is promoted, so the same predicate would let a stranger
draw on the established bucket for the whole life of its handshake.
This version requires a promoted peer, which is the property actually
being asserted.

Two tests cover the difference and both fail if the predicate is
replaced with the master-line one: a pending inbound stranger must keep
drawing on the stranger bucket for its whole lifetime, and the
established gate must not admit on a bare map hit.

The four handshake reject arms that leak a session index are untouched;
every hunk here sits at or above the end of the msg1 handler.
This commit is contained in:
Johnathan Corgan
2026-08-01 22:37:30 +00:00
parent 069d450983
commit aca89bbab8
9 changed files with 1298 additions and 87 deletions
+15
View File
@@ -78,6 +78,19 @@ pub struct RateLimitConfig {
/// Max handshake resends per attempt (`node.rate_limit.handshake_max_resends`).
#[serde(default = "RateLimitConfig::default_handshake_max_resends")]
pub handshake_max_resends: u32,
/// Burst capacity of the established-link msg1 bucket
/// (`node.rate_limit.established_handshake_burst`).
///
/// Absent (the normal case) derives it from `node.limits.max_peers`.
#[serde(default)]
pub established_handshake_burst: Option<u32>,
/// Tokens/sec refill rate of the established-link msg1 bucket
/// (`node.rate_limit.established_handshake_rate`).
///
/// Absent (the normal case) derives it from `node.limits.max_peers`,
/// `node.rekey.after_secs` and `handshake_max_resends`.
#[serde(default)]
pub established_handshake_rate: Option<f64>,
}
impl Default for RateLimitConfig {
@@ -89,6 +102,8 @@ impl Default for RateLimitConfig {
handshake_resend_interval_ms: 1000,
handshake_resend_backoff: 2.0,
handshake_max_resends: 5,
established_handshake_burst: None,
established_handshake_rate: None,
}
}
}