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>
Tailscale
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:
- the Android app is at https://github.com/tailscale/tailscale-android
- the Synology package is at https://github.com/tailscale/tailscale-synology
- the QNAP package is at https://github.com/tailscale/tailscale-qpkg
- the Chocolatey packaging is at https://github.com/tailscale/tailscale-chocolatey
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:
- https://github.com/tailscale/tailscale/graphs/contributors
- https://github.com/tailscale/tailscale-android/graphs/contributors
Legal
WireGuard is a registered trademark of Jason A. Donenfeld.