Commit Graph

11009 Commits

Author SHA1 Message Date
Brad Fitzpatrick
82cfea90ca all: fix JSON serialization under Go 1.27's finalized encoding/json/v2
Some checks failed
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=darwin, arm64, darwin, macOS) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=linux, amd64, linux, Linux) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=windows, amd64, windows, Windows) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=1/4, amd64, linux, Portable (1/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=2/4, amd64, linux, Portable (2/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=3/4, amd64, linux, Portable (3/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=4/4, amd64, linux, Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Go 1.27 enables GOEXPERIMENT=jsonv2 by default: encoding/json is now
backed by the json/v2 machinery, and github.com/go-json-experiment/json
compiles as a thin alias of the standard library's encoding/json/v2.
Several tag options and behaviors we relied on did not make the cut for
the final Go 1.27 API, breaking tailscaled at runtime and four packages'
tests. This change adapts to the final API while keeping the wire format
byte-for-byte identical on all Go versions.

First, the `format` tag option was demoted to experimental. Its mere
presence in a struct tag now makes marshaling and unmarshaling fail at
runtime. tailcfg.SSHAction.SessionDuration had `format:nano` (added in
a2dc517d7 to pin the v1 representation), so on Go 1.27 any netmap
containing an SSH policy failed to decode, breaking every PollNetMap.
Remove the option here and in net/speedtest; time.Duration still
marshals as int64 nanoseconds under encoding/json on all Go versions
(Go 1.27's v1 mode sets FormatDurationAsNano by default), so old
clients and servers are unaffected. Add a regression test locking in
the exact wire format.

Consequently, invert the cmd/vet jsontags rule: it previously required
an explicit `format` tag on time.Duration fields, which is now exactly
wrong. It now rejects any `format` tag option, which would have caught
this bug in CI.

Second, the `inline` tag option was renamed to `embed`. The standard
library silently ignores `inline`, while the pinned go-json-experiment
module (used on Go 1.26) only knows `inline`. Specify both options in
types/prefs and logtail; each implementation ignores the option it does
not know, producing identical output. Drop `inline` once we require
Go 1.27.

Third, encoding/json (v1) now dispatches to MarshalJSONTo and
UnmarshalJSONFrom methods and its v1 options flow into nested
jsonv2.MarshalEncode calls. Types whose v1 methods deliberately
routed through jsonv2 for v2 semantics (types/opt.Value, the
types/prefs preference types) would silently change wire format
(e.g. nil slices becoming null). Pin jsonv2.DefaultOptionsV2 in
their jsonv2 methods so the representation is the same regardless
of the entry point.

Finally, json.Marshal costs one more allocation under Go 1.27,
tripping the types/logger.AsJSON alloc test. Switch its fmt.Formatter
to jsonv2.MarshalWrite with explicit v1 options, which writes directly
to the fmt.State: one allocation on both toolchains with unchanged
output. Depaware files pick up the go-json-experiment/json/v1 options
shim as a new dependency of types/logger.

With this change, go test ./... passes with both Go 1.26.5 and
go1.27rc2.

Updates #20220
Fixes #20528
Fixes #20254

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I694c7d57fd81e55a579c579e9be10032bca569d4
2026-07-19 09:58:50 -07:00
Brad Fitzpatrick
ece1b12ebf cmd/tsconnect/wasm: don't return non-nil net.Conn interface on dial error
The NetstackDialTCP/UDP hooks returned the result of DialContextTCP/UDP
directly, so on error they returned a non-nil net.Conn interface holding
a nil *gonet.TCPConn or *gonet.UDPConn pointer, tripping up callers that
check the interface against nil and then call Close, crashing the wasm
worker. Apply the same fix that 46bdbb387 made for tailscaled and tsnet.

Fixes #20529

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I4fd66bb7615ee9b2d204256a43288ed7b7a12f35
2026-07-19 06:48:06 -07:00
Brad Fitzpatrick
7be0054a7b words: redress historical wrongs against the tuatara
b5a41ff381 originally added both tuatara and mispelled tautara,
one as a tail and one as a scale.

f174ecb6fd added tuatara as a scale, not noticing the tautara
imposter.

Fixes #20522

Change-Id: Ie4fcb262ac705c766f55d406835de419856bb170
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-18 15:34:33 -07:00
Nick Rossi
b91e844014
util/def,cmd/containerboot: add LookupEnv, simplify env parsing (#20277)
Simplifies cmd/containerboot env var parsing. Most of the private helpers did
not earn their abstraction: defaultEnv(name, "") is just os.Getenv(name), and
the rest collapse into cmp.Or and the existing def.Bool. defaultEnv,
defaultEnvs and defaultBool are gone.

Adds def.LookupEnv, the env companion to def.Bool, for the one case that needs
it: TS_KUBE_SECRET, where an explicit "" disables Kubernetes secret storage and
must stay distinct from unset (cmp.Or cannot express that).

Updates #20018

Signed-off-by: Nick Rossi <nrossi0530@gmail.com>
2026-07-17 18:32:52 -07:00
Brendan Creane
d2af6a4d39
net/dns/publicdns: don't upgrade Control D port-53-only addresses to DoH (#20463)
DoHEndpointFromIP mapped the entire 2606:1a40::/48 range to a
dns.controld.com/<id> DoH URL, but the ID-encoded addresses in that range
are legacy plaintext-DNS endpoints that refuse :443. They now fall through
as ordinary port-53 resolvers; the free anycast freedns.controld.com/pN
addresses still upgrade via exact match.

Fixes #20433

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-07-17 15:33:17 -07:00
Mike Jensen
689c6c2e6d
ipn/ipnlocal: reject SrcCaps-based packet filter rules for unsigned peers (#20513)
This change ensures `packetFilterPermitsUnlockedNodes` also considers SrcCaps-based grants when checking for unsigned peer access.

Fixes tailscale/corp#45116

Change-Id: I0ac938367888f67ed6f355fc19959cc8c31722a2

Signed-off-by: Mike Jensen <mikej@tailscale.com>
2026-07-17 15:58:06 -06:00
Brendan Creane
bab3f5fce7
wgengine/router/osrouter: remove orphaned tailnet addrs on cleanup (#20304)
* net/tsaddr: unmap IPv4-mapped IPv6 addrs in IsTailscaleIP

IsTailscaleIP branched on ip.Is4() before checking the CGNAT range, so an
IPv4-mapped IPv6 address (e.g. ::ffff:100.64.0.1) took the IPv6 path and was
tested only against the ULA range, wrongly returning false for a Tailscale
CGNAT address. Unmap at the top so both forms are classified identically;
Unmap is cheap and IsTailscaleIPv4 stays IPv4-only for callers that need it.

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

* wgengine/router/osrouter: remove orphaned tailnet addrs on cleanup

The orphan-address sweep added in #20199 ran only inside Router.Set, so the
teardown path (tailscaled --cleanup, and the unconditional cleanup at daemon
start) never removed stale Tailscale addresses a previous instance left on a
persistent tailscale0 -- it only flushed iptables/nftables.

Wire address removal into cleanUp: with no desired config, every Tailscale-range
address on the interface is an orphan, so enumerate and delete them all (IPv4
and IPv6, best-effort) in removeOrphanedAddrsForCleanup.

tailscaleInterfaceAddrs now yields the interface's addresses as an
iter.Seq[netip.Prefix], and the filters compose lazily over it: tailscaleAddrs
(every Tailscale-range address; used by cleanup), deletableAddrs (isDeletableAddr:
Tailscale-range and deletable now, i.e. excluding v6 when v6 is unavailable; used
by the live Set sweep), and orphanedAddrs (drops the desired addresses). The Set
sweep ranges the composed iterator directly, so no throwaway slices are built.
delAddress is made idempotent: it attempts both the loopback-rule teardown and
the address delete and joins their errors, so a missing firewall rule can't leak
the address, and it no longer no-ops on v6 (cleanup relies on that to remove v6
orphans even when this process never brought IPv6 up).

The Set-time sweep is otherwise unchanged; re-running it on network changes
(netmon) for late orphans remains a follow-up (tailscale/corp#43882).

Updates #19974
Fixes tailscale/corp#44173

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

---------

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-07-17 14:18:09 -07:00
Brad Fitzpatrick
0433cc6929 feature/syslog, cmd/tailscaled, logpolicy: add optional --syslog flag
Add a new modular syslog feature providing a tailscaled --syslog flag
that sends the daemon's logs to the system syslog daemon instead of
stderr, which is useful when running as a daemon without a service
manager that captures stderr (e.g. OpenWrt's procd).

The feature package registers two new hooks: one to register its flag
before flag parsing, and one that tailscaled calls early in main to
redirect the standard library's default logger. Because logpolicy later
points the default logger at logtail, whose local console copy writes
to stderr, logpolicy now also consults the hook and sends its console
copy to the same sink (with timestamps disabled, as syslog records its
own).

The feature is linked by default only on Linux, FreeBSD, and OpenBSD,
and can be removed with the ts_omit_syslog build tag. If connecting to
the syslog daemon fails at startup, tailscaled logs a warning and
continues logging to stderr.

Fixes #16270

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I8f3a92d4c1e6b70a5d29e4f61b3c874250a9de13
2026-07-17 13:55:23 -07:00
yaruk-byte
def265083b
tstest/integration: run a smoke test against a Windows tailscaled service (#20382)
* tstest/integration: run a test against a real Windows tailscaled service

Updates #20381

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>

* tstest/integration: serialize Windows service tests and clean up state

Updates #20381

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>

* tstest/integration: address review feedback on Windows service tests

Updates #20381

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>

* tstest/integration: use background context for service teardown

Updates #20381

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>

* tstest/integration: run Windows service test via the normal windows CI run

Updates #20381

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>

* tstest/integration: address review feedback on Windows service tests

Updates #20381

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>

---------

Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>
2026-07-17 12:29:12 -07:00
Alex Chan
11a6255b22 scripts/installer.sh: remove an unused PACKAGE_NAME variable
This variable wasn't used in the commit when it was introduced (bd5c509).

Fixes #19841

Change-Id: I82a2ba613c71eb99d98c5e7063e8cd077ba03ece
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-07-17 20:12:50 +01:00
Claus Lensbøl
9175fe2675
net/tstun: drop TSMP messages injected into TUN (#20511)
Enforce that TSMP messages are only accepted for transmission over the
wireguard connection from within the client.

Updates tailscale/corp#45059

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-07-17 15:10:08 -04:00
Claus Lensbøl
82a381e54b
control/controlclient,net/tstun,wgengine/magicsock: fix handling of zero keys in TSMP (#20508)
Updates tailscale/corp#45042

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-07-17 14:02:53 -04:00
Brendan Creane
c1edf7f458
wgengine/router/osrouter: sanitize interfaceV6UsableForTun path with os.OpenInRoot (#20505)
interfaceV6UsableForTun interpolates the interface name into a /proc path.
A plain filepath.Join + os.Open only cleans the path, so a tunname with
".." (or a symlinked component) could read outside /proc/sys/net/ipv6/conf.
Open under that fixed directory with os.OpenInRoot, which rejects any path
escaping the root (openat-based, so also TOCTOU-resistant), still using
filepath.Join to build the relative name. See https://go.dev/blog/osroot.

Updates #20447

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-07-17 10:17:06 -07:00
Jordan Whited
cc0b3ddbbe net/packet: add TSMPType docs
Updates #cleanup

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-07-17 09:56:25 -07:00
Jordan Whited
3076698cdf net/packet: fix TSMPType docs
Updates #cleanup

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-07-17 09:28:49 -07:00
Jordan Whited
94381a191a disco: fix UDPRelayEndpoint.AddrPorts slice cap math
Fixes tailscale/corp#45066

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-07-17 09:11:12 -07:00
Michael Ben-Ami
7ec9b7ffa3 feature/conn25: preserve TTL on DNS rewrites
We were accidentally hardcoding TTL 0 before.

Fixes tailscale/corp#45025

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-07-17 10:27:14 -04:00
Brendan Creane
cfd101f9d7
configure DNS even when router.Set fails (#20488)
Some checks failed
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=darwin, arm64, darwin, macOS) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=linux, amd64, linux, Linux) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=windows, amd64, windows, Windows) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=1/4, amd64, linux, Portable (1/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=2/4, amd64, linux, Portable (2/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=3/4, amd64, linux, Portable (3/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=4/4, amd64, linux, Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
* wgengine: configure DNS even when router.Set fails

Reconfig configured the router first and returned on any router.Set error,
before the DNS block ran. On a host where router config fails on every
reconfig -- e.g. a tun MTU below 1280 that breaks IPv6, or a kernel missing
netfilter features -- the OS resolver was never told about MagicDNS or the
tailnet search domain, so tailnet names failed to resolve with no DNS error
in the logs.

Record the router error and continue instead of returning on it, still
attempt dns.Set, and join the router, DNS, and VPN-reconfigure errors into
the return value. DNS stays after router config (still needed: some DNS
managers refuse to apply settings before the device has an address); only
the error coupling is broken. Fixes a regression from 84430cdfa (v1.8.0).

Updates #20447

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

* wgengine/router/osrouter: gate IPv6 on per-interface support, not just global

getV6Available reported IPv6 usable whenever the netfilter runner reported
global IPv6 support, missing the case where the kernel has IPv6 but has not
enabled it on tailscale0 specifically -- e.g. when the tun MTU is below the
1280-byte IPv6 minimum, so /proc/sys/net/ipv6/conf/tailscale0 never exists
and the v6 address and route adds fail, aborting the whole Set. See #20447.

AND a per-interface check into getV6Available, evaluated per call so a later
Set picks up v6 if the interface gains it. All v6-gated operations funnel
through getV6Available, so Set now skips v6 gracefully instead of erroring.
Also remove the dead r.v6Available field that masked this with its global
name.

Updates #20447

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

---------

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-07-16 14:53:30 -07:00
Michael Ben-Ami
b2de420e3d feature/conn25,types/appctype: serve active Conn25 state over localapi
At /v0/conn25-state.

State includes whether the node is configured for Connectors 2025, as
well as client-specific and connector-specific state, if the node is
acting in those contexts.

Client-specific state includes the reserved Magic IPs and Transit IPs on
the client that have not been returned to their IP pools, and their
associated apps, domains, real destination IPs, and active flow counts.

We also report IP pool utilization: the number of magic and transit IPs
in use versus each pool's capacity, split by IP family.

Connector-specific state includes a peer list of clients that have
registered Transit IPs with the connector, and the apps are real
destination IPs the Transit IPs map to.

Updates tailscale/corp#40125

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-07-16 16:35:38 -04:00
Mike Jensen
71e5a98404
Update tailscale/gliderssh to pull in tailscale/gliderssh#12 (#20485)
Updates tailscale/corp#41997

Change-Id: I5fb3d4705766deb71abd0b79a186e99e86be0b15

Signed-off-by: Mike Jensen <mikej@tailscale.com>
2026-07-16 13:46:23 -06:00
Jordan Whited
6a2aa6889e go.mod: bump wireguard-go for priority msg callback
Updates #20081

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-07-16 08:59:34 -07:00
ayanamist
50f1c285ba ipn/ipnlocal,cmd/tailscale/cli: support unix socket targets for TCP serve
Allow `tailscale serve --tcp <port> unix:/path/to/socket` and `tailscale serve --tls-terminated-tcp <port> unix:/path/to/socket` to forward TCP connections to a Unix domain socket. Previously only host:port targets were supported for TCP serve mode.

Updates #20161

Signed-off-by: ayanamist <ayanamist@gmail.com>
2026-07-16 09:13:38 -06:00
Alex Chan
38345dce3d .github: double the timeout for go vet in CI
The job is consistently failing on main when it hits the 5 minute
timeout; let's double it to get useful results.

Updates #cleanup

Change-Id: Iaff2f95d4944929e6832273c94d628f376e2d30e
Signed-off-by: Alex Chan <alexc@tailscale.com>
2026-07-16 16:05:04 +01:00
Mike O'Driscoll
71b90de0d4
derp/derpserver,cmd/derper: use slices.Clip for cert chain copies (#20484) 2026-07-15 22:13:27 -04:00
Mike O'Driscoll
bf7d815631
derp/derpserver,cmd/derper: don't mutate cert provider's shared tls.Certificate (#20478)
ModifyTLSConfigToAddMetaCert (and its inline copy in cmd/derper) appended
the DERP meta cert directly to the *tls.Certificate returned by the
underlying GetCertificate. autocert returns a certificate sharing a cached
chain slice (and, on the TLS-ALPN token path, the same pointer) across
concurrent handshakes, so the in-place append was a data race and could
grow the served chain unboundedly.

Return a shallow copy with the meta cert appended to a fresh backing
array instead, and have cmd/derper reuse ModifyTLSConfigToAddMetaCert
rather than duplicating the wrapper.

Fixes #20352

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-07-15 17:16:40 -04:00
Brad Fitzpatrick
bef2cd8088 .github, tstest/natlab/vmtest: replace old VM runner job with natlab tests
The "vm" CI job ran a single test (TestRunUbuntu2404 from
tstest/integration/vms) on a privileged self-hosted runner. Its coverage
is nearly all redundant with the modern natlab vmtest suite, which boots
real Ubuntu VMs and already exercises connectivity, kernel TUN, SSH,
Taildrop, ACME, and OS DNS integration on GitHub-hosted runners.

The two things it tested that natlab didn't are added back as natlab
tests so the runner can be decommissioned:

TestUbuntuSystemdUnit runs tailscaled via the stock systemd unit that
Linux packages ship (cmd/tailscaled/tailscaled.service with
tailscaled.defaults as its EnvironmentFile) instead of launching the
binary directly, verifying the unit's directives and its Type=notify
readiness handshake.

TestDNSExtraRecordsSearchDomains verifies that control-plane DNS
ExtraRecords and search domains are resolvable through the guest's OS
resolver (libc to systemd-resolved to quad-100).

Updates #13038

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I8d0dfb8b8153289e7ca78f3af03dfece9497bfe8
2026-07-15 15:25:14 -04:00
Brad Fitzpatrick
6bf05cb63e ipn, ipn/ipnlocal: remove darwin & ios from goosGetsLegacyNetmapNotify
The Apple clients' last consumer of the legacy Notify.NetMap field was
converted to peer deltas in tailscale/corp#44962, so tailscaled no
longer needs to build and emit full netmaps on the IPN bus for darwin
and ios. Windows is now the only remaining platform on the legacy path.

Updates #12542

Change-Id: I295d826735191bb601d2b69d8d85d37a5a82b6c9
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-15 14:47:16 -04:00
Brad Fitzpatrick
168b20d3b4 ipn/ipnlocal: remove android from goosGetsLegacyNetmapNotify
The android client was converted in https://github.com/tailscale/tailscale-android/pull/797

Updates #12542

Change-Id: Ibb2cc6fbafdad93ae44e1a60e5cc5de8183f9b97
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-15 13:41:08 -04:00
Brad Fitzpatrick
65fd320aa6 ipn/ipnlocal: use the live peer map, not the netmap's stale Peers slice
The nodeBackend's netMap.Peers slice is frozen at the last full netmap
install; the live per-peer state lives in the nodeBackend.peers map,
updated by delta mutations. Three spots still read the stale slice or
paid to materialize a fresh one:

AppendMatchingPeers iterated netMap.Peers and re-looked-up each ID in
the peers map (with a lock round-trip per peer), so peers added by a
delta since the last full netmap were invisible to it. That affected
its callers: taildrop's file-target list, exit node suggestions, and
conn25's connector discovery. It now snapshots the peers map directly
(sorted by node ID, matching the old netmap ordering).

DebugPeerDiscoKeys read netMap.Peers and so returned stale disco keys
after deltas. It now reads the peers map via the new
nodeBackend.peerDiscoKeys.

pingPeerAPI called NetMapWithPeers, building and sorting the full
O(n) peer slice, just to linearly scan it for one IP. It now uses the
nodeBackend's existing by-address index and the new O(1) PeerByID
accessor, and passes the peers-free netmap to peerAPIBase, which only
reads the self node's addresses.

Updates #12542

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I2e57527d64733b4eb17006f896faaa907b1d128c
2026-07-15 13:29:26 -04:00
Mario Minardi
0bae201912 tstest/natlab: split SSH test into separate tests
Split TestTailscaleSSH into separate tests per host OS being tested to
allow for potentially running these tests in parallel on different
machines.

Updates https://github.com/tailscale/tailscale/issues/13038

Signed-off-by: Mario Minardi <mario@tailscale.com>
2026-07-15 10:53:13 -06:00
Brad Fitzpatrick
0fb8226708 gokrazy, tstest, cmd/vnet: switch amd64 kernel to gokrazy/kernel.amd64
The tailscale/gokrazy-kernel module was a fork of rtr7/kernel that
stalled at Linux 6.8.9 (July 2024). All of the kernel config options we
had added in that fork (ENA, Xen for EC2, virtio-mmio for qemu microvm,
virtio RNG, IPv6 policy routing, netlink diag, etc) are now present in
the gokrazy project's own gokrazy/kernel.amd64 module, which tracks
current kernel.org releases (Linux 7.1.3 as of this change) and is the
gokrazy project's supported kernel for x86_64 PCs and VMs.

Switch the tsapp and natlabapp images, the natlab VM tests, and the
CI workflow to gokrazy/kernel.amd64, drop the tailscale/gokrazy-kernel
dependency, and update gokrazy/kernel.arm64 to latest while here.

Verified with TestEasyEasy, TestJustIPv6, and TestTailscaleSSH in
tstest/natlab/vmtest with --run-vm-tests.

Updates #1866

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I90c3765a4e18f5609b4d77b51ac38d17c8e3688a
2026-07-15 11:45:02 -04:00
Brad Fitzpatrick
4660a961eb ipn/ipnlocal, wgengine/wgcfg/nmcfg: stop building peer lists on delta path
Processing a peer add/remove delta still materialized the full netmap
(an O(n) slicesx.MapValues plus sort over all peers, at 10k+
peers in a large tailnet) twice per delta: once in UpdateNetmapDelta
purely to hand the self node to Engine.SetSelfNode, and once in
authReconfigLocked.

Neither needs peers anymore. SetSelfNode gets the self node from the
existing nodeBackend.Self accessor. authReconfigLocked only reads
self-node fields (SelfNode, NodeKey, GetAddresses, HasCap) now that
WireGuard peers ride the incremental route manager and per-peer config
source, so it can use the peers-free NetMap accessor.

That also makes nmcfg.WGCfg vestigial: since wgcfg.Config lost its
Peers field, its peer walk existed only to emit the [v1] skip logs
(expired peers, unselected exit nodes, unaccepted subnet routes),
duplicating filtering the route manager already does. Delete the
package and construct the two-field wgcfg.Config inline. The skip
logs go away; if they're missed, the route manager can log them
incrementally at upsert time instead of rescanning every peer on
every reconfig.

With this, the runtime.DidRange analysis (see the ts_rangehook test)
shows a delta netmap update performing no O(n) range loops except
updateRouteManagerExtras, and the delta phase of that test drops from
1.09s to 0.14s for 400 deltas at n=10000 (from 4.79s at the
start of this effort, before the incremental route manager work).

Updates #12542

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ia0e03ef9db0c988790b2c29de1f0505305e93f58
2026-07-15 11:21:36 -04:00
Brad Fitzpatrick
3515b009c2 ipn/ipnext, ipn/ipnlocal, feature/conn25: pass peer seq to AllowedIPs hook
The ExtraWireGuardAllowedIPs hook was called once per peer on every
authReconfig, so each netmap delta paid an O(n) scan over all peers
even when conn25 (the only implementer) wasn't configured and every
call returned nothing.

Invert the API: the hook now receives an iter.Seq2 of the current
peers and returns the extra prefixes keyed by node ID. An idle
extension returns nil without iterating, so the unconfigured case
does no per-peer work at all.

With this, the runtime.DidRange analysis (see the ts_rangehook test)
no longer reports the updateRouteManagerExtras peer scan on netmap
deltas.

Updates #12542

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9181e77416fa22f4c904620d42e9bcb934165216
2026-07-15 10:12:58 -04:00
coyaSONG
f68e4d93fd cmd/tailscale/cli: fix plain TCP serve status
Do not label plain TCP forwarding as TLS over TCP. Render status
annotations only when TLS termination or PROXY protocol is configured.

Fixes #20367

Change-Id: I3f6507365ceedc2950451810e9715afb85176fc5
Signed-off-by: coyaSONG <66289470+coyaSONG@users.noreply.github.com>
2026-07-15 08:01:37 -06:00
Kristoffer Dalby
a534ad5e86 gokrazy/build: expose AMI pipeline as composable steps
Make CheckAWSAuth, UploadToS3, ImportSnapshot, and RegisterAMI public so
a build server can run them individually (e.g. Marketplace publishing
after RegisterAMI). Rename BuildAMI to BuildAndImportAMI, now a thin
orchestrator over them. Each step records into Result and returns its
artifact; the AWS steps guard on their predecessor and error clearly if
called out of order.

Updates #1866

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-15 15:15:17 +02:00
Kristoffer Dalby
720cd0a2d0 all: regenerate dep manifests for aws-sdk-go-v2/service/ec2
Generated by make updatedeps and ./tool/go run ./tool/updateflakes
after adding service/ec2 (and the smithy-go bump it pulls in).

Updates #1866

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-15 15:15:17 +02:00
Kristoffer Dalby
a7f3e08335 gokrazy/build: use AWS SDK instead of shelling out to aws CLI
Replace the four aws CLI shell-outs (s3 cp, ec2 import-snapshot,
describe-import-snapshot-tasks, register-image) with aws-sdk-go-v2 S3
and EC2 clients. Credentials come from the SDK default chain, so
existing aws sso login / aws configure / AWS_PROFILE / env / aws-vault
sessions keep working.

Verify auth via sts:GetCallerIdentity before the slow image build so a
logged-out user fails in seconds, not minutes. Upload via the S3
manager (concurrent multipart) with a native progress reader, log
[n/4] step lines, and bail on terminal import-snapshot failure states
instead of polling forever.

Updates #1866

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-15 15:15:17 +02:00
Kristoffer Dalby
b049ce71a5 gokrazy/build: show import-snapshot progress, quiet in CI
Capture the AWS Progress/StatusMessage fields and report them instead of
reprinting the full describe-import-snapshot-tasks JSON every 5s. On a
terminal, repaint one live percentage line; otherwise log one line per
phase change so CI stays terse.

Updates #1866

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-15 15:15:17 +02:00
Kristoffer Dalby
7653a1e438 gokrazy: split build.go into thin main + reusable build package
Move the appliance/AMI build logic into tailscale.com/gokrazy/build so
Go callers (e.g. flash-appliance) can call it directly instead of
driving build.go over --json. build.go is now a thin flag wrapper; flags
and --json output are unchanged. Package-level state becomes a Builder
with an exported Config, ctx-first steps, and a Build one-shot.

Updates #1866

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2026-07-15 15:15:17 +02:00
Brad Fitzpatrick
bb4f458207 feature/captiveportal: move captive portal code out of ipnlocal, netcheck
Captive portal detection was half-migrated: it had a build tag and
buildfeatures constant, but its code still lived in build-tag-gated
files in ipn/ipnlocal and net/netcheck, with its per-backend state
(context, cancel func, signaling channel) as fields on LocalBackend.

Move it under feature/captiveportal. The health-driven detection loop
becomes an ipnext.Extension holding its own state: it starts and
stops the loop from the BackendStateChange hook and subscribes to
health.Change events on the eventbus itself, removing the captive
portal hooks and special cases from LocalBackend entirely. The DERP
map now comes from a new ipnext.NodeBackend.DERPMap method, and the
preferred DERP region from magicsock's last netcheck report (the
same underlying source as the previously used Hostinfo.NetInfo).

The netcheck probe hook is now exported with a signature free of
netcheck internals, and its implementation moves to the small
feature/captiveportal/netcheckhook package, which installs the hook
as an import side effect. That package stays free of tsd/wgengine
dependencies so the tailscale CLI can keep probing for captive
portals in "tailscale netcheck" without linking the daemon-side
extension. The net/captivedetection library itself is unchanged and
stays put; after this change it is only linked when something pulls
in netcheckhook or the feature extension.

tailscaled links the feature by default via condregister as before,
but tsnet no longer does (shrinking tsnet, k8s-operator, and tsidp);
tsnet users who want it can blank-import the feature package, and
tsnet's dep test now locks that in.

Updates #12614

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I3f1d09f9dc03e18f9a648ab5e42d16fa540b3fa9
2026-07-14 20:22:23 -04:00
Brad Fitzpatrick
72ca0cae4b wgengine/wgcfg,wgengine,ipn/ipnlocal: remove Peers from wgcfg.Config
The wireguard-go device now learns its peer set solely from the live
per-peer config source that LocalBackend installs with
Engine.SetPeerConfigFunc, backed by the route manager. Peers are
created lazily on first packet and converged per peer with
Engine.SyncDevicePeer, so the full-peer-list snapshot in wgcfg.Config
and the diff-and-reconfigure machinery around it (wgcfg.Peer,
ReconfigDevice, and the engine's full device sync in
maybeReconfigWireguardLocked) are dead weight: they duplicated state
that the route manager already owns and forced every netmap change to
rebuild and rehash the entire peer list.

Delete the Peers field and the Peer type from wgcfg, along with
ReconfigDevice and maybeReconfigWireguardLocked. Engine.Reconfig no
longer does any device peer work; it only manages the private key,
addresses, and the non-peer subsystems. Full-netmap application converges the device by
syncing exactly the peers whose routes the route manager reports as
changed or removed.

Updates #12542

Change-Id: Ic776e42cfaa5be6b9329b3d381d5cbde17d7078b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-14 19:57:59 -04:00
Brad Fitzpatrick
87c0d36942 net/routemanager,ipn/ipnlocal: name the changed-allowed-IPs map type
The map[key.NodePublic][]netip.Prefix that flows from route manager
commits to WireGuard device syncs has subtle semantics (a nil value
means the peer was removed or no longer contributes any prefixes)
that were documented on routemanager.Result.AllowedIPs and then
re-documented, or not, at each signature that passed it along. Give
it a named type, PeersWithRouteChanges, and document the nil
semantics once on it.

Updates #12542

Change-Id: I2566361a5331eb11b2b70a5bcdb497cc20ee561d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-14 19:57:59 -04:00
Brad Fitzpatrick
f0ce89b715 net/tstun,wgengine,ipn/ipnlocal: make tstun's peerConfigTable use RouteManager table
Previously tstun.Wrapper.SetWGConfig walked wgcfg.Config.Peers on every
netmap to rebuild its own IP-to-peer table for masquerade NAT rewrites
and jailed-peer classification. Now the tun layer instead consumes the
route manager's shared immutable outbound snapshot directly, via a new
Engine.SetPeerRoutes method: LocalBackend pushes the snapshot (plus this
node's native Tailscale addresses) after every route manager commit that
can change it, and per-packet lookups read the interned PeerRoute
attributes from that table.

When no current peer is jailed or masqueraded, LocalBackend installs a
nil table (gated on RouteManager.HasDataPlaneAttrs), preserving the
per-packet nil-check fast path. The exitNodeRequiresMasq machinery is
deleted: its purpose was populating the table with all peers so that
more-specific entries shadow an exit node's /0, and the always-full
route manager table gives that shadowing inherently.

This is the last step before removing the Peers field from wgcfg.Config.

Updates #12542

Change-Id: Ifce09ca929a3f2511303ca1d6efdd583739494ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-14 12:41:11 -07:00
Mario Minardi
e4144230f4 util/osuser: reject leading dashes in usernames
Reject leading dashes in usernames and add double dash to getent call
on linux to prevent values sent as usernames being interpreted as
command options.

Fixes https://github.com/tailscale/corp/issues/44813

Signed-off-by: Mario Minardi <mario@tailscale.com>
2026-07-14 12:59:07 -06:00
Brad Fitzpatrick
9d01b036c7 tstest/natlab/vmtest: test jailed and masqueraded peers end-to-end
The tun-layer per-peer data plane (jailed packet filter selection and
masquerade NAT rewrites) had no end-to-end coverage: nothing asserted
that a peer the control server marks jailed actually has its flows
dropped, or that masquerade addresses assigned by control actually
carry rewritten traffic in both directions.

Add a two-gokrazy-node natlab test driving both knobs from the test
control server and probing with HTTP requests between the nodes'
webservers after each netmap transition, asserting each response
carries the serving node's greeting so masqueraded flows provably
reach the intended node. TSMP pings are deliberately not used as
probes: tstun answers those before running the packet filter, so
they succeed even for jailed peers.

Updates #12542

Change-Id: Ia978e8d368f08a5a1117280f12bd50310969d0ec
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-14 11:39:56 -07:00
Brad Fitzpatrick
0c4dcbdfeb ipn/ipnlocal: replace magicDNSAddrs bool params with a flags type
Two adjacent bool arguments at call sites are easy to transpose and
hard to read. Use an unexported bitmask type instead, per review
feedback on PR #20414.

Updates #cleanup

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I0417270c9c3f4b2522911c39aad375cbaf025a82
2026-07-14 10:59:00 -07:00
Brad Fitzpatrick
3d07b5d6e1 wgengine,cmd/tailscaled,feature/bird: move BIRD integration to ./feature
The BIRD (BGP) integration previously lived half in cmd/tailscaled
(which created a chirp client via a build-tag-gated file on some
platforms) and half in wgengine (which carried the client in its
Config and toggled the "tailscale" protocol as the node gained or
lost primary subnet router duty).

Move it all to a new feature/bird package, installed on the engine
via the new wgengine.HookNewBird hook, like other feature/* packages.
wgengine.Config.BIRDClient (and the wgengine.BIRDClient interface)
are replaced by a BIRDSocket path from which the engine constructs
the feature's Bird handle at startup. The subnet router overlap
detection and protocol toggling move into feature/bird, preserving
the previous ordering: state is recomputed before Reconfig's
ErrNoChanges early return and applied after the router is configured.

tailscaled keeps BIRD support by default on the platforms that
previously had it (linux, darwin, freebsd, openbsd) via
feature/condregister.

Also, add an integration test, as this feature lacked much test
coverage previously.

Updates #12614

Change-Id: I7866a50779e454c87933b358735f7dcd9e2b126f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-14 10:48:09 -07:00
Mike Jensen
9bd62683dd
go.mod: revert update vulnerable dependencies (#20435) (#20456)
This reverts commit 468a7f4973 on request to @ChaosInTheCRD

Although passing all our CI checks, @ChaosInTheCRD would like to plan manual testing as part of incorporating these updates.

Updates #cleanup

Change-Id: I3f007b571b884c9538a97ac5d3ded782bcba2347

Signed-off-by: Mike Jensen <mikej@tailscale.com>
2026-07-14 10:26:32 -06:00
Michael Ben-Ami
c9b5a918ce feature/conn25: add client metrics for dns response rewrite errors and remove
noisy logs

Remove most logs in mapDNSResponse() that could potentially be spammed by
a misbehaving or abusive DNS client or resolver.

Keep logs, and complement with metrics, for failed rewrites, as they
likely point to an internal error, e.g. ip pools exhausted. Metrics
allow for potential alerting in the future.

Updates tailscale/corp#40125
Updates tailscale/corp#40126

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-07-14 15:31:33 +00:00
Mike Jensen
468a7f4973
go.mod: update vulnerable dependencies (#20435)
This change updates vulnerable dependencies with a direct fix path. Updated:
  * github.com/prometheus/prometheus@v0.311.3 - Direct dependency addressing https://pkg.go.dev/vuln/GO-2026-5710 and https://pkg.go.dev/vuln/GO-2026-5662
  * github.com/go-openapi/swag@v0.27.0 - Needed to fix mutual dependency on github.com/go-openapi/testify after prometheus update
  * github.com/go-git/go-git/v5@v5.19.1 - Addresses https://pkg.go.dev/vuln/GO-2026-5496
  * helm.sh/helm/v3@v3.21.1 - Root update to address most containerd CVEs
  * github.com/containerd/containerd@v1.7.33 - Addresses remaining container CVEs, in total: https://pkg.go.dev/vuln/GO-2026-5758 https://pkg.go.dev/vuln/GO-2026-5475 https://pkg.go.dev/vuln/GO-2026-5378
  * sigs.k8s.io/controller-runtime updated to v0.23.3 - This is needed to accommodate the k8s.io/api v0.35.3 update (test change needed for update)

Vulnerabilities were discovered from govulncheck, which includes reachability in the analysis.

Updates #cleanup

Change-Id: I8345745d22a7e6ee106b58c410889e0aef748be4

Signed-off-by: Mike Jensen <mikej@tailscale.com>
2026-07-14 08:29:50 -06:00