updates tailscale/corp#33007
When a selected exit node can't carry internet traffic, blackhole default
routes are installed and all internet traffic is dropped. That is the
correct behavior -- better to drop than leak to the local network, as
documented on ipn.Prefs.ExitNodeID -- but it was entirely silent: the
stable-ID lookup in nodeBackend.updateRouteManagerPrefs misses without a
log line, and Status leaves ExitNodeStatus nil. The user sees a healthy
Tailscale with no internet.
Add an exit-node-unavailable health warning covering every way the
selection can fail to carry traffic, reported via health.ArgExitNodeReason.
The warning names the exit node, caching its display name while it is
still a peer so the name survives its departure, and falls back to the
stable ID or IP. When the selection is mandated by the ExitNodeID or
ExitNodeIP policy settings, the message tells the user to contact their
network administrator instead of suggesting they pick another exit node.
Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
openresolv exits 2 when no config snippets are registered; treating that
as a failure aborted the entire DNS reconfiguration. Filtering out our own
snippet could also leave "resolvconf -l" with no arguments, which lists
every snippet including ours and would make quad-100 its own upstream.
Fixes#20825
Signed-off-by: Brendan Creane <bcreane@gmail.com>
If web proxy auto discovery is disabled system wide, net/tshttpproxy now respects this configuration and avoids connection to the proxy system service.
Updates #16813
Updates tailscale/corp#29168Fixestailscale/corp#38489
Signed-off-by: James Tucker <james@tailscale.com>
The messages needs to be received so that a node not participating in
caching can talk to a node that is participating in caching but is
unable to share its key over control.
Fixes#21008
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Promote the toolchain from Go 1.26.6 to Go 1.27.0, matching what
go.toolchain.next.rev has been testing. Besides the toolchain files
themselves (updated by pull-toolchain.sh), this bumps the go.mod go
directive, the Dockerfile golang base image, and the README, and
regenerates the depaware.txt files and the gzip assets in
tempfork/spf13/cobra and util/eventbus, whose bytes change with
Go 1.27's rewritten compress/flate.
Also bump golangci-lint to v2.13.1, the first release line built
with Go 1.27; the prebuilt v2.10.1 binary refuses to target a Go
version newer than the one it was built with.
Also bump golang.org/x/net to v0.58.0 (plus the sibling x/ module
upgrades it requires) to pick up upstream commit 8d10596d2624
(http2: avoid deadlocks in wrapped ClientConn state callback),
which we hit during Go 1.27 rc testing.
Also add docs/go-bump-checklist.md for next time.
Updates #20220
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ia3e4c9effafbc91227eed39efb52f1fba1b8d89c
Extend e2e test setup to work with a remote registry and real cluster.
Previously only kind was supported.
Detect cluster node architecture and build images only for the given
architecture to reduce build time. Build for all platforms as the fallback
option. This assumes only one node architecture per cluster, which is
reasonable for our test cases.
By default, --build loads the built images into a kind cluster. Use with
--registry to instead specify and push to a remote registry.
Fixestailscale/corp#46577
Signed-off-by: Becky Pauley <becky@tailscale.com>
So that when control notices a node isn't responding and sends that info
down in a netmap we will stop picking that node as a connector. This
doesn't provide great failover (it takes several minutes) but it's
better than not failing over at all.
Updates tailscale/corp#47250
Signed-off-by: Fran Bull <fran@tailscale.com>
The functions used to be used in ipn/ipnlocal but we changed that, so
the functions can be moved into feature/conn25 now.
Updates tailscale/corp#47250
Signed-off-by: Fran Bull <fran@tailscale.com>
Server.close() dereferenced s.sys.Bus unconditionally. s.sys is only
assigned partway through doInit, so it is still nil when Start fails
before that point, and Close then panics with a nil pointer dereference.
Close is reachable in that state: Start is idempotent via initOnce and
returns the stored initErr, so callers that `defer Close()` before
checking the error land in close() with s.sys == nil. Every other
resource released nearby (s.dialer, s.localAPIListener,
s.loopbackListener) is already nil-guarded; this line was the outlier.
This is reachable in practice from libtailscale/TailscaleKit, where a
failed tsnet start on darwin is followed by a close and surfaces as
EXC_BAD_ACCESS rather than the underlying start error.
Use GetOK instead of Get as well, so an unset Bus returns ok=false
rather than panicking via SubSystem.Get.
Adds TestCloseBeforeStart, which panics without this change.
Updates #20988
Signed-off-by: Prakash Rudraraju <1471544+prakashrj@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tsnet nodes have long been able to use an exit node by setting the
ExitNodeID pref through LocalClient, but that was neither documented
nor tested.
Add a package doc section (and regenerate the README) showing how to
select an exit node via EditPrefs, and add TestDialThroughExitNode
verifying that a Dial of a non-tailnet IP is routed over WireGuard to
the exit node rather than dialed from the host network.
Fixes#20986
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ia1715a628245c52163083803889fd1f475abfab7
To decide whether we can bump GOAMD64 for our production binaries, we
first need to know what x86-64 microarchitecture levels the fleet's
CPUs actually support. Add two gauges to the default metric set on
linux/amd64: goamd64_capable, the maximum GOAMD64 level the host CPU
supports per /proc/cpuinfo, and goamd64_compiled, the level the
running binary was built with per its build info. Both are computed
once at init and report 0 on error.
The per-level feature lists match the checks in Go's
runtime/asm_amd64.s.
Updates tailscale/corp#47099
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I3b4e5431ae1501f0b7d584c12d9af805e9ab6125
Instead of accessing the key and string directly, put it behind a method
to make it easier to use that as a proxy when we add multiple keys
(control and TSMP origin).
Instead of only updating a single origin for disco keys, teach peermap
how to work with multiple disco key sources so a key can originate from
either control or TSMP.
This sets the work up for switching dynamically between disco keys
later. As of this commit, the keys still arrive for the most part via
the controlClient, but this commit sets us up for:
- Switching dynamically between received keys.
- Route TSMP keys directly into magicsock, not via controlClient.
- Potentially revert controlClient to the state before any TSMP
changes, making it single threaded.
- Use switching keys as trigger for optimistic WG handshakes, instead
of tearing down the full connection and setting it up again.
Updates #20494
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Invalidate the trust window so we re-run path discovery
instead of coasting on the dead path until trust lapses on its own.
Partially reverts 85bb5f8 removal of resetLocked within updateFromNode.
Fixes#20268
Change-Id: Ib18b31e4329513294b682e698d1a9f426a6a6964
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
Clients can advertise an opaque app name in their ClientInfo but the
server previously did nothing with it.
Constrain app names to at most 32 bytes of printable ASCII, enforced
both in derp.NewClient and by the server when it parses the ClientInfo.
Extend the peerPresent frame, following its existing pattern of
appending optional fields, with a length-prefixed app name after the
flags byte, so trusted mesh watchers (other DERP nodes and stats
tools) can attribute connections by app. Old clients ignore the extra
bytes; old servers send frames without them.
Also add a derper --disallow-app-names flag taking a comma-separated
list of app names whose connections are refused, except for trusted
mesh peers.
Updates tailscale/corp#24454
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I6e721258675145833aafa1355fabf7fc05a5a204
Test that the client parses peerPresent frames from servers of various
eras: old servers that send fewer fields than the client knows about,
and newer servers that send trailing fields the client doesn't know
about, which it must ignore. This matters during rollouts of new DERP
servers, when a region's meshed nodes and watchers run a mix of
versions.
This is in advance of a following commit that extends the frame with a
new trailing field.
Updates tailscale/corp#24454
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I0d46126a6003a9eb07a4da46dc291872149b1a07
Clock.Advance previously fired each Ticker once per period between the
current time and the advance target, even though all but the first few
sends were dropped on the Ticker's full channel. A test that advances
the clock 180 days with a 5 second ticker registered did 3.1 million
fires, each taking a mutex, attempting a channel send, and fixing the
event heap. Under the race detector, where synchronization operations
are 20-30x more expensive, this dominated test runtime.
Now, when a tick is dropped because the channel is full, skip the
remaining missed ticks in one step, staying aligned to the original
schedule and still firing the final tick at or before the advance
target. This matches time.Ticker, which drops ticks it cannot deliver,
and is observably identical behavior. Ticks that fit in the channel are
still all delivered.
In the internal corp repo, this takes controlclient.TestExpiry under
race from 6.0s to 2.2s (its no-race time is 0.5s), and the new
benchmark improves from 449µs to 90ns per 24h advance:
BenchmarkTickerLargeAdvance-16 100 449313 ns/op (before)
BenchmarkTickerLargeAdvance-16 13311910 90.22 ns/op (after)
Updates tailscale/corp#47035
Change-Id: I4826df212415eb2ea7ceb5bc46848e816f8ccde1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(bumping in oss mostly to get it into corp, and we require them to be in sync
for now. But we do use this in derpserver too.)
Updates tailscale/corp#46884
Change-Id: If01e3e91787e9ec4331eb9c997292239c6b379f1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
There's a deadlock in x/net/http2 with Go 1.27 that's
fixed with v0.58. This is prep for switching to Go 1.27.
Updates #20220
Change-Id: I8e81eafc0c2f78e1760b36e9d39ac4f6046565af
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The set-config loop uses uint16 endpoints. When Last is 65535, the increment wraps to zero and file or Unix targets continue indefinitely.
Break after applying Last so every closed range terminates without changing ordinary range behavior.
Fixes#20873
Signed-off-by: Bonobo <github@in9.at>
Pulls in https://github.com/tailscale/wireguard-go/pull/85 which
fixes an unbounded memory leak in mkIPInCIDRsTestFunc. It used a
package-level placeholder Peer, and AllowedIPs.Insert threads every
trie node onto that peer's trieEntries list, so each call leaked
its whole trie. SetAllowedIPs calls it on every netmap update, so
peer churn accumulated trie nodes until the client OOMed.
Fixestailscale/corp#47010
Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
When a map poll's context is canceled mid-read, the error that
surfaces from the response body read depends on the transport and Go
version: Go 1.26's http2 returned the context error, while Go 1.27's
returns the underlying "use of closed network connection". Normalize
the read-error paths in sendMapRequest to report ctx.Err() when the
context was canceled so callers see a stable, meaningful error
regardless of Go version.
Updates #20220
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I5898d3f452a2e61cfdf3ee015386365943c08e70
The service-pg-reconciler reconciles Services annotated for an ingress
ProxyGroup, but its ProxyGroup watch reused ingressProxyGroupFilter,
which is ingressesFromIngressProxyGroup. That handler lists Ingresses
and returns Ingress keys, so when a ProxyGroup became Available the
requests it produced never matched a Service and the reconciler's Get
just came back NotFound.
This fixes this by adding servicesFromIngressProxyGroup, which lists the
Services indexed for the ProxyGroup and returns their keys, matching what
the egress path already does with egressSvcsFromEgressProxyGroup.
Fixes#20944
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Only allow TransitIPs to be allocated when the client has permission to
access the requested app.
Updates tailscale/corp#40076
Change-Id: Ib4b37afa25ffbdd220ba2c0fd9cfec8d5df5311f
Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
Also reports when shard results are missing, unreadable, duplicated, or
empty, so the pass count and percentage can't overstate what actually ran.
Updates #20931
Signed-off-by: Yaruk Asghar <yaruk@tailscale.com>
Guard optional annotations maps in generated CEL expressions before
looking up expose or proxy-group annotations. This lets ordinary
annotationless Services and Ingresses pass admission while preserving
deny-all and allowlist behavior for present proxy-group annotations.
RELNOTE: Kubernetes ProxyGroupPolicy now permits annotationless resources.
Fixes#20906
Change-Id: I8b9475d34c003ca6d233246c021ac656e0530fb1
Signed-off-by: joshrzemien <joshua.k.rzemien@gmail.com>
Historically, when DERP regions were switched away from strings to
numeric identifiers in PR #14641, tailcfg.Node.HomeDERP was declared
as an int instead of its own type.
This PR declares a new tailcfg.DERPRegionID type, represented by an
int64, and converts the following fields to use this type:
- netcheck.Report.PreferredDERP
- netcheck.Report.RegionLatency
- netcheck.Report.RegionV4Latency
- netcheck.Report.RegionV6Latency
- tailcfg.DERPHomeParams.RegionScore
- tailcfg.DERPMap.Regions
- tailcfg.DERPNode.RegionID
- tailcfg.DERPRegion.RegionID
- tailcfg.NetInfo.PreferredDERP
- tailcfg.Node.HomeDERP
- tailcfg.PeerChange.DERPRegion
- tailcfg.PingResponse.DERPRegionID
Note that the original field was an int, while the new field is backed
by an int64. This change makes DERPRegionID the same size on both
32-bit and 64-bit architectures.
Fixes: #20165
Change-Id: Ic6f795a6d791dd16f756f246d5a02085443e212f
Signed-off-by: Simon Law <sfllaw@tailscale.com>
After a netmap delta is applied, we scan the mutations for affected peers and
update the cache (if enabled) for those peers. For removals in particular, we
were relying on the node backend to resolve node IDs (provided by the delta
mutation) to stable IDs.
Prior to 65fd320a this happened to work because the node backend would hold on
to all the peers mentioned by the previous full netmap, even after applying
deltas. But that was essentially accidental, and once we fixed it not to do
that, these lookups no longer worked. We need the stable ID, since that is how
the cache is keyed, and now that they're no longer pinned, we were not properly
evicting removed peers from the cache.
To fix this, capture removed peer stable IDs while applying mutations to the
node backend, instead of trying to look them up afterward.
Updates #20796
Change-Id: I14ded78eaf9657645f0869a52460fd3cd86edba6
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
The packet filter only sees a via address's outer ULA, so netstack unconditionally dialed whatever IPv4 was embedded in it.
This change refuses TCP, UDP, and ping relays to host-scoped destinations after UnmapVia.
Credit to the Anthropic infrastructure security team for finding and reporting.
Fixes https://github.com/tailscale/corp/issues/46646
Change-Id: I93d8e27a2eddbce7eb3f8c5fa4677f8de3a8ed9e
Signed-off-by: Mike Jensen <mikej@tailscale.com>
The websocket upgrade path bound the returned conn's reads and writes
to the context passed to AcceptHTTP via wsconn.NetConn. That context is
typically an http.Request context, which net/http cancels once the
calling handler returns, so a caller that served the accepted conn
beyond its handler's lifetime had the conn killed out from under it.
The hijack-based HTTP/1 upgrade path has no such binding.
Make the two paths consistent: ctx now only bounds the handshake (its
deadline, if any, is applied to the conn by controlbase.Server) and the
returned conn's lifetime is the caller's responsibility. Document that
contract on AcceptHTTP and add a regression test that uses a
websocket-accepted conn after the accepting handler has returned.
Updates tailscale/corp#46806
Updates tailscale/corp#29053
Change-Id: I4fc4ea5cddc2c6174fdf21f8d832f2e0984a7533
Co-authored-by: Adriano Sela Aviles <adriano@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Bump five dependencies to resolve the following advisories;
GO-2026-6238, GO-2026-5764, GO-2026-5597, GO-2026-5490, GO-2026-5496,
GO-2026-5105.
Updates tailscale/corp#9497
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
Let clients pin favorite devices, exit nodes, and services so GUIs can
surface & change them. Pins are stored per login profile in the new
favorites feature module, keyed per category; devices and exit nodes by
StableNodeID, services by ServiceName.
The item types live in a leaf package feature/favorites/pintype,
keeping them out of the core ipn hierarchy. Each category has
its own type (pintype.Device, pintype.ExitNode, pintype.Service).
Exposed over LocalAPI at GET/POST /localapi/v0/pins, where POST replaces
only the categories named in the request so a client can update one
category without clobbering the others. Pins are local to the device and
are not synced across a user's devices.
updates tailscale/corp#44836
Signed-off-by: Will Hannah <willh@tailscale.com>
Co-authored-by: Will Hannah <wph@Wills-Virtual-Machine.local>
Our tests are inconsistent in how they set environment variables and cleanup.
Missing cleanup logic can leak environment variable state across tests and
change the behaviour of subsequent tests.
This recently caused flakes in feature/acme, where `TestGetCertPEMWithValidity`
leaked `TS_CERT_SHARE_MODE` and `TestAsyncRenewalDedup` to fail inconsistently.
To fix the immediate flake and prevent future leaks, introduce a `SetenvForTest`
helper that handles setting and cleaning up environment variables in tests.
Fixes#20902
Change-Id: I9f8ef45ec875d66034cfd0054311bd69b67e2243
Signed-off-by: Alex Chan <alexc@tailscale.com>
containerboot waits up to 60s for the initial map response before
failing. Slow map responses (>60s seen in production) leaves
containerboot to timeout and fail.
Add a TS_BOOT_TIMEOUT env var to override the default. Falls back to
60s when unset.
Fixes#20912
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Port some tiny testcases that apply to our corp impl of the rdv hasher
to OSS as well. This is in preparation for cutting over to the OSS
impl exclusively.
Updates tailscale/corp#46471
Signed-off-by: Amal Bansode <amal@tailscale.com>
This registers per-user RSOP policy stores when a user logs into a Windows session and ensures that per-user poicy settings are delivered to clients via IPN bus.
The store lifecycle is tied to Windows session lifetime via desktopSessionExt's SessionInitCallback, which handles unattended mode (per-user policies stay enforced after gui disconnection) and multi-user (refcounted across sessions for the same user).
Updates tailscale/corp#42259
Signed-off-by: kari-ts <kari@tailscale.com>
The connector struct holds a map of peer+transitIP -> destinationIP that
it uses for routing traffic. The client registers new entries in the map
over the peer API.
Stop the transitIPs map from growing indefinitely by expiring entries
after 1 hour.
Updates tailscale/corp#38261
Signed-off-by: Fran Bull <fran@tailscale.com>
In cases where cigocacher is using a proxy that handles the auth, it's
possible to fetch stats without knowing our own access token. Don't fail
early if the access token isn't passed. If there's no proxy, it will
fail with the error from the gocached server.
Updates tailscale/corp#45427
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
Deploy k8s nameserver during e2e test setup, and point the cluster resolver
(CoreDNS or kube-dns) to it so tests can resolve MagicDNS names inside the
cluster.
Add a test to verify singleton L7 Ingress is reachable from inside the
cluster using its MagicDNS Name.
Update existing egress tests to use a dedicated tailnet target per test (to
avoid conflicts). Egress tests now also verify that an egress target is
reachable from within the cluster using both its Service and MagicDNS name.
To successfully curl using the target's MagicDNS name, publish test CAs as a
ConfigMap to the cluster, and mount these for each curl pod.
Fixestailscale/corp#38027
Signed-off-by: Becky Pauley <becky@tailscale.com>
kubeconfigPath dereferences FileInfo for non-ENOENT stat errors even though os.Stat returns nil FileInfo. Preserve the failing list entry so checkKubeconfigWritable reports the existing access error instead of panicking.
Updates #11604
Signed-off-by: Bonobo <github@in9.at>
Static size checks (iossize) catch binary dirty-page growth but nothing
covered runtime heap cost, which is what actually consumes the iOS
Network Extension's 50 MiB jetsam budget. Bring up a tsnet backend
(with the full condregister feature set, matching shipping clients)
against an in-process testcontrol server and assert live post-GC heap
budgets for (a) backend startup with zero peers and (b) marginal cost
per netmap peer.
The startup test measures 1.3 MiB today and fails loudly on the
conn25 flow-table pre-allocation regression (17 MiB) that jetsam-killed
the iOS extension on large tailnets.
Budgets are deliberately generous (6-12x current measurements) to stay
flake-free while still catching the multi-MiB regressions that matter
for mobile.
A new debugknob enables us to constrain the GSO/GRO batch size to 1 for
these tests so as to avoid the memory allocation associated with those
buffers, which are a known issue with their own work stream.
Updates tailscale/corp#46408
Updates tailscale/corp#18514
Signed-off-by: James Tucker <james@tailscale.com>
Currently we serialise ACME account setup and ACME issuances for domains
which don't yet have a valid certificate, but not for async renewals of
still-valid certificates.
This patch adds a check that we only have one async renewal for a domain
in-flight at a time, and a test that ensures we de-dupe these renewals.
Updates tailscale/corp#46420
Change-Id: Ibbaa537cd28b8adf238b5c9396403e845f95b7e7
Signed-off-by: Alex Chan <alexc@tailscale.com>
Our desktop detection was using "wayland-1" as a search string for
detecting wayland desktops, but many desktops use other indexes for the
session. Additionally we did not detect mir or gamescope.
Add a new detection method that leans on systemd-logind (if available)
and fall back to using the existing method of looking for open unix
sockets, but add search strings and tail the index off of the wayland
session detection.
Fixes#20847
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
This change contains the protocol changes needed to support
describing authorization for conn25 apps. As a temporary transition
measure during development, app configurations can disable authorization
enforcement.
Updates tailscale/corp#40076
Change-Id: I3183c10374aacb0048f6632c384f71f758c20f2f
Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>