mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 08:14:42 +00:00
Meter established-link msg1 separately from stranger admission
The msg1 rate limiter ran before the established-peer carve-out, so a rekey or restart msg1 arriving on an existing link was refused on exactly the same terms as a stranger's first packet and the carve-out below it never applied to the traffic it was written for. On a node with many peers this refuses a large share of ordinary maintenance traffic: a field node at roughly 245 peers refused 8753 msg1 in 25 minutes, and 159 of the 201 distinct sources were peers it already held sessions with. Classify the source before metering it, and give established-link msg1 its own token bucket instead of a bypass. A bypass was rejected deliberately: the limiter is global precisely because UDP sources are spoofable, and an established-peer exemption is by construction keyed on source address, so metering the exempted class keeps that property where bypassing discards it. The bucket is derived from settings the operator already sets, burst from max_peers and rate from max_peers, the rekey interval and the resend budget, so raising the peer limit sizes it automatically rather than leaving a constant nobody revisits. Both parameters can be set explicitly; an explicit zero burst or non-positive rate is rejected at config validation, because it would refuse every rekey msg1 from an established peer rather than disabling the limit. Split out the established-link test as its own predicate so the rate-limit classifier and the accept_connections gate cannot drift, and convert the limiter's pending slot to a guard released on drop. The slot was previously acquired in one place and released explicitly at eighteen exit paths; once some paths stop acquiring one, any path that still released one would have freed a slot belonging to a different in-flight handshake, lifting effective concurrency above the configured maximum with no counter moving and no log firing. The "Msg1 rate limited" line now reports which limb refused, the pending count or the token bucket, which it did not distinguish before. Classification costs an O(peers) scan on every inbound msg1 including refused ones, where the previous order refused at O(1). The scan is only needed because addr_to_link is keyed on the unresolved dial address; correcting that keying reduces this to a single O(1) lookup. Recorded at the predicate.
This commit is contained in:
@@ -134,6 +134,21 @@ Handshake rate limiting protects against DoS on the Noise IK 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.
|
||||
|
||||
### Retry / Backoff (`node.retry.*`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user