James Tucker fc66c4beb9 tstest/natlab/vmtest: add peer relay suspend/resume scenario
Add TestPeerRelaySuspendResume, a full-stack VM reproduction of the
literal repro steps from #20082: two nodes (HardNAT, direct impossible)
exchange traffic over a peer relay (One2OneNAT, PeerRelayGrants), one
node suspends (QMP stop) for longer than the relay session's
steady-state lifetime, the relay reaps the idle session, the node
wakes, and traffic must recover. The issue's 5m+ steady-state reap is
compressed to seconds by shrinking the relay server's endpoint
lifetimes to bind=5s (also the endpoint GC tick) and steadyState=15s
via the debug-peer-relay-server-lifetimes LocalAPI debug action
(local.Client.DebugSetPeerRelayServerLifetimes on the relay node's
agent client), invoked right after EnableRelayServer and before the
relay path is established, since server endpoints are advertised with
the lifetime values in effect at allocation time. The suspended
client's session is then reaped within 20s (verified server-side by
polling DebugPeerRelaySessions for the recorded VNI; observed 18s
across runs). Suspend is 40s total; recovery is asserted as TSMP a->b
within 90s of resume, then b->a within 30s, then peer-relay route
re-establishment within the sibling tests' 60s budget. The budget math
is documented inline.

Pre-suspend traffic is shaped, with legitimate traffic only, so that
both sides' wireguard-go peers hold a magicsock *lazyEndpoint frozen on
the relay session epAddr at the suspend instant (the state the
in-process repro, magicsock.TestPeerRelaySessionReapRecovery, forces by
rewinding endpoint.lastRecvWG): 12s of bidirectional TSMP pings (>=1
connection-noted verification handoff), a 25s quiesce (KeepaliveTimeout
10s + connection-noted threshold 10s + margin), then a single final
ICMP ping issued by the to-be-suspended node. Empirically determined
details that the shaping depends on, documented in the test:

 * A LocalAPI TSMP ping always sends a TSMP disco-key advertisement
   data packet right behind the echo request (wgengine.Ping ->
   sendTSMPDiscoAdvertisement), so a TSMP ping receiver always sees two
   back-to-back WireGuard packets and the second replaces the
   just-planted lazyEndpoint with the healthy endpoint; the final ping
   must be ICMP (one packet each way).
 * magicsock's periodic disco-key advertisements (2min cadence anchored
   at first contact, riding the ~5s call-me-maybe cadence relay paths
   sustain) land unpredictably relative to the suspend instant and
   rescue the receiver the same way; they are disabled on a and b via
   TS_USE_CACHED_NETMAP=false.
 * QEMU does not drop packets sent to a stopped VM: they queue in the
   netdev socket and are delivered at wake, when the guest's frozen
   CLOCK_MONOTONIC makes its last WG receive look ~1s old, so any
   queued transport packet un-poisons the woken node. The final ping
   therefore comes from the suspendee, freezing the passive keepalive
   it schedules inside the suspended VM instead of queueing a rescuing
   keepalive toward it.

On current main the test FAILS deterministically (7/7 local runs
across the envknob- and LocalAPI-based lifetime stacks, ~183s each) at
the recovery assertion, with the issue's exact signature: the
suspended-side session is reaped 18s after suspend, both directions of
TSMP traffic black-hole for the full 90s window, the awake node's
wireguard-go retries "Sending handshake initiation" into the dead
relay session every 5s throughout, and the disco plane meanwhile
stays healthy (disco pings succeed via DERP, fresh relay sessions get
allocated but never bind). Earlier, less-shaped variants of the
scenario (final TSMP ping, or final ping from the awake side)
self-recovered 7-21s after resume via the rescue mechanisms above,
which is what motivated pinning them down. Post-fix, recovery should
come via DERP fallback within seconds, followed by relay path
re-establishment.

a and b run with TS_LOG_VERBOSITY=2 and vnet.VerboseSyslog (the relay
with VerboseSyslog) so failures carry the wireguard-go handshake-retry
evidence in the test log.

Updates #20082

Change-Id: Ifa809daa1fa9b6fc19adacbd89c01215f9db8464
Signed-off-by: James Tucker <james@tailscale.com>
2026-06-11 02:59:21 +00:00
2026-06-01 12:09:49 -07:00
2026-06-08 09:58:54 -07:00
2026-01-27 16:15:17 -08:00
2026-06-02 10:59:29 -07:00

Tailscale

https://tailscale.com

Private WireGuard® networks made easy

Overview

This repository contains the majority of Tailscale's open source code. Notably, it includes the tailscaled daemon and the tailscale CLI tool. The tailscaled daemon runs on Linux, Windows, macOS, and to varying degrees on FreeBSD and OpenBSD. The Tailscale iOS and Android apps use this repo's code, but this repo doesn't contain the mobile GUI code.

Other Tailscale repos of note:

For background on which parts of Tailscale are open source and why, see https://tailscale.com/opensource/.

Using

We serve packages for a variety of distros and platforms at https://pkgs.tailscale.com.

Other clients

The macOS, iOS, and Windows clients use the code in this repository but additionally include small GUI wrappers. The GUI wrappers on non-open source platforms are themselves not open source.

Building

We always require the latest Go release, currently Go 1.26. (While we build releases with our Go fork, its use is not required.)

go install tailscale.com/cmd/tailscale{,d}

If you're packaging Tailscale for distribution, use build_dist.sh instead, to burn commit IDs and version info into the binaries:

./build_dist.sh tailscale.com/cmd/tailscale
./build_dist.sh tailscale.com/cmd/tailscaled

If your distro has conventions that preclude the use of build_dist.sh, please do the equivalent of what it does in your distro's way, so that bug reports contain useful version information.

Bugs

Please file any issues about this code or the hosted service on the issue tracker.

Contributing

PRs welcome! But please file bugs. Commit messages should reference bugs.

We require Developer Certificate of Origin Signed-off-by lines in commits.

See commit-messages.md (or skim git log) for our commit message style.

About Us

Tailscale is primarily developed by the people at https://github.com/orgs/tailscale/people. For other contributors, see:

WireGuard is a registered trademark of Jason A. Donenfeld.

Description
No description provided
Readme BSD-3-Clause 166 MiB
Languages
Go 95.8%
C 1.5%
TypeScript 1%
Shell 0.5%
Swift 0.3%
Other 0.5%