Commit Graph
312 Commits
Author SHA1 Message Date
Brad Fitzpatrick 8e1fbc4740 derp/derpserver: serve hijacked connections on their own goroutine
Previously the DERP handler served each connection for its lifetime
on its hijacked connection's net/http handler goroutine. That
goroutine's conn.serve stack frame kept the dead HTTP/1 server state
reachable for the whole DERP connection: the http.conn and its 4KB
bufio.Writer (hijack hands over c.bufw but conn.serve still references
it, so derpserver returning it to its flush pool never made it
collectable), the 4KB bufio.Reader, the upgrade *http.Request with its
parsed headers, and the request context chain. The goroutine also kept
the stack growth from the TLS handshake and HTTP request parsing.

Instead, hand the connection off to a new goroutine and return from
the handler (ala tailscale/corp@dc09e27aef), letting all the HTTP
upgrade state be collected. Give Accept a smaller 1KB frame reader,
draining and releasing the hijacked reader if it contains buffered
bytes from a fast-start client, and a nil bufio.Writer so writes go
through pooled buffers held only for the duration of a write instead
of a per-connection buffer.

Because the handler now returns at handoff time, cmd/derper's
gauge_derper_tls_active_version decrement can no longer be deferred
to handler return: intercept Hijack in the TLS metrics wrapper and,
for hijacked connections (DERP, its WebSocket flavor, and CONNECT),
decrement the gauge once when the hijacked connection closes,
restoring the gauge's connection-lifetime semantics. Teach
derpserver's TCP RTT stats to unwrap the close-hook conn so they
still find the underlying *net.TCPConn.

Also soften the UntypedHexString deprecation notices in types/key to
warnings: the untyped hex string format is the DERP wire protocol's
key encoding, so these call sites are legitimate and permanent, and
a Deprecated marker just makes them light up in editors and linters.
The cautionary text about the format's risks remains.

Measured with 100,000 idle TLS DERP connections on linux/amd64:
standing memory drops from 55.6KB to 32.6KB per connection (-41%).

