Commit Graph

10528 Commits

Author SHA1 Message Date
Kristoffer Dalby 384b7fb561 release/dist/qnap: preserve .codesigning files as build artifacts
Stop deleting .qpkg.codesigning files in build-qpkg.sh and include
them in the returned artifact list from buildQPKG.

These files contain the last 32 characters of the base64-encoded CMS
signature produced by QDK code signing. They are consumed by pkgserve
to populate <signature> entries in the QNAP repository XML, matching
the format used by myqnap.org and qnapclub.eu.

Updates corp#33203

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-04-28 12:29:56 +01:00
Will Norris 2d85f37f39 client/systray: support several different color themes
Currently we only have a dark theme icon with white and grey dots over
a black background. For some desktops, a logo with black and grey dots
over a white background might be preferable. And for desktops where the
bar is *almost* black or white, but not quite, an option to render the
logo with dots only and no background can look really nice.

Add a new -theme flag to the systray command with the default staying
the same as it is today.

Updates #18303

Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d
Signed-off-by: Will Norris <will@tailscale.com>
2026-04-27 18:54:14 -07:00
License Updater 325f52c654 licenses: update license notices
Signed-off-by: License Updater <noreply+license-updater@tailscale.com>
2026-04-27 18:38:06 -07:00
Brad Fitzpatrick d0ae993334 tstest/natlab/vmtest: add more subnet router tests
Add two tests building on TestExitNode's framework:

TestSubnetRouterPublicIP brings up a client, a subnet router, and a
webserver, each on its own NAT'd network with distinct WAN IPs. The
subnet router advertises the webserver's network as a route. The test
toggles the client's --accept-routes preference and asserts that the
webserver's echoed source IP switches between the client's own WAN
(direct dial) and the subnet router's WAN (forwarded through the
router and SNAT'd).

TestSubnetRouterAndExitNode adds a fourth node, an exit node that
advertises 0.0.0.0/0 + ::/0, and uses a table-driven layout with
subtests to cover the four combinations of (exit on/off, subnet
on/off). The case where both are on confirms longest-prefix match
wins: the subnet router's /24 takes precedence over the exit node's
/0. The exit node itself is configured with --accept-routes=off so
that, in the exit-only case, it forwards directly to the simulated
internet rather than re-routing the forwarded traffic via the subnet
router (which would otherwise mask the exit node's WAN as the
observed source).

Adds an Env.SetAcceptRoutes helper for toggling the RouteAll pref via
EditPrefs, used by both tests.

Updates #13038

Change-Id: Ifc2726db1df2f039c477c222484f535bebc40445
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-27 17:06:17 -07:00
Brad Fitzpatrick c0e6ffed0d tstest/tailmac: add NIC hot-swap, disconnected NIC, and screenshot server
Add NIC attachment hot-swap support to Host.app: VZNetworkDevice.attachment
is writable at runtime, so --disconnected-nic creates a NIC with no
attachment, and --attach-network hot-swaps it to a vnet dgram socket
after boot/restore. macOS detects link-up and does DHCP.

Refactor TailMacConfigHelper: extract createDgramAttachment() and
createDisconnectedNetworkDeviceConfiguration() from the monolithic
createSocketNetworkDeviceConfiguration().

Add --screenshot-port flag for headless mode. Host.app serves GET
/screenshot as JPEG via a localhost HTTP server, capturing the
VZVirtualMachineView via CGWindowListCreateImage. The Go test harness
polls these to push live thumbnails to the web dashboard.

Also: SIGINT handler in headless mode for clean VM state save.

Updates #13038

Change-Id: I42fba0ecd760371b4ec5b26a0557e3dd0ba9ecae
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-27 17:03:09 -07:00
Brad Fitzpatrick 5c1738fd56 tstest/natlab/{vmtest,vnet}, cmd/tta: add TestExitNode
Add a vmtest TestExitNode that brings up a client, two exit nodes, and a
non-Tailscale webserver, each on its own NAT'd vnet network with a
distinct WAN IP. The test cycles the client's exit node setting between
off, exit1, and exit2 and asserts that the webserver echoes the expected
post-NAT source IP for each.

Three pieces were needed to make this work:

