Merge master into next (rekey-test ping retry, platform-warning cleanup, CHANGELOG backfills)

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Johnathan Corgan
2026-05-18 01:47:51 +00:00
5 changed files with 102 additions and 14 deletions
+48
View File
@@ -149,6 +149,19 @@ with v0.3.x peers.
passes 70/70. The constant and surrounding draw/redraw machinery
are kept in place pending diagnosis of why XX cutover state cleanup
doesn't absorb variable-interval rekeys the way IK does.
- macOS UDP receive path now batches up to 32 datagrams per kernel
wakeup via `recvmsg_x(2)`, matching the Linux `recvmmsg(2)`
amortization shape introduced in v0.3.0. Previously macOS fell
through to single-packet `recv_from`, capping inbound rate on
Apple builds with the same per-syscall + per-task-wakeup overhead
Linux had already eliminated. `recvmsg_x` is an xnu-private syscall
declared via `unsafe extern "C"` against a local repr(C)
`msghdr_x`; same approach used by `quinn-udp`. Same
`(count, kernel_drops)` contract as the Linux path, with
`kernel_drops` always 0 on macOS (no `SO_RXQ_OVFL` equivalent).
Bench numbers on aarch64-apple-darwin (100B payloads, 3 s
windows): 1 sender 1.09x, 2 senders 1.72x, 4 senders 1.56x,
8 senders 1.46x.
- Receive hot path: removed two per-packet copies. New borrowed
`SessionDatagramRef` decoder is used in the forwarding handler so
local delivery and coordinate-cache warming no longer allocate or
@@ -161,9 +174,44 @@ with v0.3.x peers.
formatted directly from the `SocketAddr` without an intermediate
`String`. Focused decode bench: ref 1.6 ns/op vs owned 34.7 ns/op
(21.4x).
- Quieted non-Linux test-build warnings from intentionally
platform-specific code: the nftables firewall parser
(`#[allow(dead_code)]` now gated to non-Linux targets where the
parser is compiled but unused), the macOS `utun` address-family
helper and the long TUN reader entry point (narrow allowances),
and a macOS Ethernet test module's clippy struct-layout lint
(rewritten MAC-copy loop, explicit layout annotation). No
behavioral change; the goal is to keep `cargo test` and
`cargo clippy` clean on cross-platform builds so unrelated
warning fixes don't get bundled into behavioral PRs.
### Fixed
- Coord cache invalidation made surgical at parent-position-change
and root-change sites. Replaces the previous unconditional
`CoordCache::clear()` calls with two targeted methods:
`invalidate_via_node(node_addr)` (drops entries whose cached
ancestry contains the changed node, used at parent-switch /
become-root / loop-detection sites) and `invalidate_other_roots`
(drops entries from a different tree, used at root-change sites).
The previous global flush left `find_next_hop` returning `None`
for every non-direct-peer destination after every parent switch
until the cache passively re-warmed; surgical invalidation
preserves entries that remain correct across the topology change.
Peer-removal retains the original "no invalidation" behavior
(`find_next_hop` already recomputes against the current peer set
every call, and Discovery handles "no route" on demand).
- Rekey integration test (`testing/static/scripts/rekey-test.sh`):
Phase 1, Phase 3, and Phase 5 strict per-pair pings now retry up
to 4 attempts (configurable via `MAX_PING_ATTEMPTS` /
`PING_RETRY_DELAY`). Under low-level packet loss (1% per
direction), single-shot 20-pair ping_all misses with probability
~33% per phase from ICMP noise alone, masking the routing-state
signal the asserts target. The 4-attempt retry brings that floor
to ~3.2e-6 per phase. The `wait_for_full_baseline` convergence
loop itself stays single-shot — retries there would conflate
transient ping loss with still-converging routing state. Test
scaffold only; no daemon code changes.
- Apply ±15s symmetric jitter per session to the FMP and FSP rekey
timer trigger. Eliminates the steady-state dual-initiation race
in symmetric-start meshes; previously the smaller-NodeAddr
+2
View File
@@ -31,6 +31,8 @@
//! and the ICMPv6-echo-request accept are not classified — they
//! don't pertain to listening TCP/UDP ports the operator binds.
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
use serde_json::Value;
use crate::control::listening::Proto;
+3 -2
View File
@@ -534,8 +534,8 @@ fn get_mac_addr(interface: &str) -> Result<[u8; 6], TransportError> {
// MAC address starts after the interface name in sdl_data
let data_ptr = sdl.sdl_data.as_ptr();
let mut mac = [0u8; 6];
for i in 0..6 {
mac[i] = unsafe { *data_ptr.add(nlen + i) } as u8;
for (i, byte) in mac.iter_mut().enumerate() {
*byte = unsafe { *data_ptr.add(nlen + i) } as u8;
}
return Ok(mac);
}
@@ -565,6 +565,7 @@ fn get_mac_addr(interface: &str) -> Result<[u8; 6], TransportError> {
#[cfg(test)]
#[cfg(target_os = "macos")]
#[allow(clippy::items_after_test_module)]
mod tests {
use super::*;
+2
View File
@@ -389,6 +389,7 @@ fn utun_af_inet6_header() -> [u8; 4] {
/// from the dup'd fd directly.
#[cfg(target_os = "macos")]
#[inline]
#[allow(dead_code)]
fn parse_utun_af_prefix(buf: &[u8]) -> Option<u32> {
if buf.len() < 4 {
return None;
@@ -565,6 +566,7 @@ impl Drop for ShutdownFd {
/// avoiding the need to close the TUN fd externally (which would cause a
/// double-close when `TunDevice` drops).
#[cfg(target_os = "macos")]
#[allow(clippy::too_many_arguments)]
pub fn run_tun_reader(
mut device: TunDevice,
mtu: u16,
+47 -12
View File
@@ -157,6 +157,23 @@ LOG_EVENT_POLL_INTERVAL=1
TIMEOUT=5
CONVERGENCE_PING_TIMEOUT=1
# Strict-ping retry policy for the per-phase ping_all asserts. Under 1%
# i.i.d. packet loss (the lab condition surfaced by ISSUE-2026-0028) a
# single-shot ping fails at roughly 2% per directed pair, so a 20-pair
# strict assert misses with probability ~1 - (0.98)^20 ≈ 33%, which is
# below the per-pair loss-math floor but well above the routing-state
# signal we want to measure. Retrying each failing pair up to
# MAX_PING_ATTEMPTS-1 additional times with ~PING_RETRY_DELAY between
# attempts drops the loss-math floor to ~(0.02)^MAX_PING_ATTEMPTS per
# pair, making any residual failure attributable to a non-loss
# mechanism rather than ICMP noise. Applied to Phase 1 (final strict
# ping_all after wait_for_full_baseline converges) and Phase 3 / Phase
# 5 (post-rekey strict asserts). The wait_for_full_baseline convergence
# loop itself stays single-shot — its job is to detect when the mesh
# first sees a fully clean 20-pair batch, and retries inside the loop
# would conflate "transient ping loss" with "still converging."
MAX_PING_ATTEMPTS=4
PING_RETRY_DELAY=1
PASSED=0
FAILED=0
TOTAL_PASSED=0
@@ -181,25 +198,43 @@ ping_one() {
local label="$3"
local quiet="${4:-}"
local ping_timeout="${5:-$TIMEOUT}"
local max_attempts="${6:-1}"
if output=$(docker exec "fips-$from" ping6 -c 1 -W "$ping_timeout" "${to_npub}.fips" 2>&1); then
local rtt=$(echo "$output" | grep -oE 'time=[0-9.]+' | cut -d= -f2)
if [ -z "$quiet" ]; then
echo " $label ... OK (${rtt:-?}ms)"
local attempt=1
local output rtt
while (( attempt <= max_attempts )); do
if (( attempt > 1 )); then
sleep "$PING_RETRY_DELAY"
fi
PASSED=$((PASSED + 1))
else
if [ -z "$quiet" ]; then
if output=$(docker exec "fips-$from" ping6 -c 1 -W "$ping_timeout" "${to_npub}.fips" 2>&1); then
rtt=$(echo "$output" | grep -oE 'time=[0-9.]+' | cut -d= -f2)
if [ -z "$quiet" ]; then
if (( attempt == 1 )); then
echo " $label ... OK (${rtt:-?}ms)"
else
echo " $label ... OK (${rtt:-?}ms, attempt $attempt)"
fi
fi
PASSED=$((PASSED + 1))
return
fi
attempt=$((attempt + 1))
done
if [ -z "$quiet" ]; then
if (( max_attempts > 1 )); then
echo " $label ... FAIL (after $max_attempts attempts)"
else
echo " $label ... FAIL"
fi
FAILED=$((FAILED + 1))
fi
FAILED=$((FAILED + 1))
}
# Run all 20 directed pairs
ping_all() {
local quiet="${1:-}"
local ping_timeout="${2:-$TIMEOUT}"
local max_attempts="${3:-1}"
PASSED=0
FAILED=0
for i in 0 1 2 3 4; do
@@ -209,7 +244,7 @@ ping_all() {
for j in 0 1 2 3 4; do
[ "$i" -eq "$j" ] && continue
ping_one "node-${LABELS[$i],,}" "${NPUBS[$j]}" \
"${LABELS[$i]}${LABELS[$j]}" "$quiet" "$ping_timeout"
"${LABELS[$i]}${LABELS[$j]}" "$quiet" "$ping_timeout" "$max_attempts"
done
done
}
@@ -332,7 +367,7 @@ echo ""
echo "Phase 1: Pre-rekey connectivity (waiting for convergence)"
wait_for_peers fips-node-a 2 "$BASELINE_CONVERGENCE_TIMEOUT" || true
if wait_for_full_baseline "$BASELINE_CONVERGENCE_TIMEOUT"; then
ping_all
ping_all "" "$TIMEOUT" "$MAX_PING_ATTEMPTS"
phase_result "Pre-rekey baseline (all 20 pairs)"
else
echo " Best observed baseline before timeout: $PASSED/$((PASSED + FAILED)) passed"
@@ -359,7 +394,7 @@ echo ""
# Verify connectivity after first rekey (strict — no failures allowed)
echo "Phase 3: Post-rekey connectivity (settling ${REKEY_SETTLE}s)"
sleep "$REKEY_SETTLE"
ping_all
ping_all "" "$TIMEOUT" "$MAX_PING_ATTEMPTS"
phase_result "Post-first-rekey (all 20 pairs)"
echo ""
@@ -370,7 +405,7 @@ sleep "$SECOND_REKEY_WAIT"
# Verify connectivity after second rekey (back-to-back)
echo "Phase 5: Post-second-rekey connectivity (settling ${REKEY_SETTLE}s)"
sleep "$REKEY_SETTLE"
ping_all
ping_all "" "$TIMEOUT" "$MAX_PING_ATTEMPTS"
phase_result "Post-second-rekey (all 20 pairs)"
echo ""