Commit Graph
677 Commits
Author SHA1 Message Date
François Marier ca4e1972fa tstest/natlab/vnet: move PCP/NAT-PMP code to portmapper.go (#22145)
portmapper.go was introduced for UPnP in commit
8ed43f1f2a.

This also adds a unit test which exercises the incomplete
natlab server implementation of these portmapping protocols.

Updates #21713

Change-Id: I33b61ccbe8c1d8d99d56aa357ae24777be3f5739
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-11-03 14:51:11 -07:00
François Marier 1b68f62be8 tstest/natlab: add a new portmapper VM test (#22071)
This test encodes the current behaviour of the Tailscale client.
It only covers the creation of new mappings.

Updates #21713

Change-Id: Ia44877036569660f4129484d5059ed22ffbf9cda
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-30 10:19:14 -07:00
James Tucker 96467f7d16 tsnet,net/tsdial: resolve outgoing names using tailnet DNS policy
Resolve Server.Dial and Server.HTTPClient names using the tailnet's DNS
configuration, including MagicDNS, search domains, split DNS routes, and
global resolvers. Preserve the dialer's local MagicDNS fast path.

Add Server.LookupIP for direct lookups returning netip.Addr values. Keep
the resolver implementation private to tsnet. Search expansion uses
ndots=1, includes the MagicDNS suffix when enabled, and respects trailing
dots. Each lookup uses the current DNS configuration.

Use the internal resolver before considering host fallback. Configured
upstream failures and authoritative local denials must not fall back to
the host resolver; the host's hosts file is consulted only during fallback.
Distinguish configured routes from ambient cloud-host fallback resolvers.

Cover search expansion, split and global DNS, MagicDNS-disabled operation,
configuration updates, hosts-file precedence, and dialing in tests.

Fixes #5840
Updates #7984
Updates #15260
Updates #4677

Change-Id: I832bc4c68658d2d25c5dc29b79acf611fc11e59e
Signed-off-by: James Tucker <james@tailscale.com>
2026-10-29 15:29:43 -07:00
François Marier 8ed43f1f2a tstest/natlab: add basic SSDP and UPnP support (#21823)
This bare-bones implementation of a UPnP server covers only what
the Tailscale client needs in order to successfully create a
new mapping.

A new flag is introduced for the test control server so that UPnP
can continue to be disabled in all tests except natlab.

Updates #21713

Change-Id: I57779e07c6a628a12bb30dccabd94f7bb21270c4
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-28 11:58:07 -07:00
Claus Lensbøl 41d25e0c8f cmd/natlab-test-matrix,tstest/natlab/vmtest: add test matrix tool (#21818)
Add experimental tool for generating a matrix of tests to be run by CI.
This tool will parse the test files in the vmtest folder, and find
candidates that can be with with a set of variations. For now that is
expressed as different flavours of Linux, but it can be expanded across
OS'es but also other things like NAT configurations and portmapper
extensions available.

Updates #21817

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-10-28 14:05:30 -04:00
Francois Marier 2b62983a4a tstest/natlab/vnet: fix TCPConn leak
Explicitly close the other end of the losing connection.

This is a follow-up to 31baee90c0.

Updates #cleanup

Change-Id: I5bf36a7451da05545e7242ee5c1bff02fa44b362
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-26 21:49:04 -07:00
Francois Marier 48ac67151e tstest/natlab/vnet: send "unsupported version" NAT-PMP/PCP responses
When receiving a PCP request, a NAT-PMP-only server must reply with
an unsupported version response. Same for a PCP-only server
receiving a NAT-PMP request.

The tailscale client sends both a NAT-PMP and a PCP request when
probing for portmapping support and it correctly handle any
unsupported version response it receives.

Updates #21713

Change-Id: I0ec8b4a6c1de14bcc64b82f1988cde7fc01f5747
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-16 10:13:52 -07:00
chaosinthecrd e915ebc42a control/controlclient,ipn/ipnlocal,tstest/integration: test node removal
Adds tests that only a 404 map response wraps ErrNodeRemoved, that the
node-removed warning and NeedsLogin state are cleared by a new netmap or
a profile switch, and that a node restarted after it was deleted shows
the warning and reaches NeedsLogin.

Updates #18700

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-10-16 16:40:31 +01:00
Brendan Creane a0a79982d4 feature/acme,tstest/acmetest: report the authorization error when an ACME order fails (#21790)
When validation fails, WaitOrder returns only the order's status, so
tailscale cert printed "acme: order ... status: invalid" with no reason.
The CA's reason is on the authorization's challenge. Wait on each
authorization before the order and return that error instead.

To test this, move natlab's fake ACME CA to tstest/acmetest, where unit
tests can import it, and teach it tls-alpn-01, a validation hook, and
the "invalid" state a CA reports when validation fails.

Updates #21759

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-10-15 07:45:30 -07:00
yaruk-byte f61c868527 tstest/integration: add tests for DNS, routing and incoming connections (#21517)
Updates #21489

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>
2026-10-14 00:43:00 -07:00
François Marier 53c3957233 tstest/natlab/{vmtest,vnet}: add basic PCP support (#21730)
This implements enough of PCP to establish direct connections
with a node behind a hard NAT.

Updates #21713

Change-Id: I44b955e9b8ecce80ddc49afdfb01eb49c8d56f8c
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-13 08:51:12 -07:00
Francois Marier 87ec06810d tstest/natlab/vmtest: give LANPing attempts more than 5s
TTA's /ping runs four pings, taking 3-4s. The 5s timeout wasn't
enough for slow VMs using TCG.

Updates #cleanup

Change-Id: I70006f4c07b776d58619e0ef75c91d0d65b13fff
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-09 17:02:41 -07:00
Brad Fitzpatrick 0a1eb8c4da ipn/ipnlocal: don't answer single-label names from quad-100
Since 7e609b258 (v1.102), quad-100 looks up MagicDNS names in
nodeBackend.nodeByName, which also holds short-name keys for in-process
dials. So a query for "foo." matched peer "foo.tail-scale.ts.net" and
was answered with its Tailscale IP. 1ec348784 fixed that for MagicDNS
off; this fixes it for MagicDNS on, too, matching pre-1.102 behavior.
The OS applies the search domain to short names before they reach
quad-100, so a single-label query is a root zone name, not a tailnet
name, and must be forwarded (e.g. to LAN mDNS/LLMNR or a global
nameserver). Short names still work for in-process dials via
NodeByName.

Updates #20789

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ic8199a96cf79178210c9a85e0cebfb46567a894a
2026-10-09 11:01:40 -07:00
François Marier f5e7486c17 tstest/natlab/vnet: refactor NAT-PMP support (#21715)
This will make the addition of PCP support easier.

No changes to NAT-PMP support or tests.

Updates #21713

Change-Id: I7ac58badbd4e8680c0e50d8dd5d78d6d0abb4aa0
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-09 11:00:06 -07:00
Claus Lensbøl 1e3097c9f2 tstest/natlab/{vnet,vmtest},cmd/tta: add roaming test to natlab (#21695)
To faciliate roaming a few things had to happen as tta communicated over
the same links as tailscaled communicates over, meaning that the
connection to tta gets cut when cutting the interface.

 - Whenever we have connected to the client after waiting for it to come
   up, clean up idle connections.
 - Have tta use keepalives to reconnect.
 - Delete default route on just via, not the interface.
 - Add capabilities to QMP for setting the interface state.
 - Implement the test.

Fixes tailscale/corp#49639

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-10-09 13:05:45 -04:00
Dep Updater b9262195b1 go.mod,go.toolchain.rev: bump golang.org/x/net, Go toolchain
* golang.org/x/net: v0.59.0 to v0.60.0
* Go toolchain: https://github.com/tailscale/go/compare/24ee2fd0610e6c505ee4ec061a81215afe119d1f...509d1573c1cafe9b210180e4ce87f44a409edbba
* golangci-lint: v2.13.2 to v2.14.0, for Go 1.27.2's export data version 5

Also fix testcontrol's serveC2N to read the whole c2n response before
returning, as x/net v0.60.0's http2 server now closes the request body
when the handler returns.

Triggered by @bradfitz via the bumpdep workflow.

Updates #21723

Signed-off-by: Dep Updater <noreply+dep-updater@tailscale.com>
2026-10-09 08:12:09 -07:00
Brad Fitzpatrick 1264887365 tstest/deptest: make ImportAliasCheck results per-commit in CI
ImportAliasCheck runs git grep in a subprocess, whose file reads Go's
test cache can't see. With a shared CI test cache, callers' tests kept
reporting a stale "ok (cached)" for new commits that added bad
x/exp/{maps,slices} imports, as long as the test binary didn't change.
That's how tailscale/corp#49716 merged with green CI.

Read GITHUB_SHA so Go records it in the test cache key, making the
cached result per-commit in CI.

Updates tailscale/corp#49716

Change-Id: I6301f5ed067a05294185b05a195550efa8e5aa01
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-10-08 11:35:59 -07:00
Francois Marier 8bdb4283de tstest/natlab/vmtest: use -icount for gokrazy VMs under TCG
On hosts without hardware virtualization, gokrazy VMs can hang at
boot with no console output. Timing jitter under software emulation
breaks the kernel's early boot timer calibration, causing it to spin
waiting for timer interrupts that never arrive.

Passing -icount shift=auto ties virtual clocks to instruction execution,
making timer calibration deterministic and preventing the boot hang.

Updates #cleanup

Change-Id: I7c0aa96e5344041af9a7246f7d50bed1da47ab95
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-07 18:54:33 -07:00
Brendan Creane 4862f5f466 tstest/natlab/vmtest: add a dhcpcd DHCP client option for Ubuntu guests (#21672)
WithDHCPClient(DHCPClientDhcpcd) makes systemd-networkd leave the vnet NIC
unmanaged and runs the dhcpcd that the Ubuntu 24.04 image ships on it.
dhcpcd is the DHCP client on Raspberry Pi OS and other distributions, and
unlike networkd it configures the interface first and runs its resolvconf
hook afterwards. TestDhcpcdOpenresolvDNS checks that dhcpcd holds the
lease, its hook registered the lease's nameserver with openresolv, and
tailscaled forwards through it.

Updates #21607

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-10-06 17:09:47 -07:00
Brendan Creane b62885dd02 net/dns/resolver: don't retry an upstream REFUSED or SERVFAIL over TCP (#20829)
A TCP retry to the server that just answered can only return the same
rcode, and when that server accepted the connection and never answered,
the query ran the full 10s dnsQueryTimeout and the client got nothing.
An upstream rcode now ends the UDP/TCP race when it arrives, and the
client gets it once every resolver has had udpRaceTimeout to answer
instead of waiting for all of them to report. tcpQueryTimeout is also
set on the conn, since a context cannot interrupt a blocked read, and a
new dns_query_fwd_udp_read_ctx_done counter separates query teardown
from upstream read errors.

Fixes #20826

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-10-06 16:19:28 -07:00
Francois Marier 2d34ce3bbf tstest/natlab/vnet: stop agent dials from outliving their Server
While a VM reboots, tests poll its agent roughly every 5s. Each
request that times out leaves behind a goroutine blocked in
Server.takeAgentConn, waiting for TTA to connect back. Several can
pile up before a request succeeds and the test ends, and the rest
wait forever.

Making takeAgentConn return at Server shutdown unblocks any goroutine
still waiting when the test ends.

In a full vmtest run, the two dials stranded by the reboot in
TestGokrazyUpdatesItselfToSameImage logged until the package exited
10 minutes later. With this change, "still waiting" log lines in the
same run drop from 267 to 72, and the longest wait goes from 10m10s
to 40s.

Updates #cleanup

Change-Id: Ib4c1d2ce8f2a7b73d3f0c1e6a9e5b2d8f4a1c7e3
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-06 11:48:17 -07:00
Brendan Creane 284ca9991d tstest/natlab: upload tailscaled logs from cloud-image guests (#21664)
Only gokrazy builds trust the fake log catcher's TLS certificate, so
Ubuntu, Debian, Fedora and FreeBSD guests failed every upload and the
failure dump of their tailscaled logs was always empty. vnet now also
serves the log catcher over plain HTTP on port 80, and the harness points
those guests at it with TS_LOG_TARGET. Env.NodeLogs returns what a node
has uploaded.

macOS guests still do not upload: tailmac does not pass TailscaledEnv to
tailscaled, so the setting would not reach it.

Updates #13038

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-10-06 07:54:31 -07:00
Francois Marier 31baee90c0 tstest/natlab/vnet: don't hand out idle agent conns the agent already closed
TestSelfSignedDERPHashPinning occasionally fails on a slow vm with:

    tailscale up (limit 1m30s): Get "http://unused/up?accept-routes=true": EOF

Because this endpoint has both IPv4 and IPv6 addresses, TTA will fall
back to the second address when running on a slow host and race both
connections. The losing connection will get closed by net.Dialer and
never produce a response.

By keeping the gVisor endpoint with the conn, we can avoid returning
closed connections in takeAgentConnOne().

This fix uncovered a bug in Env.Tailscale(): calling "tailscale
update" via a GET request may be replayed by net.http.Transport and
that will trigger a second update undoing the first. Using POST
disables all such retries.

Update #cleanup

Change-Id: I2f8b7c4d9e1a3b5c7d0e2f4a6b8c1d3e5f7a9b0c
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-05 11:26:11 -07:00
Alex Chan 9128778b65 types/key: rename NLPublic/Private to TLPublic/Private
The NL prefix is one of the last references to the "Network Lock" name
which we're cleaning up.

Updates tailscale/corp#37904

Change-Id: I751194e0b49447e97a65ed6975e3da680226ce85
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-10-02 16:03:51 +01:00
François Marier d33279d752 tstest/natlab/vmtest: wait for tailscaled to break race with tta (#21472)
The agent and tailscaled are started concurrently by gokrazy and
tta can win that race, in which case it answers 502 Bad Gateway
because tailscaled.sock does not exist yet.

This is most likely to be seen on slower machines using TCG.

Updates #cleanup

Change-Id: I4012bd189adfb2565470c814204fcf18545f1017
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-10-01 11:35:16 -07:00
Brad Fitzpatrick 654c105f8b tstest/natlab/vmtest, gokrazy, cmd/tailscale/cli: add minimal-build vmtests
Add vmtests that boot pairs of gokrazy VMs whose tailscale and tailscaled
are minimal builds and check that IP traffic flows between them over
WireGuard, so we notice if a minimal build stops doing its one job:
build_dist.sh --extra-small (direct path through NATs), that minus NAT
traversal (DERP across NATs, direct on a shared LAN), and DERP-only.

They found that "tailscale up" built with ts_omit_ipnbus returned before
calling Start or EditPrefs, making it a no-op. Fix that, and have it wait
for the Running state by polling tailscaled's status instead of watching
the IPN bus, so scripts can rely on it as with normal builds.

The extra-small feature list moves to featuretags.ExtraSmall so
build_dist.sh (via a new "featuretags --extra-small") and vmtest share
it. The gokrazy builder gains --output and --go-build-tags flags to build
the variant images, and natlabprep --gokrazy prebuilds them all for CI.

Updates #13038
Updates #12614

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I0caa2c7b3adf2399da09b6d07a942f1dc34e8486
2026-09-30 15:53:47 -07:00
Claus Lensbøl b31be2cd1e tstest/natlab/{vnet,vmtest}: clean up natlab inconsitencies (#21483)
There were a handful of inconsitencies in natlab with the biggest being.
 - Pair and grid tests living under tstest/integration and using the old
   test style.
 - Inconsistent use of e vs env.
 - Methods for actions on node living on the vnet.Node rather than being
   a method on vmtest.Env

Clean up these inconsitencies.

Updates #cleanup

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-09-29 14:57:10 -04:00
Francois Marier eb79f3e33e tstest/natlab/vmtest: fix test flake
It's possible for multiple direct connections to be counted and
these tests are successful as long as it's more than one.

Fixes #21578

Change-Id: I979efc92a43f78d0e51d602d96e61828e6fabdff
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-09-29 10:56:17 -07:00
Brad Fitzpatrick bcc7e46f42 tstest/integration/testcontrol: add experiment.reco implementation
Select the published reco-backed test server with a build tag while
preserving the default implementation and existing caller API. Pin the
public module with its proxy-verified checksums; no workspace is needed.

Accept incremental peer messages in the TSP test. Exercise legacy map
semantics with the default server and version-floor rejection with reco,
including current-version streaming, without manual test skips.

Document GOFLAGS usage so child Go builds inherit the experiment tag.

Updates tailscale/corp#49097

Change-Id: I9b3e086c4c6a9a57a076e76b883422875c843f20
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-09-29 10:25:02 -07:00
Kristoffer Dalby c4a66d1ce1 ipn/ipnlocal, tstest/largetailnet: test removals bundled with full netmaps
A peer removal sent with any MapResponse field that forces a full netmap
rebuild never reaches IPN bus watchers; these tests fail.

Updates #15660

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-09-29 19:23:51 +02:00
Brad Fitzpatrick b4e3a5299c cmd/tailscaled, safesocket: add --windows-mode=dev
This adds a tailscaled string flag "--windows-mode" which accepts two
possible values: the empty string (default) to get the normal behavior
(tailscaled running as an admin, usually as a service), and "dev", to
make the safesocket named pipe path be at a location that regular
users (non-admins) can create.

It then modifies the safesocket client side (as used by the CLI) to
try the dev mode path too on failure.

This lets people work on tailscaled.exe+tailscale.exe in a terminal
easily during development, as either an admin or non-admin. (This
used to work prior to the move away from TCP localhost to named pipes
for safesocket on Windows)

Updates #2791

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9c1e3a5b7d0f2a4c6e8b0d2f4a6c8e0b2d4f6a8c
2026-09-28 13:26:52 -07:00
Brendan Creane 2d4379386a net/dns: don't take over DNS when the OS has no upstream resolvers (#20794)
On backends that can't do OS-level split DNS, Tailscale forwards the default
route to the system's own resolvers, which it reads out of the OS config. At
boot that config may not be populated yet, because NetworkManager or
systemd-resolved haven't run, and Tailscale took over regardless: it pointed
the OS at 100.100.100.100 but compiled an empty "." route, so every
non-Tailscale name failed to resolve for the rest of the process's life.

Fail compileConfig instead, which leaves the OS resolvers in place, and
retry the last config with a bounded backoff until resolvers appear. A
health warning explains why MagicDNS is inactive in the meantime.

Sandboxed macOS and iOS are exempt. There the network extension reapplies
the config itself when the OS nameservers change, and quad-100 as the
primary resolver is what keeps tailnet names resolving while the base config
is still empty. Dropping the exemption is tracked in tailscale/corp#48962.

This also changes the outcome on an openresolv host with no snippets
registered. Since the fix for #20825, openresolv reports an empty base config
there and tailscaled took over with no upstream, so every public name got
SERVFAIL. Such a host now keeps its resolv.conf and shows the health warning
instead. TestOpenresolvDNS checks that outcome.

TestSplitDNSEmptyBaseConfig covers the boot race end to end in natlab: it
empties resolv.conf on a guest using the "direct" backend, checks that
tailscaled leaves it alone, then adds a resolver and checks that both tailnet
and public names resolve.

Fixes #20341

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-09-27 10:20:42 -07:00
Martin Minkus 27b2a7d1a1 wgengine/router/osrouter: leave pf and forwarding alone in FreeBSD netstack mode
FreeBSD handles subnet routes in netstack by default, but the router
still installed its pf NAT rules whenever SNATSubnetRoutes was set,
which is the default. Every FreeBSD node, subnet router or not, loaded
and enabled pf, inserted anchor references into the host's main
ruleset and loaded NAT rules that netstack never needed, since
netstack dials subnet destinations from the host's own addresses.
It also flipped the forwarding sysctls for any advertised route.

Only do either when the kernel path is opted into with
TS_DEBUG_NETSTACK_SUBNETS=false and routes are advertised.

TestSubnetRouterFreeBSDManyFlows ran in netstack mode since the default
changed, so it no longer exercised pf at all. Opt it into the kernel
path and assert the anchor holds NAT rules, and have
TestSubnetRouterFreeBSD assert the default mode leaves pf untouched.

Also drop the stale claim in handleSubnetsInNetstack that the pf NAT
rule never matches; that was the (self) pool bug, since fixed.

Updates #21450

Change-Id: Ibf91a676a026cebb29f64e39a64fe8e75488360b
Signed-off-by: Martin Minkus <martin.minkus@sonic.com>
2026-09-25 12:10:53 -07:00
Brad Fitzpatrick d4f2bb4cff ipn/ipnlocal: skip the kernel peerapi listener on FreeBSD when netstack is present
FreeBSD is a weak-host stack like Linux, so a LAN-adjacent machine can
send a SYN for a node's Tailscale IP to the node's NIC and get the
kernel's SYN-ACK from the peerapi port, confirming which tailnet
identity that MAC address belongs to. Linux closes this with
SO_BINDTODEVICE and macOS/iOS with IP_BOUND_IF, but FreeBSD has no
per-socket interface bind (only IP_RECVIF and SO_SETFIB).

Instead, treat FreeBSD like Android: when netstack is present, don't
create a kernel-level peerapi listener at all and use the fake listener,
since netstack already intercepts peerapi connections from peers in
userspace and the kernel socket only ever served the local host. When
netstack is compiled out (ts_omit_netstack), the kernel listener is the
only way to serve peers, so keep it and log that it must be restricted
with pf.

Along the way, tailscaled's own pf source NAT rule on FreeBSD turned out
to rewrite the source of such a SYN-ACK to the LAN IP and a random port,
so a plain connect() from the LAN never completes. The SYN-ACK still
leaks, though, so the natlab test now watches for SYN-ACKs on the
attacker's NIC with tcpdump instead of checking for a completed
handshake, and gains a FreeBSD variant. With the fix disabled, that
variant fails on the leaked SYN-ACK from the peerapi port.

Fixes #21419
Updates tailscale/corp#48248

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I0d2de4ebc9bae28450d500bd7b6ddbfa70f9052f
2026-09-25 12:10:40 -07:00
Francois Marier ef50433800 tstest/natlab/vnet: tear down gVisor network stacks at Server shutdown
Resolves #21442

Change-Id: I98f220d1a0ddc1a15cd9ebe7a8aaa745d17a43b2
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-09-25 09:39:50 -07:00
Francois Marier 8d43ba6737 tstest/natlab/vmtest: close unused pipe read FD to avoid leak
There are two copies of the read file descriptor (parent & child).
Since the parent copy is unused, we can close it after fork.

Updates #cleanup

Change-Id: Id789e0a04970c9fb8eaeed1f299aa25f29fcf48c
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-09-23 13:04:11 -07:00
François Marier 02de76ba62 tstest/natlab: ignore transport errors during gokrazy update (#21441)
In my testing using TCG, I hit non-EOF network errors that made
this test fail. Since we already poll and check the image version
later, we can treat this response as best-effort.

Resolves #21440

Change-Id: Ia923fc2c199aae7a543d00e06d25a375512daa01
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-09-23 10:50:34 -07:00
Francois Marier a35aff2895 tstest/natlab: move running instructions to a README
Updates #cleanup

Change-Id: I9580fbfeeef2edf0c678c8ab1ec70131fd20f0b5
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-09-23 10:19:34 -07:00
Brad Fitzpatrick b3de3b2217 ipn/ipnlocal, net/netns: bind Linux peerapi listener to the tun device
Linux is a weak-host stack, so a LAN-adjacent machine can complete a
TCP handshake with a node's peerapi listener by sending a packet to
the node's Tailscale IP, with no credentials and no tailnet
membership.

macOS and iOS already bind the listener to the tunnel interface, and
Windows is protected by its strong host model, so Linux tun mode was
the only platform that leaked.

Bind the Linux listener to the tunnel interface as well, so the
kernel only answers connections that arrive from the tunnel or from
the local host. A natlab VM test verifies that a same-LAN machine can
no longer complete the handshake, while local and peer peerapi keep
working.

FreeBSD has the same weak-host exposure but no per-socket equivalent,
so handling it there with pf is a TODO (#21419).

Updates tailscale/corp#48248

Reported-By: Samuel Keeley (@keeleysam)
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I5f8501b0938c9f7aa39c4c12ebddd988c72e89bf
2026-09-22 14:10:30 -07:00
Brad Fitzpatrick 3014ad828e tstest/natlab/{vmtest,vnet}: bound "tailscale up", dump tailscaled logs on failure
When a natlab node's "tailscale up" hung (TestEasyEasy on CI, twice on
main), the only bound was the 10 minute test context, so the failure
arrived as go test's timeout panic. The VM console logs weren't dumped
(t.Cleanup doesn't run on a panic), and they wouldn't have helped
anyway: on gokrazy the console holds only kernel and init output, while
tailscaled's stdout/stderr goes to a remote syslog that vnet discards
unless the node has VerboseSyslog set. There was no way to see what the
stuck node was doing.

Bound each node's "tailscale up" in Env.Start to 90 seconds (it takes
about a second against the in-process control server), so a stuck node
becomes a normal test failure. On failure, dump the tail of each node's
tailscaled logs from vnet's fake log.tailscale.com log catcher, which
already buffered them per node but exposed them to nothing; add
Server.NodeLogs for that. Also add VMTEST_VERBOSE_SYSLOG=1 to stream
the guests' syslog into the test output live, the vmtest equivalent of
tstest/integration/nat's --log-tailscaled flag.

With this, the CI hang reproduced locally under CPU pressure (2 CPUs
shared with busy loops) in 1 of 22 runs, and the dumped logs showed
tailscaled's control client stuck at "awaiting unpause", which is fixed
separately.

Updates #deflake

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Icfd3a2cdc66d924388a722f3a3cd19f86e116921
2026-09-20 11:02:04 -07:00
yaruk-byte be89526457 tstest/integration: stop skipping Windows integration tests (#21187)
Updates #20750

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>
2026-09-17 17:11:00 -07:00
yaruk-byte 1c7e77f094 tstest/integration: wait longer to remove the staged binaries at teardown (#21207)
The Windows GitHub runners' provisioning daemon, provjobd.exe, opens a
handle to each freshly-written tailscaled.exe. tb.TempDir's own cleanup
retries for a fixed 2s and gives up, failing whichever test happens to tear
down while the handle is held.

Updates #21099

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>
2026-09-17 09:44:05 -07:00
Brad Fitzpatrick 62f3130637 tstest/integration: deflake TestNATPing
TestNATPing called SetMasqueradeAddresses on the test control server
and then immediately read both nodes' status, expecting the new
masqueraded peer addresses to already be there. But the change reaches
the nodes asynchronously via their streaming map responses, so under
load the status check ran before the new map response arrived and the
test failed with "n1 sees n2 as 100.64.0.2; want 100.64.2.1" and the
like. This was the dominant failure mode on the flakes dashboard (11 of
the 18 most recent CI failures) and the only one found in a six hour
Antithesis run (run 30b4de27d89d8d7a651e7b43b3f1ec3f-61-9, 54 failures
in 16,599 runs).

Wait for each node's status to report the expected peer address
instead. Also retry the "tailscale ping" invocations, since a ping can
fail transiently right after a map response changes a peer's addresses
and before the engine is reconfigured; the second most common failure
mode was pings exiting with status 1. Failed pings now include the CLI
output in the error rather than a bare exit status.

Locally, flakestress (32 workers) reproduced the failure in 29 of 210
runs before this change (13.8%) and in 0 of 1,173 runs after.

The remaining Windows-only failure mode on the dashboard, TempDir
cleanup failing because tailscaled.exe is still open, is a
harness-wide issue that affects every integration test and is not
specific to this test.

Fixes #12169
Updates tailscale/corp#47865

Change-Id: I7c3e2b8a5d914f0e6a2b1c9d8e7f6a5b4c3d2e1f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-09-16 16:54:02 -07:00
James Tucker 3ec674bc86 go.mod,wgengine/netstack,tstest/natlab/vnet: enable RACK and cubic with gVisor clock fix
Update gVisor to include its fix for RACK loss detection with coarse
monotonic clocks. Configure netstack with the 500 microsecond clock
resolution used on Windows so RACK accounts for timestamp quantization.

Remove the TCP recovery override that disabled RACK, enabling gVisor's
default RACK behavior on all platforms.

Switch netstack to cubic congestion control. The int overflow in CUBIC
sender cwnd arithmetic that required pinning reno has since been reworked
upstream into float arithmetic with RFC 9438 target clamping.

Align the natlab vnet stack with netstack: enable cubic, and drop the
now-redundant explicit SACK and receive-buffer moderation sets, both of
which are gVisor defaults.

Fixes #9707

Signed-off-by: James Tucker <jftucker@gmail.com>
2026-09-15 17:15:35 -07:00
Brendan Creane 3ea665b113 tstest/natlab/vmtest: cover openresolv with a second snippet registered (#21003)
TestOpenresolvDNS pins the case where Tailscale owns the only resolvconf
snippet, so on its own it would also pass if tailscaled never read an OS
base config at all. Register a snippet the way a DHCP client would, then
toggle accept-dns to force a reapply, and assert its nameserver becomes
quad-100's upstream.

SetAcceptDNS is the toggle: tailscaled has no reason to re-read the OS
config on its own, and this avoids rebooting the guest to change what the
OS resolver looks like mid-test.

Updates #20825
Updates tailscale/corp#44793

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-09-15 10:24:47 -06:00
Brad Fitzpatrick 1a311b7e3c cmd/tailscaled: fail fast on Windows when wintun.dll is missing
wintun-go loads wintun.dll only from the application directory and
System32. The MSI puts it there, but a plain "go build" tailscaled.exe run
from a terminal has no wintun.dll, and tstunNewWithWindowsRetries then
retried tstun.New for five minutes with nothing in the log but

    tstunNew: backoff: 13 msec
    tstunNew: backoff: 32 msec

because backoff.BackOff never logs the error it's given, and after the
timeout the function returned context.DeadlineExceeded, discarding the
real error.

Instead, log with helpful text if we detect that.

Updates #21290

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I09dca3317974a977deb9956657a7e36e9ec8c572
2026-09-15 08:34:55 -07:00
Brad Fitzpatrick f9e5df3089 tstest/integration: fix ETXTBSY race when copying test binaries
Tests that exec a fresh CopyTo copy of the tailscale/tailscaled
binaries occasionally failed with "text file busy" on GitHub
Actions, previously worked around with the retry loop in
awaitTailscaledRunnable.

The root cause: CopyTo's Linux hardlink fast path only works while
the built binary is still linked somewhere. The kernel refuses to
hardlink an inode whose link count is zero, even via the still-open
FD, so once the building test's TempDir (and every other test's copy)
has been cleaned up, later tests silently fall through to the
byte-copy path. That path writes the new copy from the test process
itself, and if another parallel test forks a child while the write FD
is open, the child holds the inherited FD (O_CLOEXEC only closes at
exec, not at fork) and a subsequent exec of the fresh copy fails with
ETXTBSY. This is golang.org/issue/22315. On macOS and the BSDs,
CopyTo always takes the byte-copy path, so every copy races there.

Fix it the way the syscall package documents: hold ForkLock for
reading across the copy so that no fork overlaps the lifetime of the
write FD. A standalone stress program reproduced the race in 86 of
200 execs under a fork storm and in 0 of 200 with the lock held.

Updates #15868
Updates #15865

Change-Id: I8a6be72826c9073a9187d7e6c90c8733d2dadb05
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-09-14 12:04:05 -07:00
Evan Lowry 29cfb0b4cb control/controlclient: add support for retry-after in map sessions (#21188)
Previously retry-after support was added to register requests in #19403,
in order to have more fine tuned client/control interactions in cases of
rate limiting.

This change expands that support to map sessions as well.

Updates tailscale/corp#47299

Signed-off-by: Evan Lowry <evan@tailscale.com>
2026-09-11 16:31:55 -03:00
Brendan Creane 970cc199fe tstest/natlab/vmtest: add VM coverage for openresolv DNS backend (#20999)
* net/dns: export OSConfigurationReadWarnable

A natlab vmtest checks that a node is not reporting this warning. Exporting
the Warnable lets the test take the warning's text from it, instead of
keeping a copy of the wording that could stop matching without failing.

Updates #20825
Updates tailscale/corp#44793

Signed-off-by: Brendan Creane <bcreane@gmail.com>

* tstest/natlab/vmtest: add VM coverage for the openresolv DNS backend

dnsMode() picks one of five Linux DNS backends, and natlab could provision
only systemd-resolved and direct. Add a DNSOpenresolv mode and a VM test for
it, so the backend behind #20825 is covered.

No cloud image ships openresolv and a guest cannot download it, so its two
source files are vendored under testdata and installed with cloud-init's
write_files. openresolv's build is a set of sed substitutions with nothing to
compile, so resolvconf.go does the substitutions in process.

Updates #20825
Updates tailscale/corp#44793

Signed-off-by: Brendan Creane <bcreane@gmail.com>

---------

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-09-10 09:12:12 -07:00
yaruk-byte 7dfc149c8e tstest/integration: wait for the daemon to exit after killing it (#21107)
Updates #21099

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>
2026-09-09 10:39:45 -07:00