Updates #21064

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: If05a0c6ea79134807e9e8872861db216
2026-09-08 11:26:05 -07:00
Brad Fitzpatrick faa10ddaaa go.toolchain.branch: switch to Go 1.27
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
2026-08-27 09:52:15 -07:00
Brad Fitzpatrick 75519889f5 derp, cmd/derper: relay client app names to watchers, allow banning them
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
2026-08-24 08:37:00 -07:00
Will HannahandWill Hannah e1ffeeffba client,feature/favorites: add locally-pinned favorites (#20563)
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>
2026-08-19 09:52:56 -04:00
Simon Law 00699abdfb tailcfg,tailcfg/{nodecap,selfcap}: split capability constants to their own packages (#20639)
Package tailcfg defines the types and constants used by the Tailscale
protocol, but since everything is all in one package, it’s difficult
to sift through the docs: https://pkg.go.dev/tailscale.com/tailcfg

We define and enumerate capabilities as string constants for
tailcfg.NodeCapability and tailcfg.PeerCapability. This PR extracts
them into their own packages:

- tailcfg.CapabilityFileSharing becomes nodecap.FileSharing
- tailcfg.NodeAttrOnlyTCP443 becomes nodecap.OnlyTCP443
- tailcfg.PeerCapabilityTaildrive becomes peercap.Taildrive

We originally intended for CapabilityFoo to grant an entitlement or
permission for Foo, and for NodeAttrBar to configure Bar in the
nodeAttrs section of the policy file. However, there was no technical
enforcement of this convention, so new capabilities have used the
NodeAttr prefix regardless of meaning. Therefore, this PR unifies
tailcfg.CapabilityFoo and tailcfg.NodeAttrBar into a single package as
nodecap.Foo and nodecap.Bar.

Ran `go fix -inline ./...` and committed the changes that replaced
uses of the tailcfg aliases with the authoritative ones.

Updates #20259

Change-Id: Ieb7e7e6c8247c39faf42fdf15c68cdc7c621c730
Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-08-07 16:30:35 -07:00
Rollie Ma f4978b4b9b feature,client: add serviceclientprefs for desktop client service launch (#20501)
Add serviceclientprefs, an optional feature that stores and loads the
desktop clients' saved service launch preferences, one file per login
profile.

- Add GET|POST /localapi/v0/prefs/service-clients to load and save the
  current profile's service client prefs.
- Add local client GetServiceClientPrefs and SetServiceClientPref that
  call the new local api endpoint.
- Store the prefs with the ipn/store FileStore at
  TailscaleVarRoot()/profile-data/<profileID>/service-client-prefs/<hex-encoded-key>,
  so DeleteProfile cleans them up for free. Fall back to an in-memory
  store when there's no var root.
- Register the feature and its local api route from build tagged files
  so the whole thing drops out under ts_omit_serviceclientprefs.
- Add the serviceclient package holding Pref and Prefs (saved client,
  username, database name, and last used time), so the local api client
  and desktop apps can import the types without the feature machinery.

Change-Id: I340a99c1b332d181fb1556fbf3e8003bb3b95a08
Updates: https://github.com/tailscale/tailscale/issues/20429

Signed-off-by: Rollie Ma <rollie@tailscale.com>
2026-07-22 20:36:05 -07:00
Brad Fitzpatrick 5384d23690 cmd/derper: add opt-in support for LetsEncrypt IP address certificates
LetsEncrypt made certificates for bare IP addresses generally
available in January 2026. They require the short-lived ACME
certificate profile and are valid for about six days.

Add a new --acme-ip-certs flag. When set (with the default
--certmode=letsencrypt), connections that arrive by IP address (no
TLS SNI, or an IP address SNI matching the connection's destination
address) get a LetsEncrypt cert for that IP, obtained on demand using
the "shortlived" profile and the HTTP-01 challenge served on derper's
plaintext HTTP port. Because the certificate is requested for
whatever address the connection actually arrived on, it works for
both IPv4 and IPv6 with no per-address configuration, and a client
can never make us request a certificate for an address that isn't
ours. Connections with a DNS name in the SNI keep using the regular
autocert manager for --hostname.

autocert can't do any of this itself, as it neither orders IP address
identifiers nor serves connections without SNI, so this adds a small
dedicated cert manager using tailscale.com/tempfork/acme instead.

Clients can then connect to https://<IP> without the DERPMap CertName
pinning that self-signed certs from --certmode=manual require.

Updates tailscale/corp#45167
Updates #11776

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I8e2d5b0a7c4f9e1b3d6a8c2f5e0b9d4a7c1f3e6d
2026-07-22 12:33:42 -07:00
Brad Fitzpatrick 82cfea90ca all: fix JSON serialization under Go 1.27's finalized encoding/json/v2
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
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
Simon Law 932260511e ipn/ipnlocal: use routecheck reports to make exit node suggestions
Now that the routecheck subsystem is continuously collecting
reachability reports in the background, we can add a hook to
LocalBackend for fetching its report. That allows
suggestExitNodeUsingTrafficSteering to consult that report when
disqualifying candidates, instead of blocking on an immediate probe.

Exit node suggestions will only consult the report when the
`client-side-reachability` and `client-side-reachability-routecheck`
node attributes are both set on the current node.

Updates #17366
Updates tailscale/corp#33033

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-07-02 20:26:27 -07:00
kari-ts 07cefc083d ipn/{ipn,ipnlocal}: add per-user policy snapshots to IPN bus (#20135)
This adds the NotifyInitialPolicy watch option and the Policy field in
Notify so that clients can receive the effective policy snapshot via IPN
bus.

This extends policyclient.Client so ipnlocal can get and watch policy
snapshots, which is used by sysPolicyChanged to notify watchers.

User-scoped policy store registration, management, and cleanup will be
added in a follow-up

Updates tailscale/corp#42259

Signed-off-by: kari <kari@tailscale.com>
2026-06-30 12:44:29 -07:00
Brad Fitzpatrick 1c77079fd7 ipn/ipnlocal, feature/acme: move most remaining cert code into feature/acme
f5eac39ea ("feature/acme, ipn/ipnlocal: start moving ACME/cert state
into an extension") started to move the cert code into feature/acme
but was meant as a baby step.

This goes further, moving almost everything, leaving only some hooks
in ipnlocal.

When we later move "serve" support out to feature/serve, this will
look a bit different in that the hooks currently in ipnlocal will move
to feature/serve (cert support already depends on serve).

As part of this, cert-related tests move to feaure/acme too, which
means some test infra from ipnlocal now moves to shared ipnlocaltest.
(it's not big at the moment, but I imagine it growing)

Updates #12614

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9ea89aa9754f12d54b81751b6bd830f2664241ff
2026-06-29 12:57:22 -07:00
Brad Fitzpatrick 6ab5d91071 go.mod: bump some deps to match corp
Updates tailscale/corp#43243
Updaets #20067

Change-Id: I27e19f34e2216f3ac1a4e2a6b38c0ac473b8c7ad
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-06-10 21:29:22 -05:00
Simon Law 2d6844c565 cmd/tailscale/cli: add routecheck command (#19641)
Introduce a new `tailscale routecheck` command which prints a report
of high-availability routers that are reachable.

This command rhymes with the `tailscale netcheck` command and but
instead of reporting on local network conditions, `routecheck` reports
on remote connectivity.

Updates #17366
Updates tailscale/corp#33033

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-01 11:50:24 -07:00
Simon Law 2ee9eacb94 client/local,ipn/localapi: add /localapi/v0/routecheck endpoint (#19640)
In order to support a `tailscale routecheck` command, we introduce the
`/localapi/v0/routecheck` endpoint to the local API. This endpoint
returns the most recent report collected by the routecheck client.
If `force=true` is an argument in the query string, then this endpoint
will actively probe before returning the report.

Updates #17366
Updates tailscale/corp#33033

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-06-01 11:06:14 -07:00
Brad Fitzpatrick 04ae61fe4b tstest/integration/jswasmtest: add headless-Chromium tests for @tailscale/connect
Add Go tests that drive a real headless Chromium (via chromedp) against
the built cmd/tsconnect/pkg/ artifact and verify the @tailscale/connect
public API surface end-to-end. The package has not been republished in
three years, in part because no test exercises the produced artifact at
runtime — only tsc --noEmit and a Go build run in CI.

TestCreateIPN loads pkg.js into the browser, calls createIPN with a junk
auth key, and asserts that pkg.createIPN / pkg.runSSHSession are
functions and that createIPN() returns an IPN with the documented
run/login/logout/ssh/fetch methods. No control-plane traffic.

TestFetchTailnetPeer stands up a full local tailnet (testcontrol +
DERP + a tsnet.Server peer) and verifies that the browser-side WASM
client can join over WebSocket-noise to the same control, connect to
DERP over WSS, and then ipn.fetch() an HTTP service hosted on the tsnet
peer through the tailnet. The test asserts the response body matches a
known string. Browser state transitions are logged: NoState -> NeedsLogin
-> Starting -> Running.

Tests are opt-in via --run-headless-browser-tests (matching the existing
--run-vm-tests pattern in tstest/natlab/vmtest) so they never fire in
casual `go test ./...` runs. When the flag is set, a test is skipped if
cmd/tsconnect/pkg/ has not been built, and fails with t.Error if no
chromium binary is found on $PATH (honoring $CHROME_BIN as an override).
findChromium also falls back to /Applications/Google Chrome.app and
/Applications/Chromium.app on darwin, since macOS Chrome's executable
lives inside an .app bundle and is not on $PATH by default. The
.github/workflows/test.yml wasm job is extended to install
google-chrome-stable and run the tests with the flag after build-pkg.

To prevent silently testing a stale pkg/main.wasm (built from an older
checkout than the rest of the test invocation), build-pkg now writes
pkg/build-info.json recording the sha256 of the raw (pre-wasm-opt)
go-build output. The test does its own `go build` of
cmd/tsconnect/wasm with the same -tags/-trimpath/-ldflags (factored
into a new cmd/tsconnect/wasmbuild package shared by both call sites)
and t.Fatalfs with a "rebuild" instruction on mismatch. Cost is
near-zero because the Go build cache from the prior build-pkg makes
the rebuild a cache hit.

The new wasmbuild package also replaces cmd/tsconnect's hardcoded -tags
string with a minimal-feature-set computation. wasmbuild.Keep names the
small set of feature/featuretags entries the browser client actually
needs (netstack, logtail, dns, health, c2n, ipnbus); wasmbuild.Tags()
emits a ts_omit_<f> for every other
omittable feature in feature/featuretags.Features, with transitive deps
expanded via featuretags.Requires. An init() panics if Keep references
a feature unknown to feature/featuretags so a rename there fails
loudly. Net effect on size: 32M raw / 9.4M brotli before this change,
25M raw / 4.4M brotli after — vs the last-published 1.39.98 at 21M /
3.8M. The transitive package-import graph is unchanged (176
tailscale.com/* packages either way): featuretags omits eliminate
dead code via `const HasX = false`, not imports. Trimming the import
graph would require a separate, larger refactor splitting interface
packages by build tag.

Writing TestFetchTailnetPeer surfaced several real issues, all fixed
here:

  * cmd/tsconnect built the wasm with the nethttpomithttp2 tag, but
    control/ts2021 (since commit 1d93bdce2, "control/controlclient:
    remove x/net/http2, use net/http", Oct 2025) requires HTTP/2 from
    net/http's bundled implementation. With nethttpomithttp2 set, the
    bundle is excluded and the wasm client cannot speak HTTP/2 to any
    control plane, including production. Drop the tag. Wasm size grows
    ~1 MB raw / ~300 KB brotli (more than offset by the feature
    pruning above). The last published @tailscale/connect (1.39.98,
    early 2023) pre-dates the regression, which is why no consumer has
    reported the breakage.

  * tstest/integration/testcontrol.Server's /ts2021 noise upgrade
    endpoint rejected anything but POST. WebSocket clients (the only
    transport available to browser-WASM) come in as GET. Allow both;
    the controlhttp AcceptHTTP path dispatches on the Upgrade header,
    so the websocket library still enforces GET for WS upgrades.
    This matches production, where the same controlhttpserver.AcceptHTTP
    routes purely on the Upgrade header without checking method.

  * derp/derphttp's urlString built the DERP URL from node.HostName
    only, dropping node.DERPPort. Non-WS clients use a separate code
    path (connectToHost) that honors DERPPort, but WebSocket-only
    clients (browser-WASM) went through urlString and so could not
    reach a DERP running on any port other than 443. Include the port
    when it differs from the scheme default.

Also move addWebSocketSupport from cmd/derper (where it was main-only)
to derp/derpserver.AddWebSocketSupport so tstest/integration.RunDERPAndSTUN
can wrap its DERP handler with WebSocket support — without that, the
test DERP would not accept the browser's wss connection.

Fixes #9394

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Iff9cdee303e3b239924249b5bffb2fd04e02f391
2026-05-20 10:48:29 -07:00
Brad Fitzpatrick 6b729795c3 derp/derpserver: use hashtriemap for peer lookup
Replace the process-global Server.mu lookup in the packet send hot path
with a global hashtriemap mirror of local clientSet entries. The
authoritative clients map remains guarded by Server.mu; clientsAtomic is
only a lock-free fast path for active local clients.

Misses, stale inactive client sets, duplicate accounting, and mesh
forwarding still fall back to lookupDestUncached. This avoids taking
Server.mu for the common local active-client send path, at the cost of
adding one global concurrent map that mirrors Server.clients for local
peers.

The benchmark uses four destination peers. The before run sets
TS_DEBUG_DERP_DISABLE_PEER_HASHTRIE=true to force the old mutex lookup
path; the after run uses the hashtrie fast path.

    goos: linux
    goarch: amd64
    pkg: tailscale.com/derp/derpserver
    cpu: Intel(R) Xeon(R) 6975P-C
                          │    before     │                after                │
                          │    sec/op     │   sec/op     vs base                │
    LookupDestHashTrie-16   176.050n ± 1%   1.904n ± 6%  -98.92% (p=0.000 n=10)

                          │   before   │             after              │
                          │    B/op    │    B/op     vs base            │
    LookupDestHashTrie-16   0.000 ± 0%   0.000 ± 0%  ~ (p=1.000 n=10) ¹
    ¹ all samples are equal

                          │   before   │             after              │
                          │ allocs/op  │ allocs/op   vs base            │
    LookupDestHashTrie-16   0.000 ± 0%   0.000 ± 0%  ~ (p=1.000 n=10) ¹
    ¹ all samples are equal

Updates #3560 (very indirectly, historically)
Updates #19713 (as an alternative to that PR)

Change-Id: Ifb72e5c9854ad00e938cd24c6ab9c27312f297e8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-05-12 16:08:16 -07:00
Alex Valiushko 01d0bdd253 cmd/derper,derp: add metrics for rate limit hits (#19560)
Expvars track count of rate limiters exceeding their threshold.
Covers (1) global rate limiter and (2) total of local rate limiters.

Also publish optional rate-limit metrics during ExpVar() call
if -rate-config is specified. Fixes current rate-limit metrics
being published outside of "derp" in /debug/vars.

Updates tailscale/corp#38509

Change-Id: Ic7f5a1e890d0d7d3d7b679daa4b5f8926a6a6964
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
2026-04-29 10:29:09 -07:00
Brad FitzpatrickandAvery Pennarun a97850f7e2 cmd/derper: fix TestLookupMetric to pass when run alone
TestLookupMetric was added in e8d140654 (2023-08-17) without
initializing the dnsCache and dnsCacheBytes globals. When run in
isolation, handleBootstrapDNS writes a nil body (from the
uninitialized dnsCacheBytes), causing getBootstrapDNS to fail
decoding an empty response with EOF.

Add a setDNSCache test helper that stores the dnsEntryMap, marshals
dnsCacheBytes, and registers a t.Cleanup to nil both out, so tests
that forget to call it will hit the dnsCache-nil fatal in
getBootstrapDNS rather than silently depending on prior test state.

Also add AssertNotParallel and a dnsCache-nil fatal check to
getBootstrapDNS, the central helper all bootstrap DNS tests flow
through, to prevent future tests from running in parallel (they
all mutate package-level DNS caches and metrics) and to give a
clear error if a test forgets to initialize the DNS caches.

Fixes #19388

Change-Id: I8ad454ec6026c71f13ecfa14d25925df5478b908
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Co-authored-by: Avery Pennarun <apenwarr@tailscale.com>
2026-04-13 17:20:43 -07:00
Jordan Whited 929ad51be0 cmd/derper: mark rate-config flag as experimental and unstable
Updates tailscale/corp#38509

Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-04-13 12:24:59 -07:00
Mike O'Driscoll ca5db865b4 cmd/derper,derp: add --rate-config file with SIGHUP reload (#19314)
Add a --rate-config flag pointing to a JSON file for per-client receive
rate limits (bytes/sec and burst bytes). The config is reloaded on SIGHUP,
updating all existing client connections live. The --per-client-rate-limit
and --per-client-rate-burst flags are removed in favor of the config file.

In derpserver, rate limiting uses an atomic.Pointer[xrate.Limiter] per
client: nil when unlimited or mesh (zero overhead), non-nil when
rate-limited.

Document that clientSet.activeClient Store operations require Server.mu.

Updates tailscale/corp#38509

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-04-10 18:37:54 -04:00
Mike O'Driscoll e689283ebd derp/derpserver: add per-connection receive rate limiting (#19222)
Add server-side per-client bandwidth enforcement using TCP backpressure.
When configured, the server calls WaitN after reading each DERP frame,
which delays the next read, fills the TCP receive buffer, shrinks
the TCP window, and naturally throttles the sender — no packets are dropped.

- Rate limiting is on the receive (inbound) side, which is what an abusive
  client controls
- Mesh peers are exempt since they are trusted infrastructure
- The burst size is at least MaxPacketSize (64KB) to ensure a
  single max-size frame can always be processed

Also refactors sclient to store a context.Context directly instead of a
done channel, which simplifies the rate limiter's WaitN call.

Flags added to cmd/derper:
  --per-client-rate-limit (bytes/sec, default 0 = unlimited)
  --per-client-rate-burst (bytes, default 0 = 2x rate limit)

Example for 10Mbps: --per-client-rate-limit=1250000

Updates #38509

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-04-07 18:40:41 -04:00
Brad Fitzpatrick 5ef3713c9f cmd/vet: add subtestnames analyzer; fix all existing violations
Add a new vet analyzer that checks t.Run subtest names don't contain
characters requiring quoting when re-running via "go test -run". This
enforces the style guide rule: don't use spaces or punctuation in
subtest names.

The analyzer flags:
- Direct t.Run calls with string literal names containing spaces,
  regex metacharacters, quotes, or other problematic characters
- Table-driven t.Run(tt.name, ...) calls where tt ranges over a
  slice/map literal with bad name field values

Also fix all 978 existing violations across 81 test files, replacing
spaces with hyphens and shortening long sentence-like names to concise
hyphenated forms.

Updates #19242

Change-Id: Ib0ad96a111bd8e764582d1d4902fe2599454ab65
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-05 15:52:51 -07:00
Mike O'Driscoll 1403920367 derp,types,util: use bufio Peek+Discard for allocation-free fast reads (#19067)
Replace byte-at-a-time ReadByte loops with Peek+Discard in the DERP
read path. Peek returns a slice into bufio's internal buffer without
allocating, and Discard advances the read pointer without copying.

Introduce util/bufiox with a BufferedReader interface and ReadFull
helper that uses Peek+copy+Discard as an allocation-free alternative
to io.ReadFull.

  - derp.ReadFrameHeader: replace 5× ReadByte with Peek(5)+Discard(5),
    reading the frame type and length directly from the peeked slice.
    Remove now-unused readUint32 helper.

    name                  old ns/op  new ns/op  speedup
    ReadFrameHeader-8     24.2       12.4       ~2x
    (0 allocs/op in both)

  - key.NodePublic.ReadRawWithoutAllocating: replace 32× ReadByte with
    bufiox.ReadFull. Addresses the "Dear future" comment about switching
    away from byte-at-a-time reads once a non-escaping alternative exists.

    name                              old ns/op  new ns/op  speedup
    NodeReadRawWithoutAllocating-8    140        43.6       ~3.2x
    (0 allocs/op in both)

  - derpserver.handleFramePing: replace io.ReadFull with bufiox.ReadFull.

Updates tailscale/corp#38509

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-03-24 10:52:20 -04:00
Brad Fitzpatrick bd2a2d53d3 all: use Go 1.26 things, run most gofix modernizers
I omitted a lot of the min/max modernizers because they didn't
result in more clear code.

Some of it's older "for x := range 123".

Also: errors.AsType, any, fmt.Appendf, etc.

Updates #18682

Change-Id: I83a451577f33877f962766a5b65ce86f7696471c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-06 13:32:03 -08:00
Brad Fitzpatrick 2a64c03c95 types/ptr: deprecate ptr.To, use Go 1.26 new
Updates #18682

Change-Id: I62f6aa0de2a15ef8c1435032c6aa74a181c25f8f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-05 20:13:18 -08:00
Brad Fitzpatrick d784dcc61b go.toolchain.branch: switch to Go 1.26
Updates #18682

Change-Id: I1eadfab950e55d004484af880a5d8df6893e85e8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-04 21:57:05 -08:00
Fran Bull 3b737edbf1 appc,feature/conn25,net: Add DNS response interception for conn25
The new version of app connector (conn25) needs to read DNS responses
for domains it is interested in and store and swap out IP addresses.

Add a hook to dns manager to enable this.
Give the conn25 updated netmaps so that it knows when to assign
connecting addresses and from what pool.
Assign an address when we see a DNS response for a domain we are
interested in, but don't do anything with the address yet.

Updates tailscale/corp#34252

Signed-off-by: Fran Bull <fran@tailscale.com>
2026-02-20 07:43:37 -08:00
Will Norris 3ec5be3f51 all: remove AUTHORS file and references to it
This file was never truly necessary and has never actually been used in
the history of Tailscale's open source releases.

A Brief History of AUTHORS files
---

The AUTHORS file was a pattern developed at Google, originally for
Chromium, then adopted by Go and a bunch of other projects. The problem
was that Chromium originally had a copyright line only recognizing
Google as the copyright holder. Because Google (and most open source
projects) do not require copyright assignemnt for contributions, each
contributor maintains their copyright. Some large corporate contributors
then tried to add their own name to the copyright line in the LICENSE
file or in file headers. This quickly becomes unwieldy, and puts a
tremendous burden on anyone building on top of Chromium, since the
license requires that they keep all copyright lines intact.

The compromise was to create an AUTHORS file that would list all of the
copyright holders. The LICENSE file and source file headers would then
include that list by reference, listing the copyright holder as "The
Chromium Authors".

This also become cumbersome to simply keep the file up to date with a
high rate of new contributors. Plus it's not always obvious who the
copyright holder is. Sometimes it is the individual making the
contribution, but many times it may be their employer. There is no way
for the proejct maintainer to know.

Eventually, Google changed their policy to no longer recommend trying to
keep the AUTHORS file up to date proactively, and instead to only add to
it when requested: https://opensource.google/docs/releasing/authors.
They are also clear that:

> Adding contributors to the AUTHORS file is entirely within the
> project's discretion and has no implications for copyright ownership.

It was primarily added to appease a small number of large contributors
that insisted that they be recognized as copyright holders (which was
entirely their right to do). But it's not truly necessary, and not even
the most accurate way of identifying contributors and/or copyright
holders.

In practice, we've never added anyone to our AUTHORS file. It only lists
Tailscale, so it's not really serving any purpose. It also causes
confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header
in other open source repos which don't actually have an AUTHORS file, so
it's ambiguous what that means.

Instead, we just acknowledge that the contributors to Tailscale (whoever
they are) are copyright holders for their individual contributions. We
also have the benefit of using the DCO (developercertificate.org) which
provides some additional certification of their right to make the
contribution.

The source file changes were purely mechanical with:

    git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g'

Updates #cleanup

Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d
Signed-off-by: Will Norris <will@tailscale.com>
2026-01-23 15:49:45 -08:00
Anton Tolchanov 17b0c7bfb3 metrics: add a NewLabelMap helper to create and register label maps
Updates tailscale/corp#31174

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2026-01-13 20:08:24 +00:00
Raj Singh d451cd54a7 cmd/derper: add --acme-email flag for GCP cert mode (#18278)
GCP Certificate Manager requires an email contact on ACME accounts.
Add --acme-email flag that is required for --certmode=gcp and
optional for --certmode=letsencrypt.

Fixes #18277

Signed-off-by: Raj Singh <raj@tailscale.com>
2025-12-25 01:57:11 -05:00
Will Norris 0fd1670a59 client/local: add method to set gauge metric to a value
The existing client metric methods only support incrementing (or
decrementing) a delta value.  This new method allows setting the metric
to a specific value.

Updates tailscale/corp#35327

Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d
Signed-off-by: Will Norris <will@tailscale.com>
2025-12-16 14:11:33 -08:00
Raj SinghandBrad Fitzpatrick 8eda947530 cmd/derper: add GCP Certificate Manager support (#18161)
Add --certmode=gcp for using Google Cloud Certificate Manager's
public CA instead of Let's Encrypt. GCP requires External Account
Binding (EAB) credentials for ACME registration, so this adds
--acme-eab-kid and --acme-eab-key flags.

The EAB key accepts both base64url and standard base64 encoding
to support both ACME spec format and gcloud output.

Fixes tailscale/corp#34881

Signed-off-by: Raj Singh <raj@tailscale.com>
Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-12-10 04:51:53 +05:30
Alex Chan e33f6aa3ba go.mod: bump the version of setec
Updates https://github.com/tailscale/corp/issues/34813

Change-Id: I926f1bad5bf143d82ddb36f51f70deb24fa11e71
Signed-off-by: Alex Chan <alexc@tailscale.com>
2025-12-03 16:43:32 +00:00
James Tucker 5ee0c6bf1d derp/derpserver: add a unique sender cardinality estimate
Adds an observation point that may identify potentially abusive traffic
patterns at outlier values.

Updates tailscale/corp#24681

Signed-off-by: James Tucker <james@tailscale.com>
2025-11-26 12:23:35 -08:00
Tom Proctor 6637003cc8 cmd/cigocacher,go.mod: add cigocacher cmd
Adds cmd/cigocacher as the client to cigocached for Go caching over
HTTP. The HTTP cache is best-effort only, and builds will fall back to
disk-only cache if it's not available, much like regular builds.

Not yet used in CI; that will follow in another PR once we have runners
available in this repo with the right network setup for reaching
cigocached.

Updates tailscale/corp#10808

Change-Id: I13ae1a12450eb2a05bd9843f358474243989e967
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
2025-11-24 21:15:46 +00:00
Alex Chan c2e474e729 all: rename variables with lowercase-l/uppercase-I
See http://go/no-ell

Signed-off-by: Alex Chan <alexc@tailscale.com>

Updates #cleanup

Change-Id: I8c976b51ce7a60f06315048b1920516129cc1d5d
2025-11-18 09:12:34 +00:00
Brad Fitzpatrick 1eba5b0cbd util/eventbus: log goroutine stacks when hung in CI
Updates #17680

Change-Id: Ie48dc2d64b7583d68578a28af52f6926f903ca4f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-11-11 00:42:31 -08:00
Claus Lensbøl 7418583e47 health: compare warnable codes to avoid errors on release branch (#17637)
This compares the warnings we actually care about and skips the unstable
warnings and the changes with no warnings.

Fixes #17635

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
2025-10-24 12:08:35 -04:00
Patrick O'Doherty e45557afc0 types/persist: add AttestationKey (#17281)
Extend Persist with AttestationKey to record a hardware-backed
attestation key for the node's identity.

Add a flag to tailscaled to allow users to control the use of
hardware-backed keys to bind node identity to individual machines.

Updates tailscale/corp#31269


Change-Id: Idcf40d730a448d85f07f1bebf387f086d4c58be3

Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
2025-10-10 10:28:36 -07:00
Brad Fitzpatrick 9a72513fa4 go.toolchain.rev: bump Go to 1.25.2
Updates tailscale/go#135

Change-Id: I89cfb49b998b2fd0264f8d5f4a61af839cd06626
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-10-08 08:59:16 -07:00
M. J. Fromberger 127a967207 appc,*: publish events for route updates and storage (#17392)
Add and wire up event publishers for these two event types in the AppConnector.
Nothing currently subscribes to them, so this is harmless. Subscribers for
these events will be added in a near-future commit.

As part of this, move the appc.RouteInfo type to the types/appctype package.
It does not contain any package-specific details from appc. Beside it, add
appctype.RouteUpdate to carry route update event state, likewise not specific
to appc.  Update all usage of the appc.* types throughout to use appctype.*
instead, and update depaware files to reflect these changes.

Add a Close method to the AppConnector to make sure the client gets cleaned up
when the connector is dropped (we re-create connectors).

Update the unit tests in the appc package to also check the events published
alongside calls to the RouteAdvertiser.

For now the tests still rely on the RouteAdvertiser for correctness; this is OK
for now as the two methods are always performed together.  In the near future,
we need to rework the tests so not require that, but that will require building
some more test fixtures that we can handle separately.

Updates #15160
Updates #17192

Change-Id: I184670ba2fb920e0d2cb2be7c6816259bca77afe
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
2025-10-02 09:31:42 -07:00
Brad Fitzpatrick 801aac59db Makefile, cmd/*/depaware.txt: split out vendor packages explicitly
depaware was merging golang.org/x/foo and std's
vendor/golang.org/x/foo packages (which could both be in the binary!),
leading to confusing output, especially when I was working on
eliminating duplicate packages imported under different names.

This makes the depaware output longer and grosser, but doesn't hide
reality from us.

Updates #17305

Change-Id: I21cc3418014e127f6c1a81caf4e84213ce84ab57
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-10-01 13:02:06 -07:00
Brad Fitzpatrick c2f37c891c all: use Go 1.20's errors.Join instead of our multierr package
Updates #7123

Change-Id: Ie9be6814831f661ad5636afcd51d063a0d7a907d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-10-01 08:10:59 -07:00
Brad Fitzpatrick 442a3a779d feature, net/tshttpproxy: pull out support for using proxies as a feature
Saves 139 KB.

Also Synology support, which I saw had its own large-ish proxy parsing
support on Linux, but support for proxies without Synology proxy
support is reasonable, so I pulled that out as its own thing.

Updates #12614

Change-Id: I22de285a3def7be77fdcf23e2bec7c83c9655593
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-30 10:25:56 -07:00
Fran Bull 65d6c80695 cmd/tailscale/cli,client,ipn: add appc-routes cli command
Allow the user to access information about routes an app connector has
learned, such as how many routes for each domain.

Fixes tailscale/corp#32624

Signed-off-by: Fran Bull <fran@tailscale.com>
2025-09-29 13:48:07 -07:00
Brad Fitzpatrick 976389c0f7 feature/sdnotify: move util/systemd to a modular feature
Updates #12614

Change-Id: I08e714c83b455df7f538cc99cafe940db936b480
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-29 13:08:36 -07:00
Brad Fitzpatrick 7bcab4ab28 feature/featuretags: make CLI connection error diagnostics modular
Updates #12614

Change-Id: I09b8944166ee00910b402bcd5725cd7969e2c82c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-29 09:22:50 -07:00
Brad Fitzpatrick 475b520aa2 tsconst, util/linuxfw, wgengine/router: move Linux fw consts to tsconst
Now cmd/derper doesn't depend on iptables, nftables, and netlink code :)

But this is really just a cleanup step I noticed on the way to making
tsnet applications able to not link all the OS router code which they
don't use.

Updates #17313

Change-Id: Ic7b4e04e3a9639fd198e9dbeb0f7bae22a4a47a9
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-28 08:02:50 -07:00