vnet now forwards TCP between simulated networks at the packet level,
mirroring the existing UDP path. When a guest VM sends TCP to another
simulated network's WAN IP, the source network's gateway rewrites src
via doNATOut and routeTCPPacket hands the packet off to the destination
network, which rewrites dst via doNATIn and writes the rewritten frame
onto the destination LAN. The TCP stacks of the two guest VM kernels
talk end-to-end; vnet just NATs the IP/port headers in flight, so all
TCP semantics (handshakes, options, sequence numbers, payload) are
preserved without a gvisor TCP termination in the middle. Adds a
focused TestInterNetworkTCP that exercises this path without any
Tailscale machinery.

cmd/tta binds its outbound dial to the default route's interface using
SO_BINDTODEVICE. Without that, the moment tailscaled installs
0.0.0.0/0 → tailscale0 in response to setting an exit node, TTA's
existing TCP connection to test-driver gets rerouted through the exit
node. From the test driver's perspective the connection's packets then
arrive with the exit node's WAN IP as the source rather than the
client's, so they don't match the existing flow and the connection is
dead — manifesting in the test as a hang on EditPrefs (which had
actually completed in milliseconds on the daemon side, but whose
response never made it back). Pinning the socket to the underlying NIC
keeps TTA's agent connection on a real interface regardless of any
policy routing tailscaled installs later. We bind rather than carry the
Tailscale bypass fwmark because the fwmark approach is conditional on
tailscaled having configured SO_MARK-based policy routing, while
binding is unconditional.

vmtest grows an Env.SetExitNode helper that sets ExitNodeIP via
EditPrefs through the agent, used by the new test.

Updates #13038

Change-Id: I9fc8f91848b7aa2297ef3eaf71fed9d96056a024
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-27 16:54:20 -07:00
Alex Chan 10b63f27ce tstest/clock: explain what happens if you don't set a Start time
While working on #19444, I assumed that omitting `Start` would return a
clock that started at January 1, year 1, because that's the zero value
for a `time.Time`, but actually it uses the current UTC time instead.

This behaviour is non-obvious, so document it.

Updates #cleanup

Change-Id: Id91400778578655953ff3e1671ce470db97cfe91
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-04-28 00:15:46 +02:00
Brad Fitzpatrick ad5436af0d tstest/largetailnet, tstest/integration/testcontrol: add in-process large-tailnet benchmark
Add a Go benchmark that exercises a single tailnet client (a [tsnet.Server]
running in the test process) against a synthetic large initial netmap and
a stream of caller-driven peer add/remove deltas, all in-process.

The harness is split in two parts:

  - tstest/largetailnet, a reusable package containing a [Streamer]
    that hijacks the map long-poll on a [testcontrol.Server] via the new
    AltMapStream hook, sends one initial MapResponse with N synthetic
    peers, and forwards caller-supplied delta MapResponses on the same
    stream. Helpers like MakePeer / AllocPeer build synthetic peers with
    unique IDs and addresses derived from the Tailscale ULA range.

  - tstest/largetailnet/largetailnet_test.go, BenchmarkGiantTailnet
    (headless tailscaled workload, no IPN bus subscriber) and
    BenchmarkGiantTailnetBusWatcher (GUI-client workload with one
    Notify subscriber attached). Both are gated on
    --actually-test-giant-tailnet (skipped by default), stand up an
    in-process testcontrol + tsnet.Server, let Up block until the
    initial N-peer netmap has been processed, then ResetTimer and run
    add+remove pairs via b.Loop. Per-delta sync is via a test-only
    [ipnlocal.LocalBackend.AwaitNodeKeyForTest] channel that closes
    once the just-added peer key appears in the netmap (no-watcher
    variant) or via bus-Notify drain (bus-watcher variant).

To support the hijack, [testcontrol.Server] grows an AltMapStream hook
and a small MapStreamWriter interface for benchmarks/stress tests that
need to drive a controlled MapResponse sequence; the normal serveMap
path is untouched when AltMapStream is nil. The streamer answers
non-streaming "lite" map polls (which controlclient issues before the
streaming long-poll to push HostInfo) with an empty MapResponse and
returns immediately, so the streaming poll that follows is the one
that gets the initial netmap.

The benchmark is intended for before/after comparisons of netmap- and
delta-handling changes targeted at large tailnets. CPU profiles on
unmodified main show the expected O(N) hotspots:
setControlClientStatusLocked / authReconfigLocked /
userspaceEngine.Reconfig / setNetMapLocked, plus JSON encoding of the
full Notify.NetMap to bus watchers (which dominates the BusWatcher
variant).

