tailscale / wgengine/magicsock / v1.103.0-pre, 2026-09-18

Magicsock Path States

How a Tailscale node decides, per peer and per direction, whether to send over the direct UDP path or through its DERP relay, and how long each decision takes to change.

States and transitions

  • direct UDP carries traffic
  • direct and a DERP copy of every packet
  • DERP carries traffic
DIRECT, TRUSTED pong seen within the last 6.5 s sends: UDP only heartbeat ping every 3 s DIRECT, STALE no pong for 6.5 s, address kept sends: UDP plus a copy via DERP heartbeat 3 s, probe candidates DERP ONLY no usable direct address sends: via home DERP only probe candidates on every send no pong for 6.5 s two heartbeats in a row lost pong from the best address, or any candidate since v1.98 each pong renews trust for 6.5 s heartbeat continues every 3 s ping to the best address times out: 5 s after it was sent, which is about 1.5 s after going stale v1.98 and later only; earlier releases stay stale first pong from any candidate probe each candidate at most once per 5 s, on every send and every 3 s tick CallMeMaybe via DERP each round; the peer's CallMeMaybe probes everything at once entry: new peer, or from any state on major link change, rebind, UDP send error
Per peer, per direction. The direct path is used alone only while a trust window opened by the last pong is still running. Stale means the window closed but the address is kept: every packet goes both ways until a ping to that address times out. DERP only lasts until any candidate answers a probe. Nothing measures loss or throughput, and DERP replies never take part in choosing the best address. When nothing has been sent for 45 s the heartbeat stops, the state freezes, and the next packet restarts probing. Before v1.98 (May 2026) the timeout edge did not exist: a stale address was kept, with every packet copied to DERP, until a pong on it or a strictly better candidate arrived, and DERP only was reached solely through link changes and send errors.

One outage on the clock

The same 8.5 s outage, beginning 1 s after a successful heartbeat, with traffic flowing throughout, drawn once per behaviour.

v1.98 and later WiFi down in both directions, 1 s to 9.5 s path carrying this node's outbound packets to the peer direct only both DERP only direct only pong ping lost ping lost probe, pong pong pong trust window: 6.5 s after the pong at 0 expires: stale trusted again from 11 s, renewed by every later pong the ping sent at 3 s times out at 8 s: best address cleared 5 s per-candidate limit: the LAN address, pinged at 6 s, is probed again at 11 s 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 s
v1.98 and later. The heartbeats at 3 s and 6 s are lost. Trust expires at 6.5 s: every packet is now copied to DERP and the other candidates are probed, also without answer. At 8 s the lost 3 s ping times out and the address is cleared, so only DERP carries traffic and the heartbeat has no target. The WiFi returns at 9.5 s, but the LAN address was last pinged at 6 s and the per-candidate limit holds it until 11 s. That probe is answered, the direct path is restored on the next send, and the 3 s heartbeat rhythm resumes at 12 s. A CallMeMaybe from the peer would trigger the probe sooner.
v1.96 and earlier WiFi down in both directions, 1 s to 9.5 s path carrying this node's outbound packets to the peer direct only both: direct plus a DERP copy direct only pong ping lost ping lost ping lost pong pong trust window: 6.5 s after the pong at 0 expires: stale trusted again from 12 s, renewed by every later pong the ping sent at 3 s times out at 8 s: logged, nothing cleared heartbeat keeps targeting the stale address every 3 s: 9 s lost, 12 s answered 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 s
v1.96 and earlier, same outage. Trust expires at 6.5 s exactly as above and packets are copied to DERP, but nothing ever clears the stale address: the 8 s timeout only logs. The heartbeat keeps pinging the LAN address every 3 s, so the 9 s ping is lost inside the outage and the 12 s ping is answered, which refreshes trust on the same address. Direct plus a DERP copy lasts 5.5 s, and DERP never carries traffic alone. Recovery rides the 3 s heartbeat cadence rather than the 5 s discovery limit, which is why this older client recovers at 12 s where the newer one recovers at 11 s.

