mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Companion to the ethernet `accept_connections: false` rekey-deadlock fix from earlier this release: the same dual-init failure mode shows up over UDP when peers register by hostname, and the existing addr_to_link-only carve-out in `should_admit_msg1` doesn't cover it. The carve-out's first predicate keys `addr_to_link` by the literal `TransportAddr` that `initiate_connection` inserted, which is the hostname-form when a peer config carries a hostname (e.g., `core-vm.tail65015.ts.net:2121`). Inbound packets always arrive with numeric source addrs because `udp_receive_loop` builds the `TransportAddr` from the `SocketAddr` the kernel reports via `recvfrom`. `TransportAddr` equality is byte-exact, so the two forms don't match and the lookup misses. With `udp.accept_connections: false` (or `udp.outbound_only: true`, which forces it false) the gate then rejects the rekey msg1 from an established peer. The dual-init tie-breaker stalls because the loser side never produces msg2; both sides retry indefinitely and the winner side keeps logging "Dual rekey initiation: we win, dropping their msg1" at 1Hz. The earlier ethernet fix didn't generalize to this variant because ethernet TransportAddrs are always numeric MAC bytes — both the config-time form and the inbound-arrival form match identically. Add a second predicate to `should_admit_msg1`: an active peer's `current_addr()` matching `(transport_id, remote_addr)`. `current_addr` is updated and refreshed from inbound encrypted-frame source addrs (`handlers/encrypted.rs`), which are always numeric `SocketAddr`-form, so this catches the established peer regardless of how its `addr_to_link` key was originally inserted. The fast `addr_to_link` check stays first; the iteration over peers is bounded by peer count and only runs when the first predicate misses. Regression coverage in this commit: - Unit test `test_should_admit_msg1_admits_rekey_when_addr_form_differs` in `src/node/tests/handshake.rs`. Constructs the failing scenario in-process: `addr_to_link` populated with hostname-form key, peer's `current_addr` at the resolved numeric form, query with numeric form. Without the new predicate this fails immediately. - New integration topology `rekey-outbound-only` plus matching docker-compose profile. Same 5-node mesh shape as `rekey-accept-off` but `inject-config` sets `udp.outbound_only: true` on node-b and rewrites node-b's peer-c address from the numeric docker IP to the docker hostname (`node-c:2121`), reproducing the production hostname-vs-numeric mismatch. The test asserts no sustained "Dual rekey initiation: we win" log lines on any node (>10 = bug) and the existing rekey health checks catch the connectivity loss the loop produces. - `testing/ci-local.sh` and `.github/workflows/ci.yml` extended to run the new variant in the local sweep and the GitHub CI integration matrix alongside `rekey` and `rekey-accept-off`. Verified locally: full `bash testing/ci-local.sh` sweep passes 29/29 suites (23m 12s) with the new variant green; 1084 unit tests pass.
54 lines
2.3 KiB
YAML
54 lines
2.3 KiB
YAML
# Rekey Integration Test Topology — udp.outbound_only variant.
|
|
#
|
|
# Same sparse mesh as rekey.yaml. Configs are post-processed to use
|
|
# aggressive rekey timers (35s, same as the parent) AND to set
|
|
# `transports.udp.outbound_only: true` on node b — the single-peer
|
|
# node, auto-connected to c. **Crucially**, node-b's peer-c address is
|
|
# also rewritten from the numeric docker IP to the docker hostname
|
|
# (`node-c:2121`), reproducing the production scenario where peer
|
|
# configs carry hostnames (e.g., `core-vm.tail65015.ts.net:2121`) and
|
|
# the literal hostname-form `TransportAddr` ends up as the
|
|
# `addr_to_link` key while inbound packets arrive with numeric source
|
|
# addresses. With the old `should_admit_msg1` carve-out, the
|
|
# hostname-vs-numeric form mismatch makes the lookup miss, falls
|
|
# through to `accept_connections() == false` (forced by outbound_only),
|
|
# and rejects the rekey msg1. The dual-init tie-breaker on node-c then
|
|
# stalls in a 1 Hz "we win (smaller addr), dropping their msg1" loop.
|
|
#
|
|
# This topology pins that regression alongside `rekey-accept-off`,
|
|
# which exercises the same Node-level gate but with numeric peer
|
|
# configs. The unit test
|
|
# `test_should_admit_msg1_admits_rekey_when_addr_form_differs` is the
|
|
# fast-feedback complement.
|
|
|
|
nodes:
|
|
a:
|
|
nsec: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
|
|
npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
|
docker_ip: "172.20.0.10"
|
|
peers: [d, e]
|
|
|
|
b:
|
|
nsec: "b102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fb0"
|
|
npub: "npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le"
|
|
docker_ip: "172.20.0.11"
|
|
peers: [c]
|
|
|
|
c:
|
|
nsec: "c102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fc0"
|
|
npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
|
docker_ip: "172.20.0.12"
|
|
peers: [b, d, e]
|
|
|
|
d:
|
|
nsec: "d102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fd0"
|
|
npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
|
docker_ip: "172.20.0.13"
|
|
peers: [a, c, e]
|
|
|
|
e:
|
|
nsec: "e102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fe0"
|
|
npub: "npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4"
|
|
docker_ip: "172.20.0.14"
|
|
peers: [a, c, d]
|