Commit Graph
11635 Commits
Author SHA1 Message Date
Alex Valiushko dc46358ecf go.mod: fix duplicate handshake response in wireguard-go (#22221)
Updates tailscale/corp#50867
Updates tailscale/tailscale#21884

Change-Id: If04dcde13681fc27e0e756ed465f165c6a6a6964
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
2026-11-07 11:35:32 -07:00
Mike O'Driscoll 6183501e99 flake.nix: resolve deprecation warning (#22234)
Resolve stdenv.isLinux is deprecated warning

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-11-07 10:17:56 -05:00
Nick Khyl b31eb1c9bc ipn/ipnlocal,ipn/localapi: fix flaky TestResumeIPNBus
The test resumed a live session, so notifications already
on the wire (notably, spurious health change notifications)
could arrive ahead of the initial status.

In this PR, we add LocalBackend.SuspendWatchNotifications,
which suspends a session as if it had fallen behind,
and update the test to suspend the session and drain
pending notifications before resuming it.

Updates tailscale/corp#51067
Updates tailscale/corp#50256
Updates tailscale/corp#50907

Signed-off-by: Nick Khyl <nickk@tailscale.com>
2026-11-06 19:31:20 -08:00
Nick Khyl 46f938791d go.mod: bump github.com/klauspost/compress for govulncheck
Resolves GO-2026-6865
Updates tailscale/corp#9497

Change-Id: I5aa46a03de75e4c51baa7662d1573a08f338467b
Signed-off-by: Nick Khyl <nickk@tailscale.com>
2026-11-06 15:38:10 -08:00
Nick Khyl b706df603a ipn,client/local: let IPN bus watchers be suspended instead of disconnected
Since edcc2c94, tailscaled closes the watch when a subscriber falls
behind. That doesn't work well for GUIs where closing the watch has side
effects. On Windows, the last client disconnecting can reset the backend
state unless unattended or always-on mode is enabled. On Android and
Apple platforms, nothing restarts a watch that ended this way, so the
UI silently stops updating.

Add ipn.NotifySuspendWhenBehind. A watcher that sets it stays connected
when it falls behind, but gets a Notify with Suspended set and nothing
else until the client resumes the session via the new resume-ipn-bus
LocalAPI endpoint or LocalBackend.ResumeWatchNotifications. Resuming
replays the initial Notify and new notifications follow from there.

Updates tailscale/corp#51067
Updates tailscale/corp#50256
Updates tailscale/corp#50907

Signed-off-by: Nick Khyl <nickk@tailscale.com>
2026-11-06 14:33:01 -08:00
Brendan Creane b7c3386649 net/dns,tstest/natlab/vmtest: accept a silent openresolv listing as no snippets (#22211)
openresolv before 3.13 exits 1 from "resolvconf -i" when its interface
directory holds no snippets. FreeBSD ships openresolv 3.9.2 in its base
system, so on FreeBSD hosts with static network configuration that status
aborted every DNS reconfiguration. A listing that prints nothing on stdout
or stderr now yields an empty base config whatever its exit status, which
also covers the exit status 2 that openresolv 3.17 returns.

The natlab DNSOpenresolv mode now provisions FreeBSD guests as well, by
deregistering the snippets dhclient added, and the openresolv VM tests run
on FreeBSD too. Without the net/dns change, the new FreeBSD test fails with
the health error reported in #11907.

Updates #11907
Updates #11129

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-11-06 14:15:57 -08:00
Mazdak Nasab 3dbb215e57 feature/conn25: drop address assignments queued before a client.reset (#22210)
Give the client a generation counter, bump it in reset, and stamp each
assignment with it as it is queued. insertTransitConnMapping rejects
assignments from an older generation; because that check happens under
c.mu it is exclusive with reset, which is what actually closes the
window. handleAddressAssignment also checks before sending, so an
assignment invalidated while it was still queued doesn't reach a
connector at all.

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

Change-Id: Ib524324b6e8a196fdcb5b4cd307854f61c7a0f04

Signed-off-by: Mazdak Nasab <mazdak.nasab@gmail.com>
2026-11-06 12:04:36 -07:00
Brendan Creane e906e6934d ipn/ipnlocal: resolve exit node when it arrives by netmap delta (#22184)
Since aa5da2e5f, a peer that control adds to a running client arrives as a
netmap delta rather than a full netmap. The delta path never re-resolved
the exit node prefs. An exit node selected by IP before it was in the
netmap stayed unresolved, and an auto exit node stayed on the blackhole
route, until tailscaled restarted or a full netmap arrived.

Re-resolve the exit node in UpdateNetmapDelta when the delta can change
the result. Any upserted peer qualifies while an ExitNodeIP is set. An
exit node being upserted or coming online qualifies while an auto exit
node is unresolved. The selected auto exit node going offline, losing its
exit routes, or being removed qualifies once one is selected. The last
case replaces the separate offline check, so the exit node is resolved
once per delta. The auto exit node check walks every peer, so it only
runs in those cases.

On the delta path, the resolved prefs ride in the same Notify as the peer
changes, sent while b.mu is held. resolveExitNodeLocked no longer sends a
Notify; its callers do. The full-netmap path still sends the prefs in a
separate Notify after the netmap.

Fixes #22012
Updates #22206

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-11-05 16:28:01 -08:00
Mazdak Nasab 8aa18b518f feature/conn25: drain the address assignment queue in client.reset (#22186)
client.reset clears the IP pools, assignments and byConnKey, but leaves
addrsCh alone. The channel is buffered, so assignments queued before the
reset are still delivered by sendLoop afterwards: each one POSTs to a
connector from the profile we just left, registering a transit IP this
client no longer knows about, and then fails in addTransitIPForConnector
because the assignment is gone from c.assignments.

Drain addrsCh at the end of reset. The drain is non-blocking, so holding
c.mu can't deadlock against reserveAddresses, which enqueues under the
same lock, or against sendLoop, which receives without it.

Fixes https://github.com/tailscale/tailscale/issues/22185

Change-Id: I579131bc54e82d2c797b4741ccaf6cbe7abfdc4a

Signed-off-by: Mazdak Nasab <mazdak.nasab@gmail.com>
2026-11-05 12:16:06 -07:00
alexcastgonz 01fa3caf30 ipn/ipnlocal: fix redirect for nested directories in serve (#22118)
ipn/ipnlocal: resolve serve file redirects against the request path

http.FileServer sends a Location relative to the request URL, such as
"nested/" or "../file". fixLocationHeaderResponseWriter prefixed that
value with the mount point, which dropped every directory between the
mount point and the target. A request for /doc/subdir/nested was
redirected to /doc/nested/.

Resolve the relative Location against the original request path
instead. Collapse leading slashes in the result so a request path like
//host/dir cannot produce a protocol-relative Location.

Fixes #22049

Signed-off-by: Alex Castillo González <alex.castillog33@gmail.com>
2026-11-05 11:14:35 -08:00
chaosinthecrd 1d9de5f81a k8s-operator/reconciler: test ProxyClass metrics for PeerRelay
This adds a test that walks a PeerRelay through the ProxyClass spec.metrics settings and checks the metrics Service, ServiceMonitor and TS_ENABLE_METRICS at each step, and a case for a PeerRelay waiting on a ProxyClass that is not ready. It also checks that ApplyProxyClass leaves one entry per env name when the ProxyClass sets an env var the operator also sets.

Updates #22034

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-05 17:22:05 +00:00
chaosinthecrd 9eea2c890c cmd/k8s-operator,k8s-operator: support ProxyClass metrics for PeerRelay
The PeerRelay reconciler ignored a ProxyClass's spec.metrics, so enabling metrics created no metrics Service or ServiceMonitor and the relay Pods never served /metrics. This fixes this by setting TS_ENABLE_METRICS in ApplyProxyClass and calling tailscaled.EnsureMetricsResources for PeerRelay. PeerRelay now also waits for its ProxyClass to be ready before creating anything, as the other proxies do, so the existing ProxyClass test fixture is marked ready.

ApplyProxyClass now replaces an env var that the ProxyClass also sets instead of appending a second one. The PeerRelay StatefulSet is applied with server-side apply, which rejects duplicate env names, so a ProxyClass that sets an env var the operator also sets, such as TS_ENABLE_METRICS, would stop the StatefulSet from being applied.

Updates #22034

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-05 17:22:05 +00:00
chaosinthecrd 31652c9978 cmd/k8s-operator,k8s-operator/reconciler: move metrics handling to tailscaled
The metrics Service and ServiceMonitor handling lived in cmd/k8s-operator,
and the ProxyClass reconciler kept its own copy of the ServiceMonitor CRD
check, so reconcilers in k8s-operator/reconciler could not share it. This
moves it into the tailscaled package as EnsureMetricsResources and
EnsureMetricsResourcesDeleted, which take the proxy type and parent, and
switches cmd/k8s-operator and the ProxyClass reconciler over to it. No
behaviour change.

Updates #22034

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-05 17:22:05 +00:00
Brendan Creane ae6c916f0f ipn/ipnlocal: normalize the serve hostname before the config lookup (#22141)
Serve looked up its config with the name exactly as the client sent
it. Config keys are lowercase with no trailing dot, so a plain HTTP
request for "svelte.example.ts.net." or "Svelte.Example.TS.net" got a
404. HTTPS had the same gap for case. TLS rejects a trailing dot in
SNI but does not lowercase it, so a client that sent mixed-case SNI
failed the handshake with "no webserver configured". Browsers and curl
lowercase the hostname before connecting, which hid that path.

Lowercase the name and trim the trailing dot in both branches of
getServeHandler, and lowercase the SNI in the cert callback.

Fixes #21538
Fixes #21986

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-11-05 08:32:05 -08:00
Mike O'Driscoll 4dfd5352b9 derp/derpserver: reduce setWriteDeadline calls (#22180)
Each send operation (sendPacket, sendPeerGone, sendPong,
sendKeepAlive, etc) calls SetWriteDeadline on the
underlying connection. This re-arms the netpoll timer
on each packet frame write to the intermidiate lazy
buffer for a socket write that happens later. Not the
actual socket write done by runWriter.

Move the SetWriteDeadline down to the buffer that is pulled
by runWriter for each actual socket write.

Updates tailscale/corp#50628

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-11-05 10:45:38 -05:00
chaosinthecrd 68ec83c60b cmd/k8s-operator: test requeue on optimistic lock errors
Adds tests that a conflict while provisioning a Service, a Service on a
ProxyGroup, a ProxyGroup or a Connector proxy results in a timed
requeue, and that the Connector and ProxyGroup status is set to
creating rather than failed. The test for a Service on a ProxyGroup
fails the update to the shared ingress config ConfigMap, as no watch
maps that ConfigMap back to the Service, so without the requeue the
conflict was never retried.

Updates #22074
Updates #18922

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-05 15:27:01 +00:00
chaosinthecrd 9a44f4f69c cmd/k8s-operator,k8s-operator/reconciler: requeue on optimistic lock errors
Reconcilers logged "optimistic lock error, retrying" with the full
conflict error at Info, and Connector, Recorder and ProxyGroup also put
it in their status. That reads like a failure, but a conflict between a
cached read and an update is expected and is retried. The reconcilers
also returned an empty result, so the retry only happened if a watch
event arrived. This fixes this by logging the conflict at Debug as an
update conflict, using a neutral status message and requeueing after a
short delay.

Updates #22074
Updates #18922

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-05 15:27:01 +00:00
David Bond 2993b5e6de cmd/k8s-operator: move Recorder reconciler into its own package (#20891)
Move the Recorder reconciler and its object specs into
k8s-operator/reconciler/recorder, following the earlier ProxyClass,
DNSConfig and nameserver moves, and switch it to the shared finalizer,
resource tracker and CreateOrUpdate helpers.

Hoist two duplicated helpers into tailscaled.PrefsFromStateSecret and
reconciler.IsOptimisticLockError, and add
k8s-operator/reconciler/reconcilertest for the test helpers each package
had copied, including one fake Tailscale API client in place of four.

No behaviour change; the Recorder's watches and labels are untouched.

Fixes tailscale/corp#37086

Signed-off-by: David Bond <davidsbond93@gmail.com>
2026-11-05 08:35:13 +00:00
Luo Chen dc69869769 net/netcheck: never prefer a STUN-only region as the home DERP (#21976)
The PreferredDERP selection loop only considers RegionLatency, which
STUN probes populate for STUN-only regions too. When such a region is
the only one with a latency sample (e.g. self-hosted DERP behind a
TCP-only tunnel while a public STUN server is reachable), it gets
selected as the preferred DERP and all relay traffic blackholes, since
the region has no DERP server to relay through.

Filter STUN-only regions in the selection loop by reusing the existing
regionHasDERPNode predicate (renamed to CanServeDERP), which was
already applied to the HTTPS/ICMP fallback probing but not here.

Regions missing from the map, or present without any nodes, keep the
old behavior, as some tests use latency-only DERP maps.

Fixes #21975

Signed-off-by: Luo Chen <luochen1990@gmail.com>
2026-11-04 21:22:51 -08:00
David Anderson a967b7d811 tailcfg: add a nodecap to disable hybrid handshakes
Updates tailscale/corp#50068

Signed-off-by: David Anderson <danderson@tailscale.com>
Change-Id: I07fc56c2eff6ab54ac9eec8ce33ea1d16a6a6964
2026-11-04 16:51:29 -07:00
Mazdak Nasab 274a3f2023 feature/conn25: roll back the address assignment when the send queue is full (#22165)
reserveAddresses inserts the assignment into c.assignments before
enqueueing it, and leaves it there when the enqueue fails. The connector
never learns about such a mapping, and nothing tell it later.
Add addrAssignments.remove and use it to undo the insert and return both
addresses to their pools when enqueueAddressAssignment fails. The caller
still gets a SERVFAIL, but the next DNS query starts from a clean slate.

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

Change-Id: I5a387bdcf5a5e87e2dca44867e88ed6149678729

Signed-off-by: Mazdak Nasab <mazdak.nasab@gmail.com>
2026-11-04 14:14:10 -07:00
David Bond 5455bf6a9f cmd/k8s-operator: require egress Pods in every IP family's EndpointSlice (#22157)
This commit changes the egress ProxyGroup readiness gate to mark a Pod
ready only when, for each egress Service, the Pod is an endpoint in the
EndpointSlice of every IP family that the proxy reports in its egress
status. Before, the gate accepted a Pod that was in either family's
EndpointSlice. On a dual-stack cluster, the StatefulSet rollout could
then move to the next Pod before the other family routed to the new Pod.

The gate reads the families from the proxy's state Secret, because a
proxy older than v1.102 does not report an IPv6 address. Such a proxy
never joins the IPv6 EndpointSlice, so its Pod must not wait for it.

Fixes #21077

Signed-off-by: David Bond <davidsbond93@gmail.com>
2026-11-04 19:00:53 +00:00
David Bond 44066ac026 cmd/k8s-operator: keep ProxyGroup state Secrets until cleanup finishes (#22155)
This commit removes the owner reference from ProxyGroup state Secrets,
also from Secrets that an older operator created. On a Foreground
delete, the garbage collector deleted these Secrets before the
finalizers ran. The finalizers read them to find devices, so cleanup
failed and devices stayed in the tailnet.

The ProxyGroup cleanup now deletes the Secrets after the devices, and
the reconciler watches them by label. The kube-apiserver cleanup finds
its cert resources by label, so it does not need the state Secrets. A
new e2e test covers Foreground deletes.

Fixes #22072

Signed-off-by: David Bond <davidsbond93@gmail.com>
2026-11-04 18:56:58 +00:00
Mike O'Driscoll 4afa0bb36f derp/derpserver: increase pool size to batch writer flushes (#22176)
bufioWriterPool was sized at 2KiB, this limited the number
of packets per TLS record during a push, limiting throughput
and increasing syscalls

Increase the buffer to 16KiB to pass extra DERP frames per
flush and TLS record write.

Update the websocket path to also use the pooled buffer

Updates tailscale/corp#50628

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-11-04 12:44:10 -05:00
Santhi Prakash f851ca27e4 cmd/containerboot: keep egress proxy watch alive after failed FQDN resolve (#22108)
In singleton egress mode (TS_TAILNET_TARGET_FQDN), a failed resolution
of the target FQDN logged the error but then broke out of the main
runLoop instead of continuing to the next netmap update. From then on
containerboot blocked in wg.Wait() while the pod still looked healthy,
and the egress proxy kept forwarding to stale addresses until restart.

A missing target is typically transient (peer not yet in the netmap,
ACL change in flight), so log and continue to let the next netmap
update retry the resolution and reinstall the forwarding rules.

Fixes #22102

Signed-off-by: Santhi Prakash <b.santhiprakash@gmail.com>
2026-11-04 10:21:20 +00:00
Naman Sood 4290c03d37 appc: add test for AppDNSRoutes ignoring subnet mode apps
This is a follow-up to #21777, which added this functionality but didn't
test it yet.

Updates tailscale/corp#39984.

Signed-off-by: Naman Sood <mail@nsood.in>
2026-11-03 19:19:18 -05:00
Brendan Creane 3f78bcb0a7 cmd/tailscale/cli: do not suggest an operator for path and socket targets
When tailscaled denies a serve config, the CLI advises configuring an
operator to avoid root. That advice is wrong for path and Unix socket
targets, which require root regardless. Replace the access-denied
error for those targets with one that says to use sudo.

Updates #21204

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-11-03 14:39:07 -08:00
Brendan Creane e7f595b491 ipn/ipnserver, ipn/localapi: require root to serve a path or Unix socket
The operator user could serve a path or Unix socket if tailscaled
found a sudo rule for them, but the check used the GECOS display name
and so failed for most users with a 401. The check cannot be repaired
safely: listing another user's sudo rules never prompts, so it proves
a rule exists, not that the user has authenticated. An operator who
had not entered their sudo password could serve any root-readable file
or root-only socket. Require root instead and say so in the error.

Fixes #21204
Fixes #21985
Fixes #21988

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-11-03 14:39:07 -08:00
François Marier ca4e1972fa tstest/natlab/vnet: move PCP/NAT-PMP code to portmapper.go (#22145)
portmapper.go was introduced for UPnP in commit
8ed43f1f2a.

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

Updates #21713

Change-Id: I33b61ccbe8c1d8d99d56aa357ae24777be3f5739
Signed-off-by: Francois Marier <francois@tailscale.com>
2026-11-03 14:51:11 -07:00
Philip Taron 6fa10dae73 ssh/tailssh: accept an empty --groups= in the incubator
Fixes #22158

Change-Id: I238244c34c96c444c258b9e006c0084f4aa8db27
Signed-off-by: Philip Taron <philip.taron@gmail.com>
2026-11-03 12:36:20 -08:00
Naman Sood 07915ae481 types/appctype: remove Conn25Attr type
This type was previously logically distinct from AppConnectorAttr
despite containing the same fields. It is now logically also the same,
so doesn't need to exist separately.

Fixes tailscale/corp#50897.

Signed-off-by: Naman Sood <mail@nsood.in>
2026-11-03 14:54:57 -05:00
Brad FitzpatrickandDavid Anderson 911d8f84a0 wgengine/wgcfg: wire up new wireguard-go hybrid handshake field
Wire up wireguard-go's new NewPeerConfig.Hybrid field. (no current users)

Updates tailscale/corp#50068

Co-authored-by: David Anderson <danderson@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I80d7af011b43273e6c949214c6d862f9247a82e1
2026-11-03 10:19:55 -08:00
Naman Sood d8eae46481 appc,feature/conn25,tailcfg: read conn25 config from non-experimental nodeattr
Updates tailscale/corp#39984.

Signed-off-by: Naman Sood <mail@nsood.in>
2026-11-03 12:30:26 -05:00
Brad Fitzpatrick 697bdea5aa cmd/pgproxy: delete, leave README stub
Fixes tailscale/corp#50889

Change-Id: I9ea45c14ea305fb3e5cd5adee5f64701db054f8e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-11-03 08:12:30 -08:00
Brendan Creane d45c5ddfbb cmd/tailscale/cli: check the requested port before enabling Funnel (#22120)
The funnel command checked Funnel access with a hardcoded port 443, so
any requested port passed and the CLI reported it as available on the
internet. Check the port the user asked for instead. Turning Funnel
off skips the check so existing configs on other ports can still be
removed.

Fixes #10742
Fixes #21989

Signed-off-by: Brendan Creane <bcreane@gmail.com>
2026-11-03 06:39:53 -08:00
Hamza KhokharandClaude Opus 5.5 f5d378471e cmd/k8s-operator: grant dnsconfigs/finalizers so the nameserver provisions on OpenShift (#21276)
* cmd/k8s-operator: grant dnsconfigs/finalizers so the nameserver provisions on OpenShift

The nameserver ServiceAccount, Deployment, Service and ConfigMap are created
with an ownerReference to the cluster-scoped DNSConfig and
blockOwnerDeletion: true. Clusters running the
OwnerReferencesPermissionEnforcement admission plugin - which OpenShift enables
by default and upstream Kubernetes does not - require the creating identity to
hold update on the owner finalizers subresource before accepting such a child.

The operator ClusterRole grants dnsconfigs and dnsconfigs/status but no rule on
any finalizers subresource, so every nameserver reconcile is rejected:

  serviceaccounts "nameserver" is forbidden: cannot set blockOwnerDeletion if
  an ownerReference refers to a resource you cannot set finalizers on

It retries forever, the DNSConfig never gets a status, and dns-records-reconciler
waits indefinitely. Granting the rule lets the reconcile succeed on the next
attempt; it is inert where the admission plugin is not enabled.

Static manifest regenerated with ./tool/go run ./cmd/k8s-operator/generate staticmanifests

Fixes #21275

Signed-off-by: Hamza Khokhar <hamza.khokhar660@gmail.com>

* cmd/k8s-operator: grant proxygroups, recorders and services finalizers

Extend the OwnerReferencesPermissionEnforcement fix to the other owners whose
children are created with blockOwnerDeletion: true: ProxyGroup and Recorder
(tailscale.com) and Service (core). Without update on their finalizers
subresource, creates of the owned resources are rejected on clusters that
enable the admission plugin, such as OpenShift.

Static manifest regenerated with ./tool/go run ./cmd/k8s-operator/generate staticmanifests

Updates #21275

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Hamza Khokhar <hamza.khokhar660@gmail.com>

---------

Signed-off-by: Hamza Khokhar <hamza.khokhar660@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:19:45 +00:00
BeckyPauley 1db86dac84 cmd/k8s-operator/e2e: add IP family tests for ingress (#22091)
Restructure the L3, L3 HA and L7 ingress e2e tests into IP family subtests
to cover IPv4 and IPv6, plus DualStack for ProxyGroup.

Updates tailscale/corp#38029

Signed-off-by: Becky Pauley <becky@tailscale.com>
2026-11-03 08:41:03 +00:00
Claus Lensbøl f8358c7f3b ipn/ipnlocal: remove wireguard peers on profile switch (#22150)
Remove old unusable peers from the WG config after a profile switch.

Updates #22149

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2026-11-02 18:04:50 -05:00
Aniket bbc09979eb net/portmapper: allow slower routers to answer the UPnP root device fetch (#21877)
The root device fetch timed out after one second, but some router firmware only starts answering after just over a second, so UPnP never worked behind it. Allow three seconds.

Updates #21860

Signed-off-by: Aniket <aniketkomailkro@gmail.com>
2026-11-02 17:35:16 -05:00
Andrew Dunham e19eb275ea ipn/ipnlocal,cmd/tsnet-proxy: strip underscore variants of identity headers (#22143)
Serve deletes client-supplied `Tailscale-User-Login` (and similar) headers
before setting its own. However, `http.Header.Del` only matches the exact
canonical name. A client could send Tailscale_User_Login instead, which
was passed through to the backend.

For Go (and some other) backends, that's not an issue, but backends that
map headers to CGI-style variables (HTTP_TAILSCALE_USER_LOGIN) treat
both names the same.  Thus, for Funnel requests (or tagged nodes), where
serve sets no identity headers, the backend could trust the spoofed
value. The same applies to Tailscale-App-Capabilities.

This fixes the issue by also deleting any header whose name matches
case-insensitively after replacing '_' with '-'.

This is similar to how other projects do this:
- Django drops them: https://www.djangoproject.com/weblog/2015/jan/13/security/
- nginx does not allow them by default: https://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
- Puma had a CVE and now drops them: https://nvd.nist.gov/vuln/detail/cve-2024-45614

Fixes tailscale/corp#50821

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2026-11-02 16:42:36 -05:00
Jordan Whited 11ca165858 go.mod: bump wireguard-go
https://github.com/tailscale/wireguard-go/compare/db3888e688e905f44790b44fa04785f232e0f2cf...b884e294b6d810f3bf1c24c3fabc8398d867604a

Fixes tailscale/corp#50712
Updates tailscale/corp#37878
Updates #22097

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-11-02 13:38:03 -08:00
Mazdak Nasab 35d8212746 feature/conn25: block destinations inside the magic and transit IP pools (#22147)
Similar to b6fc02d for private destinations, a destination inside one of
the configured magic or transit IP pools is never somewhere an app can be
reached: the magic range is installed as a route towards Tailscale on
every configured node, and the transit range belongs to conn25's own
peer-to-connector mappings. Forwarding to one routes back into the
mapping layer instead of out to an app.

Updates https://github.com/tailscale/corp/issues/50118
Updates https://github.com/tailscale/corp/issues/46169

Change-Id: I8116543d4b7a02e1f4c3c6b882ee3ddec1ac4f97

Signed-off-by: Mazdak Nasab <mazdak.nasab@gmail.com>
2026-11-02 14:30:42 -07:00
Fran Bull 0a88fa1ec6 feature/conn25: mint/verify TransitIPRequest tokens
Add functions to allow minting and verifying the tokens.

Updates tailscale/corp#46169

Signed-off-by: Fran Bull <fran@tailscale.com>
2026-11-02 13:23:31 -08:00
Mike O'Driscoll 858b53b289 derp: batch mesh ForwardPacket writes (#22064)
derp.Client.ForwardPacket flushed on every call, so each packet a
derper forwards to a mesh peer cost its own TLS record and write
syscall, serialized behind the client's write mutex with the syscall
inside the critical section.

ForwardPacket now appends the frame to one of two 16 KiB buffers and
returns. A flusher goroutine, started if none is running, swaps the
buffer for the spare under the mutex and writes it with the mutex
released, so callers keep filling the spare while a write is in
progress and frames from concurrent callers share a write. A caller
that finds the spare full waits for the write to complete, preserving
backpressure. The buffers are allocated on first forward, so only mesh
clients carry them.

Forward buffers are written directly to the connection rather than
through the client's bufio.Writer. A new mutex serializes those writes
with the client's other senders, since not every Conn tolerates
concurrent writes and bufio can split one frame over several.

A failed write is logged with the number of frames dropped, closes the
connection, wakes waiting callers with the error, and is returned by
every later call, so derphttp reconnects as before.

Adds BenchmarkForwardPacket, which also reports percentiles of
ForwardPacket's own latency, unit tests for batching, backpressure,
oversized frames, write failure, and write serialization, and an
end-to-end test that concurrently forwarded packets all reach the
destination.

Updates tailscale/corp#50628

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-11-02 15:57:10 -05:00
Brendan CreaneandSam Jean f3b80645fb ipn/ipnlocal: enable full duplex in the serve reverse proxy (#22116)
Go's HTTP/1 server closes the request body once the handler starts
writing the response. The proxy's Transport may still be reading that
body, and when its final read fails it closes the backend connection
under the response it is copying. The client gets a truncated response.

Fixes #21894

Signed-off-by: Brendan Creane <bcreane@gmail.com>
Co-authored-by: Sam Jean <samj@tailscale.com>
2026-11-02 09:13:00 -08:00
Adriano Sela Aviles 5cf2727225 control,ipn: add Tailnet hint for device registration
Add an ipn.Prefs.Tailnet field, passed through controlclient.Options
into tailcfg.RegisterRequest.Tailnet, so a client can recommend or
require a tailnet at registration (e.g. to skip the tailnet picker when
the intent is already known) without impersonating MDM.

Previously this value could only come from the "Tailnet" syspolicy
setting. That setting still takes precedence as an administrative
override; the prefs value is used only when no policy is set.

Updates #22093

Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
2026-11-02 09:22:43 -07:00
chaosinthecrd 57ca6e1e2a cmd/k8s-operator/e2e: test that cleanup finds devices by machine key
Adds an e2e test for proxy cleanup when the state Secret has no
device_id, as the unit tests use a fake client and can't show that
control honours the machineKey filter. It fills the tailnet with other
devices, checks the filter returns only the proxy's device, then removes
device_id from the state Secret and unexposes the Service. It checks
that the proxy's device is deleted and the other devices are left alone.

Updates #18922

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-02 11:36:55 +00:00
chaosinthecrd afb8baf000 cmd/k8s-operator,k8s-operator: test cleanup of HA devices with no device ID
Adds tests that ProxyGroup scale-down and cleanup, Recorder cleanup and
PeerRelay cleanup delete the device matching the machine key in a state
Secret that has no device ID. The Recorder test puts that Secret on
replica 0, so it also checks that cleanup carries on to the other
replicas.

Updates #18922

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-02 11:36:55 +00:00
chaosinthecrd 4f24492cf9 cmd/k8s-operator: test cleanup of devices that never reported a device ID
Adds a test that proxy cleanup deletes the device matching the machine
key in a state Secret that has no device_id. It also checks that
device_id is still used when present, that Secrets with a missing or
invalid machine key are skipped, and that Connector status does not
list a proxy before it reports its device ID.

Updates #18922

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-02 11:36:55 +00:00
chaosinthecrd 8af4c2b6bd cmd/k8s-operator,k8s-operator: delete HA devices that never reported an ID
ProxyGroup, Recorder and PeerRelay cleanup and replica scale-down only
delete a replica's device if its state Secret holds the device's ID. A
replica that is shut down after it registered with control but before it
learnt its ID leaves its device in the tailnet.

This fixes this by deleting the device by the machine key in the state
Secret in each of these places when there is no ID.

Updates #18922

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
2026-11-02 11:36:55 +00:00