Engine.Reconfig previously diffed cfg.Peers disco keys against the
previous config to find restarted peers and flush their WireGuard
sessions, with a TSMP-learned-key map to suppress resets for key
changes that arrived over a working session. That was the last
per-peer state computed from wgcfg.Config.Peers inside the engine,
and it only ran on full reconfigs, so incremental netmap deltas
never got session resets at all.
Move the detection into nodeBackend, which sees every peer change:
full netmaps in SetNetMap and incremental upserts in
UpdateNetmapDelta both now report which peers changed disco keys,
with the same TSMP suppression and mismatch accounting as before.
LocalBackend acts on the result via a new Engine.ResetDevicePeer
method, which just removes the peer from the WireGuard device and
lets the peer lookup func lazily re-create it with fresh state.
LocalBackend.PatchDiscoKey now records TSMP-learned keys in
nodeBackend instead of forwarding to the engine, so the engine's
PatchDiscoKey method and tsmpLearnedDisco map are gone. The
controlclient patchDiscoKeyer interface becomes the exported
DiscoKeyUpdater so LocalBackend can compile-time assert that it
implements it, alongside its NetmapDeltaUpdater friends, replacing
the test that asserted the same of the engine.
This is one of the last steps toward removing Peers from wgcfg.Config.
Updates #12542
Change-Id: I6b42e460f42924816beae89ca43731cb91b66054
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Engine.PeerForIP was pure delegation to the callback that LocalBackend
installs via SetPeerForIPFunc, so external callers going through the
engine were taking a pointless round trip: LocalBackend called
b.e.PeerForIP, which called right back into LocalBackend, and the
netstack UseNetstackForIP hooks in tsnet and tailscaled did the same
dance one layer removed.
Export LocalBackend.PeerForIP and make those callers use it directly.
The engine-internal cold paths (Ping, TSMP disco advertisements,
pendopen diagnostics) still need the lookup and have no netmap of
their own, so SetPeerForIPFunc stays on the interface, but the lookup
method itself is now unexported and gone from the Engine interface.
In tailscaled the UseNetstackForIP hook moves from netstack setup to
just after the LocalBackend is created, since the backend doesn't
exist yet when netstack is wired up.
Updates #12542
Change-Id: Ib1e1a4fa5c84ee0dcb9ce5d1910047f2bab9453c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The engine kept its own longest-prefix-match table (peerByIPRoute),
rebuilt from the full peer list on every reconfig, to route outbound
packets and answer PeerKeyForIP. That's now the route manager's job:
LocalBackend already installs a PeerByIPPacketFunc backed by the
RouteManager's incrementally-maintained outbound table, so the
engine's copy was redundant state with redundant O(n peers) rebuild
work.
Delete the table, the PeerKeyForIP interface method, and the BART-only
default callback. LocalBackend's peerForIP now queries the
RouteManager's outbound table directly for the subnet-route and
exit-node fallback. Engines running without a LocalBackend (such as
wgengine/bench) must install their own outbound peer lookup, since the
device's standard AllowedIPs trie only covers peers that already
exist and can't lazily create them.
Updates #12542
Change-Id: I25100399e273ed6c2bb1f6136b7cd81bc83e7313
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Previously, any peer added or removed by an incremental netmap delta
was only visible to wireguard-go after a full authReconfig: wgcfg's
ReconfigDevice re-installed a PeerLookupFunc closing over a freshly
built map of every peer's allowed IPs, doing O(n) work per change.
Instead, install the wireguard-go device hooks once, backed by live
state. Engine.SetPeerConfigFunc installs a single long-lived
PeerLookupFunc that queries LocalBackend's per-node RouteManager on
demand, and Engine.SyncDevicePeer does O(1) per-peer device sync
(remove, or update allowed IPs) as each delta mutation is applied.
Full reconfigs keep an O(n peers) device sync for now, but with no
lookup closure to reinstall and no removed-peer resurrection race; a
later change removes full-config peer syncing entirely.
The RouteManager's PeerAllowedIPs accessor backs the new hooks: its
sorted output makes unchanged state a no-op update, and its peer
filtering mirrors nmcfg.WGCfg, so expired peers and peers predating
both DERP and disco contribute no prefixes and thus cannot be lazily
created in the device, which matters because wireguard-go validates
inbound source IPs against per-peer allowed IPs.
The engine's SetPeerByIPPacketFunc callback is now authoritative when
installed, since LocalBackend's implementation covers subnet routes
and exit-node routes via the RouteManager's outbound table; the
engine's own reconfig-time BART table only serves engines running
without a LocalBackend.
The forced authReconfig on peer add/remove stays for now: the
WireGuard device no longer needs it, but OS routes, the quad-100
resolver's MagicDNS hosts map, and tstun's masquerade/jailed peer
config are still derived from the full peer set. Making those
delta-aware is the next step before gating it.
Updates #12542
Change-Id: I3ba8c7c324bca0ad0269279d03f53b1f17fb63a2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
A connection to a Tailscale Service IP on a port the service does
not serve was forwarded to the underlying host. `acceptTCP` fell through to
the isTailscaleIP case (a VIP is in the Tailscale IP range), which rewrote
the dial target to 127.0.0.1:<port> and forwardTCP'd the connection onto
whatever unrelated listener happened to be on the host's loopback at that
port.
This is reachable through the service IP by any peer which was granted
access only to the service (dst: svc:foo), so it exposes host ports the
peer has no ACL access to via the machine's regular IP. This happens
when there tailscaled has a Tun interface and the forward bits are set.
In this commit, we added a guard in acceptTCP, before the isTailscaleIP case
that RSTs connections to a VIP service IP on a port with no serve handler.
Served ports return earlier via TCPHandlerForDst, so only unserved ports reach the guard.
Layer 3 services are unaffected: their traffic is released to the host in
injectInbound and never reaches acceptTCP.
Fixes#20362
Signed-off-by: kevinliang10 <kevinliang@tailscale.com>
[This commit is pulled out of a branch that ultimately removes the
wgcfg.Config.Peers field and removes all O(n peers) processing when
handling deltas]
magicsock.Conn.UpdatePeers existed so wgengine.Reconfig could tell
magicsock the set of WireGuard peers from cfg.Peers, used only to
garbage collect the derpRoute and peerLastDerp maps and to ReSTUN when
the first peers appear. magicsock already learns the full peer list
directly from LocalBackend via SetNetworkMap, UpsertPeer, and
RemovePeer, so do that bookkeeping there and delete the API and its
cfg.Peers use.
Updates #12542
Change-Id: Id07551fc1950239f08a73a9ab02d69ce78d0de0c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
wgcfg.Config.NetworkLogging carried the network flow logging identity
inside the WireGuard config, where it was unrelated to WireGuard; it
lived there mainly so that identity changes would defeat Reconfig's
ErrNoChanges check and reach the netlog startup/shutdown logic.
Remove the field and move the whole netlog lifecycle into a new
feature/netlog package, installed on the engine via the new
wgengine.HookNewNetLogger hook, like other feature/* packages. The
logging identity now comes from LocalBackend's current netmap via the
widened NetLogSource interface (replacing Engine.SetNetLogNodeSource),
so nmcfg no longer parses audit log IDs into the config. The engine
still calls the hook before its ErrNoChanges return and before
router.Set (to capture initial packets), and again after router.Set
(to capture final packets), preserving the previous ordering.
Core wgengine no longer imports wgengine/netlog, so minimal builds
drop it entirely. tailscaled keeps netlog via feature/condregister,
and tsnet imports feature/condregister/netlog explicitly to keep
netlog enabled by default in tsnet-based binaries (tsidp,
k8s-operator).
This is pulled out of a future change that removes wgcfg.Config.Peers,
to make that PR smaller.
Updates #12542
Updates #12614
Change-Id: I41ca7dfe43c51e977c41b5f8e934bd1f0e6e6e24
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Nothing uses them. DNS and MTU are handled elsewhere.
This is pulled out of a future change that removes wgcfg.Config.Peers,
to make that PR smaller.
Updates #12542
Change-Id: I2ec8ae38dc6cce08bcc44e6c1f9177311202af89
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
ExecQueue.Shutdown does not wait for a function that is already
executing, so Close could tear down magicConn, dns, wgdev, and tundev
while a queued linkChange was still using them, panicking during
shutdown. Add ExecQueue.ShutdownAndWait, which discards queued
functions that have not started and waits for the in-flight one, and
use it in Close with a bounded context before tearing anything down.
The eventbus client is closed first and is the queue's only producer,
so no new work can arrive after the drain.
Updates #17641
Change-Id: I0350bcb59c1ee4b0dcac88cf66b93828466c8c98
Signed-off-by: Adel-Ayoub <adelayoub.maaziz@gmail.com>
Found with the regex `\b([A-Za-z]+) \1\b`.
Updates #cleanup
Change-Id: I4cc51784d9b6437d3d0c66b531828707f87f7fd5
Signed-off-by: Alex Chan <alexc@tailscale.com>
The netstack GRO receive path validates L4 checksums before marking
packets as RX checksum validated for gVisor. That validation is invalid
for IPv4 fragments because TCP and UDP checksums cover the complete
reassembled transport packet, not an individual fragment.
Keep validating the IPv4 header checksum, but let IPv4 fragments through
to gVisor for reassembly without pre-validating TCP or UDP.
Fixes#20320
Change-Id: I779363a5e0ac5abee6a8e2a2a44b418fbc5f5e27
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
`TestNetworkSendErrors/network-down` causes a data race because it
tried to `tstest.Replace` the `checkNetworkDownDuringTests` global
while `wgengine.Conn.networkDown` would read from it. This patch moves
this flag into a field within the `wgengine.Conn` struct, so there’s
no chance that two tests could trample on each other.
It also renames this field to `Conn.checkNetworkUpDuringTests`,
because `Conn.networkUp` is the name of the field that gets checked.
Fixes#20260
Signed-off-by: Simon Law <sfllaw@tailscale.com>
Tests in magicsock_test.go would routinely emit this warning:
## WARNING: (non-fatal) nil health.Tracker (being strict in CI):
because they would run NewConn without initializing a health.Tracker.
This patch initializes Conn correctly with a health.Tracker. It also
fixes some missing Close calls that can be handled in t.Cleanup.
Fixes#20263
Signed-off-by: Simon Law <sfllaw@tailscale.com>
Move all the FooForTest methods on LocalBackend to instead be
methods on a new unexported forTest type which is then given out
to callers in other packages via an exported ForTest method
(panicking in non-test contexts) that returns that unexported type.
This is unusual style (exported returning unexported) but declutters
godoc and makes call sites both more explicit and easier to read
without the "ForTest" suffix polluting the symbols. Now FooForTest()
changes into ForTest().Foo().
This was motivated by a pending change moving a bunch of code out of
LocalBackend into other packages that required adding more ForTest
methods to LocalBackend to keep the tests (now in other packages)
working. Instead, do this refactor now so the future change is prettier.
Updates #12614
Updates #cleanup
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ib25e6d76d48dc8622ac3a955e0b1220d582e63a8
The engine only used the netmap to look up self addresses and the
self node's primary routes, so pass it the self node directly
rather than the whole netmap.
Updates #12542
Change-Id: I13c0028eed65d2177baf4cf6c449f5e441845a18
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Router.Set reconciled tailscale0's addresses only against the in-memory
r.addrs map, which starts empty each run. After a restart the kernel can
still hold the addresses a previous profile put on tailscale0. With no
record of them, Set never removed them, leaving two tailnets' CGNAT
addresses on the interface. That broke connectivity, because the kernel
could source traffic from the wrong IP.
Fix this by scanning the addresses actually on the interface and, after
reconciling the desired set, removing any in Tailscale's CGNAT/ULA ranges
that aren't in the config. Non-Tailscale addresses are never touched,
and IPv6 addresses are skipped when IPv6 is unavailable, since delAddress
no-ops there. To avoid a netlink dump on every Set, the scan runs only on
the first Set and when the desired address set changes.
This also needs the iptables DelLoopbackRule to tolerate a missing rule:
an orphan left by a previous instance never went through AddLoopbackRule
here, and iptables (unlike nftables) errors when deleting an absent
rule, which would otherwise block the address delete.
Fixes#19974
Signed-off-by: Brendan Creane <bcreane@gmail.com>
userspaceEngine.PeerForIP read from e.netMap.Peers and
e.lastCfgFull.Peers, both of which go stale when peers arrive via
netmap deltas (which skip Engine.SetNetworkMap and Engine.Reconfig).
Every PeerForIP caller (Engine.Ping, the TSMP disco-key handler,
pendopen diagnostics, tsdial.Dialer.UseNetstackForIP, and
LocalBackend.GetPeerEndpointChanges) would report "no matching peer"
for freshly-added peers.
Fix it the same way SetPeerByIPPacketFunc fixed the outbound packet
hot path: have LocalBackend install a callback that reads the live
nodeBackend. nb.NodeByAddr is built from both SelfNode and Peers
(updateNodeByAddrLocked), so a single lookup covers the common case
with IsSelf set when the matched node ID is SelfNode's. The subnet-
route / exit-node-default-route slow path goes through a new
Engine.PeerKeyForIP that exposes the engine's AllowedIPs BART table
(the same table the outbound packet hot path already consults, with
exit-node selection honored), and resolves the matched key back to a
NodeView via the live nodeBackend.
Updates #12542
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I0d4b0d8997c8e796b7367c46b49b61d4fdc717b0
Another baby step toward removing slices of peers from the engine.
getStatus iterated peerSequence (a key snapshot built in Reconfig
from cfg.Peers) and then asked wgdev for each peer's stats; peers
that weren't active in wgdev silently fell out. Iterate active wgdev
peers directly via RemoveMatchingPeers(returnFalse) instead.
Updates #12542
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I3abd348abc30db706db29b3a785179259e48abda
Otherwise we may never handshake a new peer relay server endpoint
around remote client restarts and/or disco key rotation.
Updates #20215
Signed-off-by: Jordan Whited <jordan@tailscale.com>
The watchdog (ipn/ipnlocal/watchdog.go) was abusing PeerForIP with an
invalid netip.Addr as a way to acquire and release the engine's
internal locks for deadlock detection. This does the TODO to break it out
into its own method like all the other similarly named methods.
Splitting this out as a prerequisite for a follow-up rewrite of
PeerForIP itself; not having to preserve the lock-probe overload in
the new implementation keeps that follow-up smaller.
Updates #12542
Updates #cleanup
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I25cbffd11aeb65600d9128845404c4918ef88ead
This applies the same treatment from 8f210454dd (netlog) to wglog,
ending use of netmap.NetworkMap and instead getting the canonical data
from LocalBackend/nodeBackend.
This is a dependency to removing the netmap.NetworkMap from
upstream callers, like wgengine.Engine in general.
Updates #12542
Change-Id: Icb5af0799322def048a6f594b49f7d11273f025d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Outbound packets produced by netstack (used by tailscaled with
--tun userspace-networking, by tsnet, and by the SOCKS5/HTTP proxies)
enter the wrapper via InjectOutbound{,PacketBuffer} and take the
injectedRead path, which bypasses Filter.RunOut.
RunOut's side effect for UDP/SCTP is to insert the reverse-flow tuple
into the connection-tracking LRU so that Filter.RunIn admits inbound
replies that no explicit ACL rule covers. Skipping it on the injected
path meant a netstack-side dial of UDP would send fine but the reply
would be dropped as "no matching rule". The kernel-TUN path was
already fine because it goes through RunOut.
Fixes#14229Fixes#20064
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I816ef55c493a12ff4f561cd89c095559b5c2743b
When recommending an exit node, suggestExitNodeLocked ranks candidates by
the latency to their home DERP region, taken from the most recent netcheck
report. But netcheck alternates between full reports, which probe every
region, and incremental reports, which only re-probe the home region and a
handful of the fastest regions. When the most recent report is incremental,
the suggestion fell back to a random for exit nodes that are far away.
Now we rank candidates against the best recent latency, tracked by the
`netcheck.Client` - the same data that is used to pick the preferred
DERP. It uses a history of measurements which includes a full netcheck
report, so should cover all DERP regions.
Updates tailscale/corp#17516
Signed-off-by: Anton Tolchanov <anton@tailscale.com>
The Logger previously took a *netmap.NetworkMap at Startup and on every
ReconfigNetworkMap call, denormalizing it into per-IP and self lookup
maps. That denormalization is O(n) over all peers and ran on every
netmap update, contributing to the broader quadratic behavior we want
to eliminate when a single peer is added or removed.
Instead, this makes netlog ask LocalBackend (well, nodeBackend) for
the info it needs, letting us remove the netmap.NetworkMap type
entirely from the netlog package.
This is a dependency to removing the netmap.NetworkMap type from
upstream callers, like wgengine.Engine in general.
Updates #12542
Change-Id: Ib5f2de96e788a667332c0a6f7ac833b3d0053b5c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Mappings from transit IPs to real IPs are stored ephemerally in the
connector, so they're lost on restart. When we send a packet to the
connector with a transit IP it does not recognize, it sends us a TSMP
message saying so (see #19883). If we (the client) know of such a
mapping, we now re-send it to the connector so that a connection can
proceed.
Fixestailscale/corp#34256.
Signed-off-by: Naman Sood <mail@nsood.in>
decode6 didn't parse the IPv6 Fragment extension header (Next Header 44),
so any source-fragmented IPv6 packet was classified as an unknown protocol
and matched no ACL rule. The filter then silently dropped it and counted it
as an "acl" drop, even on allow-all tailnets, blackholing large UDP (DNS,
WebRTC, etc.) over a tailnet's IPv6 addresses. IPv4 fragments were already
handled by decode4.
Parse the fragment header the same way: read the first fragment's transport
ports so the filter matches it like an unfragmented packet, pass later
fragments through as ipproto.Fragment, and reject overlapping-fragment
offsets (RFC 1858) and first fragments too short to hold the transport
header as unknown.
Fixes#20083
Signed-off-by: Steve Avery <hello@stevenavery.com>
Bumps wireguard-go pin to include the roaming endpoints fix, and
two internal enhancements.
Pulls stock wireguard-go for non-tailscale simulation in tests,
to use its endpoint discovery mechanism.
Updates #20082
Change-Id: I2ff282cb7fe4ab099ce5e780a1d40ae86a6a6964
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
Package features/conn25 wires up the hooks directly on the tun wrapper
without needing to go through the userspace engine, so this codepath is
unused and not needed.
Updates #cleanup
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
Since deltas are only (at present) received from the control plane, processing
a delta signifies we are no longer operating on a netmap fully loaded from
cache, even if most of the netmap is still in the same configuration.
Updates #12542
Change-Id: I84132c4bf2dde6e5c1c57144645edb986b051dca
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
The 1 minute timeout was hitting timers inside wireguard-go, leading
stale connections hanging forever. Increasing the timeout to 2 minutes
makes a small subset of cached connections establish direct connections
slightly slower.
Updates to wireguard-go will allow a better hook for when to send these
messages in the future. This change only makes fixes the error mode but
if we have better triggers coming in wireguard-go, we should be using
those.
Updates #20081
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
9be21088f4 changed sending disco pings so
a callMeMaybe would be not be gated by endpoints existing if the node
was running off of a cached netmap.
This commit partly reverts that change, but keeps in a few bug fixes in
that commit and the tests that was introduced and now skipped.
The behaviour prior to 9be21088f4 is
retained.
Updates #20085
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
This is a refinement of #19916. Previously, we would only emit a latency log
when going from a cached netmap to an uncached one (i.e., from the control
plane). We would like to know the latency in both conditions, though, so
instead use the validity of the previous self state.
Updates #12639
Updates tailscale/projects#27
Change-Id: I6bbeb5d3162f1f98cdb3dcd244f67ef31c170957
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
magicsock de-duplicates NetInfo callbacks against c.netInfoLast, a cache
that lives on the long-lived magicsock.Conn. That cache survives a control
client swap (interactive login or profile switch), where only the control
client (and its own per-client NetInfo dedup) is replaced. As a result, the
first netcheck after the swap produces a structurally-identical NetInfo
(same PreferredDERP, same NAT shape), magicsock suppresses it as unchanged,
and the new control session never learns our home DERP. Peers can't reach
the node over DERP until some unrelated NetInfo field happens to change.
Add Conn.ResetNetInfoLast to clear the dedup cache, and call it from
LocalBackend.setControlClientLocked whenever a control client is installed,
so the next netcheck re-reports the current NetInfo to the new client.
netInfoLast is only a dedup/optimization cache (all readers nil-guard, and
it is recomputed by every netcheck), so clearing it can only add a delivery,
never lose or misroute one; it is scoped to control-client lifecycle events,
not steady-state operation.
Updates #17887Fixes#20024
Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
Commit 2b338dd6a8 removed watchdogEngine because it was weird
(so many methods) and increasingly unnecessary after we'd cleaned up
and simplified so much of the locking.
This adds back a watchdog, but an easier to maintain one that's more
idiomatic.
Updates #19759
Change-Id: I86c458473e126c0809f37696446ce7acf4cc4eb9
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
In order to allow us to measure the performance effects of client-side netmap
caching, both with and without the feature enabled, add logs to record how long
it takes after a client restart or profile switch for the node to establish
contact with peers, relative to the first uncached netmap.
We do this by keeping track of a timestamp when the connection is constructed,
and logging a record for "new" peer contacts that records how long (in
microseconds) it took from the time the peer was recorded as a candidate. The
message includes whether the contact was via DERP or direct, and whether a
cached netmap was in use at the time.
This builds on and extends the counters from #19699, but here we include new
contacts whether or not a cached netmap is in use, so that we can establish a
baseline for comparison.
Updates #12639
Updates tailscale/projects#27
Change-Id: I4f6d050e221f3881848d05a0425c4a5d1a59294c
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
The routecheck package parallels the netcheck package, where the
former checks routes and routers while the latter checks networks.
Like netcheck, it compiles reports for other systems to consume.
Historically, the client has never known whether a peer is actually
reachable. Most of the time this doesn’t matter, since the client will
want to establish a WireGuard tunnel to any given destination.
However, if the client needs to choose between two or more nodes,
then it should try to choose a node that it can reach.
Suggested exit nodes are one such example, where the client filters
out any nodes that aren’t connected to the control plane. Sometimes an
exit node will get disconnected from the control plane: when the
network between the two is unreliable or when the exit node is too
busy to keep its control connection alive. In these cases, Control
disables the Node.Online flag for the exit node and broadcasts this
across the tailnet. Arguably, the client should never have relied on
this flag, since it only makes sense in the admin console.
This patch implements an initial routecheck client that can probe
every node that your client knows about. You should not ping scan your
visible tailnet, this method is for debugging only.
This patch also introduces a new OnNetMapToggle hook, which fires when
the netmap transitions from nil to non-nil, or vice versa. This
happens either when the client receives its first MapResponse after
connecting to the control plane, or when it clears the netmap while it
is disconnecting. Routecheck uses this to wait for a valid netmap
so it knows which peers to probe.
Updates #17366
Updates tailscale/corp#33033
Signed-off-by: Simon Law <sfllaw@tailscale.com>
Add four control-plane node attributes that let us disable UDP GSO/GRO
on the magicsock UDP socket and UDP/TCP GRO on the Tailscale TUN
device.
These complement the pre-existing TS_DEBUG_DISABLE_UDP_{GRO,GSO} and
TS_TUN_DISABLE_{UDP,TCP}_GRO envknobs. They exist so we can mitigate
upstream Linux kernel regressions on a deployed fleet without
requiring a client release, after two incidents (#13041, #19777) where
buggy kernel patches landed upstream and the fix took an excessively
long time to reach downstream distros.
Knob changes are reacted to in setNetworkMapInternal / SetNetworkMap via
a comparison against a cached "last applied" value and only an actual
transition triggers work: magicsock Rebind()+ReSTUN for UDP,
ApplyGROKnobs for TUN. The TUN side is gated by buildfeatures.HasGRO and
is one-way (wireguard-go GRO disablement is sticky); re-enabling
requires a client restart.
Updates #13041
Updates #19777
Change-Id: I802993070afa659cc06809bb0bfbb7f8a0cdb273
Signed-off-by: James Tucker <james@tailscale.com>
Originally found when adding tests for working with cached netmaps, and
finding the added tests to be flakey.
When working off of a cached netmap, if a node exists in the cached
netmap but does not yet have any endpoints, DERP connections are
available but not direct ones. By sending callMeMaybe to nodes
without endpoints in the cached netmap, we can establish direct
connections for this edge case.
Aditionally, ensure that TSMP disco advert messages are not sent if the
endpoint does not have a valid address yet.
Fixes#19843
Updates #19597
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
For large tailnets (~50k+ nodes) with frequent peer churn (ephemeral
GitHub Actions workers etc.), tailscaled used to rebuild the full
netmap and fan it out on the IPN bus on every MapResponse that
added or removed a peer. There were two O(N) costs per delta: the
full netmap rebuild + every Notify.NetMap encode to every bus watcher.
This change tackles both:
1. Plumb O(1) peer add/remove through the delta path. PeersChanged
and PeersRemoved no longer prevent the delta happy path; instead,
they mutate the per-node-backend peer map in place.
2. Restrict ipn.Notify.NetMap emission to the platforms whose host
GUIs still depend on it (Windows, macOS, iOS) and migrate
in-tree consumers off it everywhere else:
- Migrate reactive consumers (containerboot, kube agents,
sniproxy, tsconsensus, etc.) off Notify.NetMap to the
previously-added Notify.SelfChange signal so they no longer
have to subscribe to the full netmap.
- Add ipn.NotifyNoNetMap so GUI clients on "legacy-emit" platforms
that have already migrated can opt out of the per-watcher
NetMap encode.
- Gate Notify.NetMap emission on the producer side by a compile-
time GOOS check, so the supporting code is dead-code-eliminated
on Linux and other geese where no GUI consumer needs it.
Re-running BenchmarkGiantTailnet from tstest/largetailnet, which was
added along with baseline numbers on unmodified main in ad5436af0d,
the per-delta cost (one peer add+remove pair) is now ~O(1) regardless
of tailnet size N:
N no-watcher (ms/op) bus-watcher (ms/op)
before now factor before now factor
10000 32 0.11 300x 166 0.13 1300x
50000 222 0.11 2000x 865 0.13 6700x
100000 504 0.12 4100x 1765 0.13 13400x
250000 1551 0.12 12500x 4696 0.15 32400x
Updates #12542
Change-Id: I94e34b37331d1a8ec74c299deffadf4d061fda9e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
SetDERPMap spawns a goroutine that calls ReSTUN, which logs via the
test logger. If the test returns before that goroutine logs, the
goroutine races with testing cleanup.
Use tstest.WhileTestRunningLogger so the goroutine's logf call becomes
a no-op once the test finishes.
Fixes#19829
Change-Id: I1097f98e40ffd1c5dd7fb7a715c918255853e3c6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The Engine watchdog wrapped every wgengine.Engine method call in a
goroutine with a 45s timeout and crashed the process on timeout. It
was added years ago to surface deadlocks during development, but the
underlying deadlocks have long since been fixed, and even when it did
fire it produced obscure stack traces (from inside the watchdog
goroutine, not the original caller) without buying much.
Audit of userspaceEngine's methods shows none have cyclic locking or
unbounded blocking now that ResetAndStop no longer loops waiting for
DERPs to drain (fa49009ee). The watchdog is dead weight; remove it
along with the TS_DEBUG_DISABLE_WATCHDOG escape hatch.
Updates #19759
Change-Id: Iba9d718fe1f8718a6631296e336b138c31b99ff1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
linuxRouter has two blocks (connmark rules and the CGNAT drop rule) that
gate on cfg.NetfilterMode, the requested config state. This may cause an
error when setNetfilterModeLocked fails, since it may keep assuming this
config is valid.
We now gate both blocks on r.netfilterMode, matching the pattern used by
SNAT, stateful, and loopback paths.
Fixes#19737
Change-Id: Ia6003a082db99c376e662132d725661afbac0ee9
Signed-off-by: Fernando Serboncini <fserb@tailscale.com>
Since f343b496c3 ("wgengine, all: remove LazyWG, use wireguard-go
callback API for on-demand peers"), Reconfig is fully synchronous:
magicConn.UpdatePeers, wgdev.RemovePeer, router.Set, and dns.Set all
return when the work is done, and the peer list is updated under
wgLock before Reconfig returns. So after Reconfig with empty configs,
len(st.Peers) is already 0.
The old loop also waited for st.DERPs to drain to 0, but UpdatePeers
only edits maps; active DERP connections idle out on their own
timeout. The sole caller (LocalBackend.stopEngineAndWait) doesn't
inspect st.DERPs anyway; it just hands the Status to
setWgengineStatusLocked. So the drain-wait was for nothing observable
and could theoretically (or at least appear to readers to) loop
forever holding b.mu. Remove that reader confusion by removing
the backoff loop entirely.
Updates #19759
Change-Id: Ibfac3f0baabcad7604b713c934a8fc37932e0a50
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The codegen path for map-of-slice-of-pointer fields, skipped
nil-valued entries. That dropped the key from the map.
This broke how dns.Config.Routes uses nil values sentinels.
Fixes#19730Fixes#19732Fixes#19746Fixes#19744
Change-Id: Ic6400227f4ab21b3ca0e8c0eeecf9b83d145a9ab
Signed-off-by: Fernando Serboncini <fserb@tailscale.com>
This patch fixes a data race in wgengine/netstack that surfaced while
running both TestTCPForwardLimits and TestTCPForwardLimits_PerClient.
Because these two tests both setup the TS_DEBUG_NETSTACK envknob, a
race happens because netstack.Impl.Close leaked its inject goroutine.
The inject goroutine also reads the TS_DEBUG_NETSTACK envknob, so if
it is still running when the next test starts, then it will break.
This patch also cleans up the tests a bit, ensuring that neither of
them run in T.Parallel. It also adds a T.Cleanup call to clear the
envknob.
Fixes#19720
Signed-off-by: Simon Law <sfllaw@tailscale.com>