Skip to content

v0.7.1 — fix simultaneous-initiation handshake race (#20)

Choose a tag to compare

@GeiserX GeiserX released this 09 Jun 22:41
· 15 commits to main since this release

Fixed

Simultaneous-initiation handshake race no longer wedges relay-only peers (closes #20).

Two peers reachable only via a DERP relay (no direct path) that initiated at the same cadence deterministically failed to ever complete a handshakeHandshake::respond unconditionally overwrote an in-flight Initiated with Responded, so the peer's later HandshakeResponse failed finish()'s state guard → handshake failed to complete + session not found looped every ~5.5s forever. A direct path's timing jitter masks this; a relay-only idle path removes the jitter and the race becomes the steady state.

Fixed by mirroring canonical WireGuard (wireguard-go / boringtun / Linux kernel — none use a deterministic public-key tie-break, which would be interop-unsafe): the per-peer handshake now retains both an in-flight initiator slot and a responder slot at once, so an inbound initiation no longer destroys the pending one. Both handshakes complete; the responder session stays provisional (send-disabled) until a confirming transport packet; the existing receive-session rotation converges them. We always respond to a peer's msg1, so interop with real wireguard-go/Tailscale/kernel peers is unchanged — the Go transport-key KAT still passes byte-for-byte.

Internal change only — no public API change (patch release). Also hardened the flaky ts_forwarder UDP-timeout test against false reds on the loaded self-hosted CI runner.


This project is not associated with Tailscale Inc.