mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Merge branch 'next' into refactor-hotpath-next
This commit is contained in:
+5
-1
@@ -323,7 +323,11 @@ with v0.3.x peers.
|
||||
the value is local CPU / allocation savings (no `ActivePeer` build,
|
||||
no `peers_by_index` insert, no link transition) and cleaner peer-
|
||||
side semantics — no fake-promotion whose subsequent data frames
|
||||
fail decryption on this side.
|
||||
fail decryption on this side. Note the known-peer / cross-connection
|
||||
bypass also covers peers the node is itself dialing (e.g. configured
|
||||
`auto_connect` peers): for those the cap is enforced by the retained
|
||||
late check, so the early drop primarily benefits unsolicited inbound
|
||||
peers the node is not contacting.
|
||||
- Mesh-size estimator (`compute_mesh_size`) no longer double-counts the
|
||||
parent's bloom cardinality during the transient cache window after a
|
||||
local parent-switch. Symptom: `fipsctl show status` / fipstop displayed
|
||||
|
||||
@@ -63,6 +63,8 @@ services:
|
||||
profiles: ["mesh"]
|
||||
container_name: fips-node-c
|
||||
hostname: node-c
|
||||
environment:
|
||||
- RUST_LOG=info,fips::node::handlers::handshake=debug
|
||||
volumes:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
- ./generated-configs/mesh/node-c.yaml:/etc/fips/fips.yaml:ro
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Integration test for the inbound max_peers admission gate.
|
||||
# Integration test for the inbound max_peers admission cap (Noise XX / next).
|
||||
#
|
||||
# Verifies the silent-drop behavior of the early-gate in handle_msg1 at
|
||||
# scale, using the mesh topology with one node's node.max_peers lowered
|
||||
# to 1. This forces 2 of node-c's 3 configured peers (b, d, e) into a
|
||||
# sustained denied state, and asserts via tcpdump that no Msg2 responses
|
||||
# go back to those denied peers across a 60s capture window.
|
||||
# Verifies the cap holds under sustained denied-peer load, using the mesh
|
||||
# topology with one node's node.limits.max_peers lowered to 1. This forces
|
||||
# 2 of node-c's 3 configured peers (b, d, e) into a sustained denied state.
|
||||
#
|
||||
# Unlike the IK version on maint/master — which gates at handle_msg1 before
|
||||
# Msg2 and asserts "no Msg2 to denied peers" — on Noise XX the identity is
|
||||
# not known until Msg3, so Msg1/Msg2/Msg3 all cross the wire before the cap
|
||||
# can act. Moreover, because the cap'd node also dials its configured peers
|
||||
# (auto_connect), denied peers match the early handle_msg3 gate's
|
||||
# is_pending_outbound bypass and the cap is enforced by the late
|
||||
# promote_connection check. The test therefore asserts a path-agnostic
|
||||
# cap-holds invariant rather than a wire-size discriminator:
|
||||
#
|
||||
# Tested behavior:
|
||||
# - Denied peers DO arrive at the cap'd node (inbound FMP-IK Msg1, 84 B)
|
||||
# - Cap'd node sends NO Msg2 responses (104 B) to denied peers
|
||||
# - Denied peers keep re-initiating handshakes (sustained inbound UDP)
|
||||
# - No denied peer is ever promoted to an active session
|
||||
# - The daemon actively refuses over-cap promotions (enforcement events)
|
||||
# - Cap'd node maintains exactly max_peers active sessions
|
||||
# - Admitted peer's session stays healthy throughout the window
|
||||
#
|
||||
# Usage:
|
||||
# ./admission-cap-test.sh Run the test (containers must be up)
|
||||
@@ -161,26 +168,31 @@ wait $LOAD_PID 2>/dev/null || true
|
||||
captured=$(wc -l < "$CAP_FILE")
|
||||
info "captured $captured tcpdump lines → $CAP_FILE"
|
||||
|
||||
# ── Phase 3: per-denied-peer wire-level assertion ────────────────────
|
||||
info "phase 3: per-denied-peer assertion (inbound Msg1 > 0, outbound Msg2 == 0)"
|
||||
# ── Phase 3: per-denied-peer cap-holds assertion ─────────────────────
|
||||
# Size-agnostic: the XX handshake message sizes differ from IK and may
|
||||
# change, so we only need evidence that each denied peer keeps trying and
|
||||
# is never promoted.
|
||||
info "phase 3: per-denied-peer assertion (sustained inbound retries > 0, not promoted)"
|
||||
OVERALL=0
|
||||
TOTAL_MSG1_IN=0
|
||||
TOTAL_MSG2_OUT=0
|
||||
TOTAL_IN=0
|
||||
FINAL_PEERS=$(docker exec fips-node-$CAP_NODE fipsctl show peers 2>/dev/null \
|
||||
| grep -oE 'npub1[a-z0-9]+' | sort -u || true)
|
||||
for n in $DENIED; do
|
||||
n_ip=$(node_ip "$n")
|
||||
# Inbound: src=n_ip:* → dst=cap_ip:2121; FMP-IK Msg1 wire size = 84 B
|
||||
msg1_in=$(grep -cE "IP $n_ip\.[0-9]+ > $CAP_IP\.2121: UDP, length 84" "$CAP_FILE" || true)
|
||||
# Outbound: src=cap_ip:2121 → dst=n_ip:*; FMP-IK Msg2 wire size = 104 B
|
||||
msg2_out=$(grep -cE "IP $CAP_IP\.2121 > $n_ip\.[0-9]+: UDP, length 104" "$CAP_FILE" || true)
|
||||
info " node-$n ($n_ip): inbound Msg1 (len 84) = $msg1_in, outbound Msg2 (len 104) = $msg2_out"
|
||||
TOTAL_MSG1_IN=$((TOTAL_MSG1_IN + msg1_in))
|
||||
TOTAL_MSG2_OUT=$((TOTAL_MSG2_OUT + msg2_out))
|
||||
if [ "$msg1_in" -eq 0 ]; then
|
||||
info " FAIL: expected inbound Msg1 retries from denied peer (peer not sustained-retrying?)"
|
||||
n_npub=$(node_npub "$n")
|
||||
# Sustained retry: any inbound UDP from the denied peer to the cap'd
|
||||
# node, regardless of handshake message size.
|
||||
in_count=$(grep -cE "IP $n_ip\.[0-9]+ > $CAP_IP\.2121:" "$CAP_FILE" || true)
|
||||
promoted="no"
|
||||
if echo "$FINAL_PEERS" | grep -q "$n_npub"; then promoted="yes"; fi
|
||||
info " node-$n ($n_ip): inbound packets = $in_count, promoted = $promoted"
|
||||
TOTAL_IN=$((TOTAL_IN + in_count))
|
||||
if [ "$in_count" -eq 0 ]; then
|
||||
info " FAIL: no inbound from denied peer (peer not sustained-retrying?)"
|
||||
OVERALL=1
|
||||
fi
|
||||
if [ "$msg2_out" -gt 0 ]; then
|
||||
info " FAIL: silent-drop gate leaked — expected 0 outbound Msg2, got $msg2_out"
|
||||
if [ "$promoted" = "yes" ]; then
|
||||
info " FAIL: denied peer was promoted to an active session — cap leaked"
|
||||
OVERALL=1
|
||||
fi
|
||||
done
|
||||
@@ -189,17 +201,35 @@ done
|
||||
pc_final=$(docker exec fips-node-$CAP_NODE fipsctl show status 2>/dev/null \
|
||||
| grep -m1 peer_count | sed 's/.*: *//' | tr -d ',' || echo 0)
|
||||
info "node-$CAP_NODE final peer_count=$pc_final (expected $MAX_PEERS)"
|
||||
[ "$pc_final" = "$MAX_PEERS" ] || OVERALL=1
|
||||
[ "$pc_final" = "$MAX_PEERS" ] || { info " FAIL: peer_count drifted from cap"; OVERALL=1; }
|
||||
|
||||
# ── Phase 5: daemon actively refused over-cap promotions ─────────────
|
||||
# Path-agnostic enforcement evidence. The early handle_msg3 gate logs
|
||||
# "Silent-dropping Msg3 at max_peers cap" (debug); the late
|
||||
# promote_connection check logs "Failed to promote inbound connection"
|
||||
# (warn, "max peers exceeded"). In the mesh topology denied peers are
|
||||
# also dialed by the cap'd node, so the late check is the active enforcer
|
||||
# — but counting both keeps the test correct regardless of which path
|
||||
# fires (and survives a future change to the early gate).
|
||||
clogs=$(docker logs fips-node-$CAP_NODE 2>&1 || true)
|
||||
gate_drops=$(echo "$clogs" | grep -c "Silent-dropping Msg3 at max_peers cap" || true)
|
||||
late_rejects=$(echo "$clogs" | grep -c "Failed to promote inbound connection" || true)
|
||||
enforcement=$((gate_drops + late_rejects))
|
||||
info "enforcement events on node-$CAP_NODE: early-gate=$gate_drops, late-check=$late_rejects (total $enforcement)"
|
||||
[ "$enforcement" -gt 0 ] || { info " FAIL: no cap-enforcement events observed in node-$CAP_NODE logs"; OVERALL=1; }
|
||||
|
||||
if [ "$OVERALL" -eq 0 ]; then
|
||||
pass "admission-cap: silent-drop gate verified at scale"
|
||||
pass "admission-cap: cap holds under sustained denied-peer load"
|
||||
pass " denied peers: $(echo $DENIED | wc -w), capture: ${CAPTURE_SECS}s"
|
||||
pass " total inbound Msg1 from denied: $TOTAL_MSG1_IN (sustained retries observed)"
|
||||
pass " total outbound Msg2 to denied: $TOTAL_MSG2_OUT (silent-drop holds)"
|
||||
pass " total inbound from denied: $TOTAL_IN (sustained retries observed)"
|
||||
pass " cap enforcement events: $enforcement (early-gate $gate_drops + late-check $late_rejects)"
|
||||
pass " cap'd node held peer_count=$pc_final (max=$MAX_PEERS)"
|
||||
rm -f "$CAP_FILE"
|
||||
exit 0
|
||||
else
|
||||
info "--- tcpdump capture tail (last 50 lines) ---"
|
||||
tail -50 "$CAP_FILE"
|
||||
info "--- node-$CAP_NODE log tail (last 30 lines) ---"
|
||||
echo "$clogs" | tail -30
|
||||
fail "admission-cap: see failures above (capture preserved at $CAP_FILE)"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user