Median ms/op over 10 runs on unmodified main, by tailnet size N:

       N      no-watcher   bus-watcher
   10000          32          166
   50000         222          865
  100000         504         1765
  250000        1551         4696

Recommended invocation:

	go test ./tstest/largetailnet/ -run=^$ \
	    -bench='BenchmarkGiantTailnet(BusWatcher)?$' \
	    -benchtime=2000x -timeout=10m \
	    --actually-test-giant-tailnet \
	    --giant-tailnet-n=250000 \
	    -cpuprofile=/tmp/giant.cpu.pprof

Updates #12542

Change-Id: I4f5b2bb271a36ba853d5a0ffe82054ef2b15c585
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-27 11:47:12 -07:00
Mike O'Driscoll 33342aec32 The connmark save/restore rules in mangle/PREROUTING restore the Tailscale bypass fwmark (0x80000) onto reply packets so that rp_filter's reverse-path check routes through the main table instead of table 52. However, the kernel only uses the packet's fwmark during the rp_filter lookup when net.ipv4.conf.all.src_valid_mark=1. (#19537)
On systems where this sysctl defaults to 0 (including GCP VMs), rp_filter performs its lookup with fwmark=0, hits rule 5270 then table 52 and routes to 0.0.0.0/0 dev tailscale0, and drops every reply packet arriving on the physical interface as a martian. This breaks all connectivity when using an exit node: DERP, DNS, control plane, and even the cloud metadata service.

Set src_valid_mark=1 when enabling the connmark rules so the rp_filter workaround actually works in these cases.

Updates #3310
Updates tailscale/corp#37846

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-04-27 13:52:45 -04:00
Brad Fitzpatrick 0e10a3f580 net/tsdial, ipn/localapi, client/local: let clients dial non-Tailscale addresses directly
Add a tsdial.Dialer.UserDialPlan method that resolves an address and
reports whether the dialer would route it via Tailscale. The LocalAPI
/dial handler now uses this to skip proxying for addresses that aren't
Tailscale routes (e.g. localhost), returning a Dial-Self response with
the resolved address so the client can dial it directly. This avoids
an unnecessary round-trip through the daemon for local connections.

The client's UserDial handles the new response by dialing the resolved
address itself, and the server passes the pre-resolved IP:port for
Tailscale dials to avoid redundant DNS lookups.

Thanks to giacomo and Moyao for pointing this out!

Updates tailscale/corp#39702

