Merge branch 'maint'

The established-link msg1 metering is re-authored against this line's
handshake handler rather than taken from the merge. The two versions of
src/node/handlers/handshake.rs differ by roughly 1437 lines from the
earlier decomposition, so the conflict hunks spanned whole divergent
bodies, including this line's restructured dual-initiation handling,
and resolving them hunk by hunk would have spliced the two structures
together. The file was taken from this line and the change re-applied
by hand; the resulting diff against the pre-merge tip is the metering
change and nothing else.

The rate limiter itself needed no adaptation: src/node/rate_limit.rs
was byte-identical on both lines, so the guard, the second bucket and
the derivation merged in unchanged.

Eighteen explicit pending-slot releases in handle_msg1 are replaced by
a single guard held for the function's scope. Every one of the
eighteen either preceded a return or ended a match arm, and the
success path released at the end of the function, so the guard is
behaviour-equivalent. One ordering difference: the slot is now
released at function exit rather than immediately before each arm's
reject-stat bump. Nothing outside the handler reads the pending count
between those two points, so it is unobservable in tree, but it is an
ordering change rather than a pure refactor.

Four tests are ported from the other line, one with its wire-module
import path adjusted for this line's layout.

Known coverage gap: no test distinguishes the guard being held from
the guard being released at acquire time. Rebinding `_slot` to a bare
`_` leaves the whole suite green and draws no clippy warning, so the
invariant rests on the binding name. Noted at the site.
This commit is contained in:
Johnathan Corgan
2026-07-29 18:03:02 +00:00
9 changed files with 967 additions and 109 deletions
+15
View File
@@ -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.*`)