In both cases, outages shorter than about 3 s never trip the fallback and outages longer than 6 s always do. Both show a relay in tailscale status for the whole stale period, because that column reports only a trusted direct address.

The timers

ConstantValueWhat it governsSource
trustUDPAddrDuration6.5 sHow long a direct address is used alone after its last pong.magicsock.go:4014
heartbeatInterval3 sPing cadence to the best address while the session is active.magicsock.go:4010
pingTimeoutDuration5 sWait for a pong before a ping counts as lost; a lost ping to a stale best address clears it.tsconst/ping.go:11
discoPingInterval5 sMinimum gap between discovery probes of the same candidate.tsconst/ping.go:15
sessionActiveTimeout45 sOutbound silence after which heartbeats stop.magicsock.go:3994
upgradeUDPDirectInterval60 sHow often a trusted but slow path re-probes every candidate looking for a better one.magicsock.go:3998
goodEnoughLatency5 msAt or under this, the upgrade probes are skipped entirely.magicsock.go:4018
endpointsFreshEnoughDuration27 sAge at which this node re-STUNs before sending a CallMeMaybe.magicsock.go:4023

All of these are compile-time constants in wgengine/magicsock. No tailscale set or tailscale up flag changes them.

Where each transition lives

TransitionTriggerSource
trusted -> trustedA pong arrives for a ping to the best address; trust becomes now plus 6.5 s.endpoint.go:2006
trusted -> staleThe trust window passes with no pong. The send path starts returning both the direct and the DERP address.endpoint.go:632
stale -> DERP onlyA ping to the best address times out while the window is closed. The address is cleared.endpoint.go:1236
stale or DERP only -> trustedA pong arrives from any direct candidate; it becomes the best address.endpoint.go:1987
probing while not trustedEvery outbound packet and every 3 s heartbeat tick runs a discovery round, limited to one probe per candidate per 5 s, and queues a CallMeMaybe over DERP.endpoint.go:1101, endpoint.go:869, endpoint.go:1415
peer's CallMeMaybeIts endpoints are added as candidates and all of them are probed at once, ignoring the 5 s limit.endpoint.go:2138
any -> DERP onlyA major link change, a rebind, or a listening port change wipes the path state of every peer.magicsock.go:3834, net/netmon/netmon.go:199
any -> DERP onlyA UDP send to the best address fails with an unreachable-type error.endpoint.go:1848
heartbeat stopsNothing sent for 45 s. The next packet sent restarts probing on the send path itself.endpoint.go:869, endpoint.go:1014

What changed, and when

MechanismLandedFirst stable release
Disco pings, the trust window, send-to-both while stale, the idle cutoff2020-07v0.100
Timer values retuned once: heartbeat 2 s to 3 s, trust 5 s to 6.5 s, idle 2 min to 45 s. Unchanged since.2022-01v1.20
Private addresses preferred over public ones, with 1% latency hysteresis, in the candidate scoring2023-05v1.44
Silent disco toggle, off unless control enables it2023-11v1.54
UDP path lifetime probing after a session goes idle2024-01v1.60
Link-local addresses preferred over private ones2024-03v1.62
Peer relay servers as a tier between direct and DERP2025-05v1.84 to v1.86
Home DERP region kept through short packet loss until two keepalives fail2025-06v1.86
Ping timeout clears a stale peer-relay address2025-06v1.86
Ping timeout clears a stale direct address too, so traffic goes DERP only; any answering candidate replaces an untrusted best address2026-04v1.98
Trust reset when a peer rotates its disco key, forcing a re-probe2026-08unreleased, on main

The v1.98 change is the one that alters behaviour on a lossy link. Its commit message gives the reason: without it, a stale direct address that blackholes could be kept forever. Everything else in magicsock during 2026 concerns disco key rotation and advertisement over TSMP, netmap caching, and peer relay details, none of which touch the timers or the direct-versus-DERP decision.

Things the diagram leaves out