Change-Id: I78d640f11ccd92f43ddd505cbb0db8fee19f43a6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-27 09:33:27 -07:00
Andrew Lytvynov 649781df84 util/pidowner: remove unused package (#19521)
Added in 2020, this appears to be unused.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-27 09:25:46 -07:00
Andrew Lytvynov a70629eae3 util/topk: remove unsued package (#19524)
Added in 2024 and appears unused.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-27 09:13:40 -07:00
Andrew Lytvynov 346d6bb04c util/sysresources: remove unused package (#19523)
Added a few years ago and appears to be unused.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-27 09:13:30 -07:00
Andrew Lytvynov 64bb40b45b util/pool: remove unused package (#19522)
Added in 2024 and appears to be unused.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-27 09:13:14 -07:00
BeckyPauley 7477a6ee47 cmd/k8s-operator: use dynamic resource names in e2e ingress tests (#19536)
Replace hardcoded resource names with dynamically generated names in
k8s-operator-e2e ingress tests to avoid collisions with stale resources.

Updates #tailscale/corp#40612

Signed-off-by: Becky Pauley <becky@tailscale.com>
2026-04-27 13:40:46 +01:00
Evan Lowry 3a05c450ce posture: add HealthTracker for serial number retrieval (#19181)
Device posture checking can fail while enabled if tailscaled does not
have access to smbios. Previously, this was only observable by looking
in the tailscaled logs.

Fixes tailscale/corp#39314

Signed-off-by: Evan Lowry <evan@tailscale.com>
2026-04-25 15:42:47 -03:00
Brad Fitzpatrick f3b2f9b0ef all: fix duplicate package docs and tighten TestPackageDocs
TestPackageDocs walked into directories starting with "." (such as
.claude worktrees) and only logged warnings on duplicate package docs
across files in a directory. Skip dot-directories (which covers the
old .git but also .claude), ignore files with "//go:build ignore" so
command files don't falsely trip the duplicate check, and promote the
duplicate-doc warning to a t.Errorf.

While here, deduplicate the package docs that were previously only
logged: drop the redundant comment from client/systray/startup-creator.go,
move the comprehensive taildrop doc into feature/taildrop/doc.go, and
remove a leftover doc fragment from feature/condlite/expvar/omit.go.

The tstest/integration/vms allowlist is no longer needed since the
//go:build ignore filter now handles its dns_tester.go and udp_tester.go
files generically.

Fixes #19526

Change-Id: Id794d96bd728826a1883a054e4a244f90fa05d3d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-24 19:01:43 -07:00
Andrew Lytvynov 873b8b8e2e maths: remove unused package (#19516)
Added in 2025 and appears to be unused.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-24 16:17:10 -07:00
Andrew Lytvynov d64ed4af89 util/expvarx: remove unused package (#19519)
Added in 2024 and appears to be unused.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-24 16:16:42 -07:00
Andrew Lytvynov 4195e34f79 util/cstruct: remove unused package (#19518)
Added in 2022 and appears to be unused.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-24 16:09:54 -07:00
Andrew Lytvynov 323198b348 envknob/logknob: remove unused package (#19515)
Added in 2023 and appears to be unused.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-24 15:48:06 -07:00
James Tucker 1b40911611 wgengine/netstack: absorb all quad-100 traffic locally, never leak to peers
Previously, handleLocalPackets intercepted traffic to the Tailscale
service IP (100.100.100.100 / fd7a:115c:a1e0::53) only for an allow-list
of ports: TCP 53/80/8080 and UDP 53. Any other port returned
filter.Accept, letting the packet fall through to the ACL filter and
wireguard-go, which would attempt a peer lookup. No peer owns the
quad-100 AllowedIP, so after ~5s pendopen.go would log:

    open-conn-track: timeout opening ...; no associated peer node

This is the common "conntrack error no peer found for 100.100.100.100:853"
log spam seen in the wild (e.g. from systemd-resolved or another
resolver speculatively trying DoT on quad-100). It also leaks quad-100
packets onto the tailnet.

Remove the port allow-list so handleLocalPackets absorbs every quad-100
packet into netstack regardless of IP protocol or port. Traffic never
reaches the conntrack / peer-routing layers.

With the allow-list gone, acceptTCP needs a corresponding guard: on a
quad-100 TCP port we don't serve, execution used to fall through to the
isTailscaleIP case (quad-100 is in the tailscale IP range), which
rewrote the dial target to 127.0.0.1:<port> and forwardTCP'd the
connection to whatever happened to be listening on the host's loopback
at that port. Add a hittingServiceIP case that RSTs cleanly instead,
placed before the isTailscaleIP fallthrough.

TestQuad100UnservedTCPPortDoesNotForward is a new integration test that
injects a TCP SYN to 100.100.100.100:853 via handleLocalPackets, stubs
forwardDialFunc, and asserts the dialer is not invoked; it catches
regressions of the acceptTCP recursion/loopback-redirection case.

Fixes #15796
Fixes #19421
Updates #3261
Updates #11305

Signed-off-by: James Tucker <james@tailscale.com>
2026-04-24 12:42:16 -07:00
Brad Fitzpatrick 006d7e180e version: use debug.ReadBuildInfo in CmdName on non-Windows
CmdName was re-opening the running executable and scanning it in
64KiB chunks for the Go modinfo markers on every call. The same
modinfo is already parsed at startup and exposed via
runtime/debug.ReadBuildInfo, so prefer that on non-Windows. Windows
still takes the scanning path because its GUI-binary override keys
off the on-disk executable name.

benchstat of BenchmarkCmdName (Linux, before vs after):

    goos: linux
    goarch: amd64
    pkg: tailscale.com/version
    cpu: Intel(R) Xeon(R) 6975P-C
               │  /tmp/old.txt  │            /tmp/new.txt             │
               │     sec/op     │   sec/op     vs base                │
    CmdName-16   556045.5n ± 1%   825.6n ± 1%  -99.85% (p=0.000 n=10)

               │ /tmp/old.txt  │             /tmp/new.txt             │
               │     B/op      │     B/op      vs base                │
    CmdName-16   64.587Ki ± 0%   1.156Ki ± 0%  -98.21% (p=0.000 n=10)

               │ /tmp/old.txt │            /tmp/new.txt            │
               │  allocs/op   │ allocs/op   vs base                │
    CmdName-16     8.000 ± 0%   7.000 ± 0%  -12.50% (p=0.000 n=10)

Fixes #19486

Change-Id: I925c5e28b64815a602459beb6c8dab8779339a6c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-24 09:48:11 -07:00
Fran Bull 306fab796c feature/conn25: add the ability to return addresses to the IP Pools
This will be used as part of the address assignment expiry work.

Updates tailscale/corp#39975

Signed-off-by: Fran Bull <fran@tailscale.com>
2026-04-24 08:48:48 -07:00
kari-ts aa740cb393 ipnlocal/drive: reduce noisey per-peer remote logs (#19493)
This drops the per peer "appending remote" log while constructing the remote list, which can get noisy on big tailnets, and keeps logs around remote availability checks, including whether a peer is missing, offline, lacks PeerAPI reachability, lacks sharing permission, or is available.

Updates tailscale/corp#40580

Signed-off-by: kari-ts <kari@tailscale.com>
2026-04-24 08:26:33 -07:00
Andrew Lytvynov ad9e6c1925 go.mod: bump github.com/google/go-containerregistry (#19500)
This drops an indirect dependency on the old github.com/docker/docker
(which was replaced with github.com/moby/moby) and fixes a couple recent
CVEs.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-04-23 10:39:27 -07:00
Claus Lensbøl ee76a7d3f8 wgengine/magicsock: do not send TSMP disco when connected (#19497)
When there is an active connection between devices, do not send new
disco keys via TSMP.

Updates #12639

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-04-23 12:23:57 -04:00
Brad Fitzpatrick a7d8aeb8ae misc/genreadme,tempfork/pkgdoc,tsnet: generate README.md files from godoc
Adds a CI check to keep opted-in directories' README.md files in sync
with their package godoc. For now tsnet (and its sub-packages under
tsnet/example) is the only opted-in tree. The list of directories
lives in misc/genreadme/genreadme.go as defaultRoots, so CI and humans
both just run `./tool/go run ./misc/genreadme` with no arguments.

The check piggybacks on the existing go_generate job in test.yml and
fails if any README.md is out of date, pointing the user at the same
command.

Along the way:

 - tempfork/pkgdoc now emits Markdown instead of plain text: headings
   become level-2 with no {#hdr-...} anchors, and [Symbol] doc links
   resolve to pkg.go.dev URLs, including for symbols in the current
   package (which the default Printer would otherwise emit as bare
   #Name fragments with no backing anchor in a README). Parsing no
   longer uses parser.ImportsOnly, so doc.Package knows the package's
   symbols and can resolve [Symbol] links at all.

 - genreadme also emits a pkg.go.dev Go Reference badge at the top of
   a library package's README; suppressed for package main.

 - tsnet/tsnet.go's package godoc is expanded in idiomatic godoc
   syntax — [Type], [Type.Method], reference-style [link]: URL
   definitions — rather than Markdown-flavored [text](url) or
   backtick-quoted identifiers, so that both pkg.go.dev and the
   generated README.md render cleanly from a single source.

Fixes #19431
Fixes #19483
Fixes #19470

Change-Id: I8ca37e9e7b3bd446b8bfa7a91ac548f142688cb1
Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Signed-off-by: Walter Poupore <walterp@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-22 15:13:09 -07:00
Brad Fitzpatrick 311dd3839d wgengine/magicsock: replace peers slice with peersByID map; add Upsert/RemovePeer
Replace Conn.peers (sorted views.Slice) with peersByID, a
map[tailcfg.NodeID]tailcfg.NodeView. The only caller that needed
the sorted slice (the disco message receive path's binary search)
becomes a single map lookup. Drop nodesEqual.

Add Conn.UpsertPeer / Conn.RemovePeer for O(1) single-peer endpoint
work. RemovePeer also performs a targeted single-disco-key cleanup
(previously that scan was O(discoInfo)).

Extract the shared per-peer upsert body as upsertPeerLocked; still
used by SetNetworkMap's bulk path. SetNetworkMap is documented as
the bulk / initial / self-change path; UpsertPeer and RemovePeer
are preferred for single-peer changes.

Make the relay server set update O(1) per peer: add serverUpsertCh
/ serverRemoveCh to relayManager with matching run-loop handlers.
UpsertPeer / RemovePeer evaluate the per-peer relay predicate
locally and dispatch upsert or remove. The full-rebuild
updateRelayServersSet stays for the initial netmap, filter
changes, and fallback.

Move the hasPeerRelayServers atomic from Conn onto relayManager,
next to the serversByNodeKey map it summarizes. The run loop is
now the single writer and needs no back-pointer to Conn;
endpoint's two hot-path readers take one extra hop to
de.c.relayManager.hasPeerRelayServers but the cost is the same
atomic load.

No callers use UpsertPeer/RemovePeer yet; a subsequent change will
plumb per-peer add/remove through the incremental map update path.

Updates #12542

Change-Id: If6a3442fe29ccbd77890ea61b754a4d1ad6ef225
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-22 15:07:11 -07:00
Brad Fitzpatrick f289f7e77c tstest/natlab/vmtest,cmd/tta: add TestSiteToSite
Verifies that site-to-site Tailscale subnet routing with
--snat-subnet-routes=false preserves the original source IP
end-to-end.

Topology: two sites, each with a Linux subnet router on a NATted WAN
plus an internal LAN, and a non-Tailscale backend on each LAN. Backends
are given static routes pointing to their local subnet router for the
remote site's prefix; an HTTP GET from backend-a to backend-b over
Tailscale returns a body containing backend-a's LAN IP.

Adds the supporting vmtest.SNATSubnetRoutes NodeOption and plumbs
snat-subnet-routes through TTA's /up handler. The webserver started by
vmtest.WebServer now also echoes the remote IP, for the preservation
assertion.

Adds a /add-route TTA endpoint (Linux-only for now) and a vmtest
Env.AddRoute helper so the test can install the backend static routes
through TTA rather than needing a host SSH key and debug NIC.

ensureGokrazy now always rebuilds the natlab qcow2 (once per test
process, via sync.Once) so the test picks up the new TTA and webserver
behavior.

This is pulled out of a larger pending change that adds FreeBSD
site-to-site subnet routing support; figured we should have at least
the Linux test covering what works today.

Updates #5573

Change-Id: I881c55b0f118ac9094546b5fbe68dddf179bb042
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-22 12:11:30 -07:00
Fernando Serboncini 81fbcc1ac8 cmd/tsnet-proxy: add tsnet-based port proxy tool (#19468)
Exposes a local port on the tailnet under a chosen hostname. Raw TCP by
default; --http or --https reverse-proxy with Tailscale-User-* identity
headers from WhoIs, matching tailscaled's serve header conventions.

Useful as a one-shot to put a dev server on the tailnet.

Fixes #19467

Change-Id: I79f63cfbbedf7e40cf0f1f51cbae8df86ae90cdf

Signed-off-by: Fernando Serboncini <fserb@tailscale.com>
2026-04-22 13:34:18 -04:00
James 'zofrex' Sanderson 36f094ea3b ipn/ipnlocal: deflake TestStateMachine{,Seamless} (#19475)
Remove the remaining known sources of flakiness in TestStateMachine and
TestStateMachineSeamless.

Updates tailscale/corp#36230
Updates #19377

Signed-off-by: James Sanderson <jsanderson@tailscale.com>
2026-04-22 10:22:47 +01:00
Brad Fitzpatrick 12813dee02 tool/listpkgs: add --has-go-generate filter flag too
For use in parallelizing go:generate up-to-date checks.

Updates tailscale/corp#28679

Change-Id: Ifc31c56de4225ba2e0fc048b0f18974dc2f2fc82
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-21 17:51:13 -07:00
Fran Bull d7916d4369 feature/conn25: add expiresAt field to addrs
And use it to allow overwrites of old address assignments in the conn25 client.

The magic and transit address pools from which the addresses come are limited
resources and we want to reuse them. This commit is a small part of that bigger
need.

We expect to follow soon:
 * Extending expiry if assignments are still in use.
 * Returning expired addresses back to the pools so they can be reallocated.

Updates tailscale/corp#39975

Signed-off-by: Fran Bull <fran@tailscale.com>
2026-04-21 14:22:39 -07:00
Fran Bull 19544b4b81 feature/conn25: move byConnKey from addrAssignments to client
addrAssignments is a table of addrs with lookup indices, representing
the assignments of magic+destination+transit IP addresses the client has
made dut to the domain being routed because of an app
.
byConnKey is a map of node public key to prefixes of transit IPs, so it
is associated with, but not that data itself, and can be its own thing.

Updates tailscale/corp#39975

Signed-off-by: Fran Bull <fran@tailscale.com>
2026-04-21 14:22:39 -07:00
Walter Poupore 04415b8177 misc/genreadme: port from corp (#19477)
also port pkgdoc, into the tempfork folder

git rev from corp at the time this copy was made:

-  e909fc93595414c90ff1339cece7c84500ab3c36

Updates #19470

Change-Id: I3d98d82020a2b336647b795210dcb7065dfa44d7


Change-Id: Ie63141860b76dd2d5ae3ff52f8a4bcdf6106421e

Signed-off-by: Walter Poupore <walterp@tailscale.com>
2026-04-21 12:18:37 -07:00
Fernando Serboncini 1669b0d3d4 misc/git_hook: fix building git_hook in a nested worktree (#19473)
When the repo is checked out as a nested worktree, a go.work in the
outer tree hijacks module resolution, which makes the rebuild fails
with "main module does not contain package." Set GOWORK=off for the
build since the hook is self-contained.

Bumps HOOK_VERSION so existing installs pick up the fix.

Updates #cleanup

Change-Id: Ibd14849efc26e4e1893c5b8e300caa71573f54bd

Signed-off-by: Fernando Serboncini <fserb@fserb.com.br>
2026-04-21 11:42:53 -04:00
Brad Fitzpatrick 1e68a11721 logtail: run HTTP tests in-memory with memnet + synctest
TestEncodeAndUploadMessages waited on the default 2s FlushDelay,
making the logtail package the slowest non-integration test in
the tree (~2s real time). Switch the shared harness from an
httptest.Server-on-loopback to a memnet.Listener-backed *http.Server
and run the tests inside synctest.Test, so fake time advances the
flush timer instantly.

Drops the net/http/httptest dependency from these tests. Combined
with the TestMain non-localhost dial guard added in the previous
commit, no test in this package can accidentally reach the real
log.tailscale.com server. Whole package now runs in ~7ms.

Updates tailscale/corp#28679

Change-Id: Ie0e7a6a79641384ed0eecb99d767e17cda8bb944
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-20 13:33:10 -07:00
Brad Fitzpatrick 5b06e32f33 logtail: add Config.Disabled to suppress the startup banner
NewLogger unconditionally writes a "logtail started" banner before
it returns, which callers that later call Logger.SetEnabled(false)
have no way to suppress: the banner is already buffered for upload
by the time the caller gets the logger back.

Add Config.Disabled so callers that know up front they want the
logger to start disabled (e.g. Android's remote-logging opt-out)
can seed the state before NewLogger's internal Write. The process-
wide Disable kill switch still takes precedence; SetEnabled can
still flip the state at runtime.

Updates #13174
Updates tailscale/tailscale-android#695

Change-Id: Icc4fa88c198447cf0faa707264dac84e359fe52c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-20 13:33:10 -07:00
Adriano Sela Aviles 4a832d8d0f types/netmap,client/local: modify services format in local api
Reverting back to the previous format (including
the "svc:" prefix in the map's keys).

Note that the /services endpoint in localapi, along
with any software that relies on this is unreleased
so this does not break any clients.

Updates tailscale/corp#40052

Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
2026-04-20 09:22:23 -07:00
James 'zofrex' Sanderson ffae275d4d ipn/ipnlocal,tailcfg: add /debug/tka c2n endpoint (#19198)
Updates tailscale/corp#35015

Signed-off-by: James Sanderson <jsanderson@tailscale.com>
2026-04-20 16:00:03 +01:00
James 'zofrex' Sanderson ec86f0ff93 ipn/ipnlocal: make TestStateMachine less flaky (#19434)
TestStateMachine & TestStateMachineSeamless both flake a lot asserting the
"Shutdown" call on cc after a Logout. This is because Shutdown is called on
a goroutine to avoid a deadlock if it's called while holding the
LocalBackend lock (#18052).

This fixes that cause of flakes by waiting for LocalBackend's goroutine
tracker to have no goroutines running (so the goroutine that calls Shutdown
must have finished).

This does not make TestStateMachine non-flaky because it can flake later in
the test, too: the assertion on "unpause" after clearing the netmap between
"Start4" and "Start4 -> netmap" sometimes fails.

Updates tailscale/corp#36230
Updates #19377
Updates #18052

Signed-off-by: James Sanderson <jsanderson@tailscale.com>
2026-04-20 15:58:21 +01:00
Brad Fitzpatrick dfc2667f8f tstest/integration/testcontrol: make Stream w/ capver >= 68 match docs, prod
testcontrol wasn't following the document specs (and prod behavior) breaking
a WIP integration test elsewhere.

Updates tailscale/corp#40088

Change-Id: I02cf70894346bad7c85940b617d99c21c5310664
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-20 07:34:04 -07:00
Alex Chan cf76202aa3 ipn/ipnlocal: log the local and remote TKA HEADs during sync
Update this log message to show both the local and remote TKA HEAD; this
is useful for debugging issues on nodes that have fallen behind the
remote TKA HEAD.

Updates tailscale/corp#39455

Change-Id: Ia62ce15756180d2fbac4a898fb94d6143df08b54
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-04-19 16:52:48 +01:00
Scott Graham cb5a53c424 ipn/ipnlocal: preserve b.loginFlags in auto-login cc.Login calls
LocalBackend stores loginFlags at construction so that per-instance
properties (e.g. LoginEphemeral set by tsnet.Server.Ephemeral) persist
for the session. StartLoginInteractiveAs already merges b.loginFlags
into its cc.Login call, but the two auto-login call sites pass bare
controlclient.LoginDefault, silently dropping any stored flags.

Merge b.loginFlags at both auto-login call sites to match the existing
StartLoginInteractiveAs pattern. LoginDefault is zero so this is a
no-op when loginFlags is empty, and restores the documented behavior
when it isn't.

Fixes #15852

Signed-off-by: Scott Graham <scott.github@h4ck3r.net>
2026-04-17 23:31:18 -05:00
Adriano Sela Aviles 618dfd4081 client/local,types/netmap: modify services format in local api
Updates the format of the service map that is served over
the local api to be keyed without the "svc:" prefix. This
change is backwards incompatible, this is OK because there
is only one tailnet with the services-in-nodecapmap feature
flag enabled, and the client side changes that start showing
services over local api have not been released. (These were
added in 4fcce6000d).

Updates tailscale/corp#40052

Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
2026-04-17 14:14:03 -07:00
Fernando Serboncini 514d7d28e7 misc/git_hook: extract shared githook package; auto-rebuild on version bump (#19440)
Pull the hook logic into a reusable githook library package so
tailscale/corp can share it via a thin wrapper main instead of
keeping a forked copy in sync.

The install flow also changes: a wrapper scripts now build the
binary and reinstall the git hooks. Pulling new shared code no
longer requires re-running the installer.

Updates tailscale/corp#39860

Change-Id: I4d606d11c8c883015c190c54e3387a7f9fe4dd32

Signed-off-by: Fernando Serboncini <fserb@tailscale.com>
2026-04-17 16:24:39 -04:00
Brad Fitzpatrick 1fbb834dc3 logtail: add Logger.SetEnabled to toggle uploads at runtime
Callers that need to turn logtail uploads on and off in response to
user preference or policy changes previously had no choice: the
package-level Disable is a one-way kill switch intended for the
controlplane DisableLogTail debug message, and requires a process
restart to undo.

Add a per-Logger disabled flag, toggled via SetEnabled, that drops
incoming entries without buffering while disabled. The process-wide
Disable still takes precedence, so a controlplane-issued kill switch
cannot be overridden by a client setting it back on.

To simplify https://github.com/tailscale/tailscale-android/pull/695

Updates #13174

Change-Id: I06e75bd719c851f5f837ca5b2d1e17f7c68355f0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-17 12:19:39 -07:00
kari-ts 8dda62cc24 feature/clientupdate: windows update should use tailscale.exe update (#19438)
Currently, clientupdate.NewUpdater().Update() is called directly inside tailscaled, which fatals. There is also a failure that doesn't return, causing a panic.

This fix allows us to use the same approach as startAutoUpdate, which is to find tailscale.exe and run tailscale.exe --update, though since it's calling the updater library directly, we get progress messages.

Fixes tailscale/corp#40430s

Signed-off-by: kari-ts <kari@tailscale.com>
2026-04-17 10:28:35 -07:00
BeckyPauley b239e92eb6 cmd/k8s-operator: add e2e test setup and l7 ingress test for multi-tailnet (#19426)
This change adds setup for a second tailnet to enable multi-tailnet e2e
tests. When running against devcontrol, a second tailnet is created via the
API. Otherwise, credentials are read from SECOND_TS_API_CLIENT_SECRET.

Also adds an l7 HA Ingress test for multi-tailnet.

Fixes tailscale/corp#37498

Signed-off-by: Becky Pauley <becky@tailscale.com>
2026-04-17 17:03:25 +01:00