mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 16:24:45 +00:00
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:
@@ -135,6 +135,28 @@ Handshake rate limiting protects against DoS on the Noise XX handshake path.
|
||||
| `node.rate_limit.handshake_resend_interval_ms` | u64 | `1000` | Initial handshake message resend interval |
|
||||
| `node.rate_limit.handshake_resend_backoff` | f64 | `2.0` | Resend backoff multiplier (1s, 2s, 4s, 8s, 16s with defaults) |
|
||||
| `node.rate_limit.handshake_max_resends` | u32 | `5` | Max resends per handshake attempt |
|
||||
| `node.rate_limit.established_handshake_burst` | u32 | derived | Burst capacity of the established-link bucket. Derived default is `node.limits.max_peers` (128) |
|
||||
| `node.rate_limit.established_handshake_rate` | f64 | derived | Refill rate of that bucket. Derived default is `(max_peers / max(node.rekey.after_secs, 1)) * (1 + handshake_max_resends)`, floored at 1.0/s — 6.4/s at shipped defaults |
|
||||
|
||||
Msg1 whose source matches an established link (rekey and restart
|
||||
maintenance traffic) draws on a second bucket rather than competing with
|
||||
stranger admission. Both keys are optional; leaving them unset keeps the
|
||||
derived sizing, which tracks `max_peers` and the rekey period
|
||||
automatically instead of becoming a constant nobody revisits.
|
||||
`max_peers: 0` (unlimited) has no peer-count-derived size, so the
|
||||
derivation falls back to `handshake_burst` / `handshake_rate`.
|
||||
|
||||
The node's total admitted msg1 rate is the **sum** of the two buckets: 228
|
||||
burst and 16.4/s at shipped defaults, of which the established half is
|
||||
reachable only by a source that already matches a live link. Size against
|
||||
the sum when budgeting handshake crypto load for a host.
|
||||
|
||||
"Established link" here means a **promoted** peer, which is stricter than
|
||||
it sounds on the XX handshake path. An inbound handshake that has sent
|
||||
msg1 but not yet completed msg3 is not promoted, so it draws on the
|
||||
stranger bucket for its whole lifetime, including every msg1 retransmit.
|
||||
Only traffic from a source already matching a promoted peer reaches the
|
||||
established bucket.
|
||||
|
||||
### Retry / Backoff (`node.retry.*`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user