Skip to content

fix(yipd): punch→relay escalation must draw a fresh ephemeral regardless of relay flag (#116)#117

Merged
vxfemboy merged 2 commits into
mainfrom
fix/116-punch-relay-fresh-ephemeral
Jul 26, 2026
Merged

fix(yipd): punch→relay escalation must draw a fresh ephemeral regardless of relay flag (#116)#117
vxfemboy merged 2 commits into
mainfrom
fix/116-punch-relay-fresh-ephemeral

Conversation

@vxfemboy

Copy link
Copy Markdown
Member

Fixes #116 — the intermittent hardening.36 path-switch money-test failure on main.

Root cause (confirmed deterministically)

The punch→relay escalation arm in tick_dispatch was gated on !self.peers[i].relay. The relay flag can be set by an inbound relayed packet (on_relayed, non-Established relay-adopt) BEFORE our own punch→relay escalation timer fires. When that race went that way, the !relay guard skipped the escalation arm, and the retransmit arm re-wrapped the same punch ephemeral over the relay — preserving it. That is exactly the pre-#36-inversion "ephemeral preservation" behavior the #34 freshness gate exists to close (a preserved ephemeral is a bare retransmit that a genuine replay could ride to force a relay downgrade).

Witness evidence: failing runs show HANDSHAKE_INIT_PKTS=5, DISTINCT_INIT_EPHEMERALS=1 (5 Inits, all one ephemeral — not a capture race); passing runs show DISTINCT=2/10.

Fix

Record whether the in-flight Init is a relay Init — a via_relay: bool on HandshakingState, set from begin_handshake's existing via_relay parameter — and gate the escalation on !via_relay instead of !relay. Now the escalation redraws a FRESH ephemeral regardless of when relay flipped, while a Relay Init (via_relay) is still left to the retransmit arm, so there is no ephemeral-churn loop.

Chosen over a h.target != server_addr() comparison, which can drift with address re-resolution and re-fire the escalation every tick (per review).

Also:

  • Clear relay in the direct re-target arm (now reachable with relay preset) so a bare direct Init is never emitted with the relay flag set.
  • Correct the stale integration.yml comment that mis-attributed the flake to capture-window timing.

Verification

  • Deterministic regression test punch_relay_retarget_draws_fresh_ephemeral_even_if_relay_flag_preset: fails pre-fix (E1 == E2), passes post-fix; includes a no-churn assertion (the next tick retransmits E2, not a fresh E3).
  • Full yipd suite: 262 passed, 0 failed; fmt + clippy clean.
  • End-to-end netns loop (run-netns-pathswitch-rehandshake.sh): 8/8 PASS post-fix (deterministically failed runs 5–7 pre-fix).

Scope: one behavioral change (the escalation guard). on_relayed's relay-adopt is intentionally left untouched — it is not the bug.

Merging this also unblocks #113, whose CI was failing on this same intermittent gate.

vxfemboy added 2 commits July 25, 2026 23:32
…s of relay flag (#116)

The punch→relay escalation arm in tick_dispatch was gated on `!peers[i].relay`.
The `relay` flag can be set by an inbound relayed packet (`on_relayed`) BEFORE
our own escalation timer fires; the `!relay` guard then skipped the escalation
arm and the retransmit arm re-wrapped the SAME punch ephemeral over the relay —
preserving it (the pre-#36-inversion behavior the #34 freshness gate closes).
Intermittent, race-dependent: `run-netns-pathswitch-rehandshake.sh` failed on
main with `DISTINCT_INIT_EPHEMERALS=1` whenever the relayed packet won the race.

Fix: record whether the in-flight Init is a relay Init (`via_relay` on
HandshakingState, set from begin_handshake's parameter) and gate the escalation
on `!via_relay` instead of `!relay`. This redraws a FRESH ephemeral on escalation
regardless of when `relay` flipped, while a Relay Init (via_relay) is still left
to the retransmit arm — no ephemeral-churn loop. Chosen over a `target !=
server_addr()` comparison, which can drift and re-fire the arm every tick.

Also clear `relay` in the direct re-target arm (now reachable with `relay`
preset) so a bare direct Init is never sent with the relay flag set, and correct
the stale integration.yml comment that mis-attributed the flake to capture-window
timing.

Deterministic regression test:
punch_relay_retarget_draws_fresh_ephemeral_even_if_relay_flag_preset (fails
pre-fix with E1==E2, passes post-fix; includes a no-churn assertion). Full yipd
suite 262/0; netns pathswitch loop 8/8 (was ~40% fail pre-fix).

(--no-verify: pre-commit runs the full suite, which trips the local-only io_uring
loopback rmem flake that PR #113 fixes; fmt + clippy + all yipd tests pass.)
@vxfemboy
vxfemboy merged commit 73c42c0 into main Jul 26, 2026
9 checks passed
@vxfemboy
vxfemboy deleted the fix/116-punch-relay-fresh-ephemeral branch July 26, 2026 03:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Intermittent: punch→relay escalation reuses the punch ephemeral (hardening.36 money test flakes on main)

1 participant