Commit Graph

4323 Commits

Author SHA1 Message Date
Kristoffer Dalby 83eff4000a poll: interrupt blocked map writes
Updates #3410

(cherry picked from commit ef456542ce)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 3a2b133a41 .github/workflows: regenerate the integration test matrix 2026-09-23 21:19:22 +02:00
Kristoffer Dalby b9c7b4c44b integration: cover node deletion ending the long poll
A deleted node is served a self node with no StableID, so Status().Self.ID
goes empty; TestACLPolicyPropagationOverTime must match on hostname instead.

Updates #3410

(cherry picked from commit 2cefb7ec06)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby fb197db163 poll, noise: tell a deleted node to re-authenticate
A bare 404 is indistinguishable from any other map-path error to a Tailscale
client: it retries forever, still logged in. Only a self node with a past
KeyExpiry reaches NeedsLogin. Also skip the reconnect grace wait, which a
deleted node can never satisfy.

Fixes #3410

(cherry picked from commit b1fb6ed2e6)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 2b6cb3a2cc types/change: drop unused VisibilityChange
It fills PeersRemoved without deleting anything. The batcher now tears down
the session behind every removed id, so a caller would kill a live poll.

Updates #3410

(cherry picked from commit 42bf00523a)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 397149e9be mapper: stop a deleted node's map session
Dropping the batcher entry left serveLongPoll streaming to a node that no
longer exists: Close ranges b.nodes and can no longer reach it, so shutdown
blocks and the client keeps polling instead of re-authenticating.

Updates #3410

(cherry picked from commit 0b69e844f5)
2026-09-23 21:19:22 +02:00
Saleh b4f991b381 types: lowercase DNS extra record names
DNS names are case-insensitive, but clients match extra records against
the lowercased query name, so records with mixed-case names (for example
"Printer.fritz.box" in an extra_records_path file) never resolved and
queries fell through to the global nameserver.

Normalize record names to lowercase where the records enter the tailcfg
DNS config, covering both dns.extra_records and extra_records_path.

Fixes #2782

(cherry picked from commit 95ba1f0566)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 2b8dbea412 mapper: assert no map response lists the recipient as its own peer
Covers every change shape under four policy shapes, plus connect churn. The
zero-matcher shape is the gap: buildTailPeers skips ReduceNodes there, so the
peer lookup is the only self filter left.

(cherry picked from commit 8995d8a558)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby d4b073e830 state: exclude self from peers on the named peer-ID path
ListPeers with explicit IDs filtered every node, not every peer, so a change
batch naming the recipient returned it as its own peer. db.ListPeers keeps
this out with `id <> nodeID`; the NodeStore rewrite dropped it.

(cherry picked from commit d9aebf472d)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 2a76860bdb build: bump test image Go to 1.27.1
tailscale HEAD now requires Go 1.27.1, so the test image build failed
and every integration job skipped.

Same change as f3f6c9822 on main, written against 0.29's Dockerfiles.
2026-09-23 21:19:22 +02:00
Kristoffer Dalby b3b55cc472 cli: fix users rename when resolved by name
The rename request sent the raw --identifier flag value, which is 0 when
the user was resolved with --name, so the API answered "user not found".
Send the matched user's ID instead.

Equivalent of #3442 on main, rewritten: that fix targets the v1 HTTP client,
which 0.29 does not have.
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 625fa86e1a integration: pin docker client to daemon API version
dockertest's bundled client builds against API v1.25; Docker Engine 29
rejects it (min 1.40), surfacing as a "broken pipe" that fails every local
image build. Pin to the daemon's reported version so builds use a live path.

Closes #2937

(cherry picked from commit dfc25f06e1)
2026-09-23 21:19:22 +02:00
Sebastien Tardif 46a80ea8f9 dns: cancel extra-records retry on shutdown and close watcher on setup error
After Remove/Rename, the extra-records filewatcher retried with
context.Background and the default 15-minute backoff budget, so Close
could not stop Run. Cancel that retry when closeCh closes. If the file
is still missing after the budget, watch the parent directory so a later
recreate is seen.

Close the fsnotify watcher on NewExtraRecordsManager error paths after
NewWatcher succeeds.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

(cherry picked from commit a48a42baf4)
2026-09-23 21:19:22 +02:00
Igor Serganov fc894aa9da linting issue fix
(cherry picked from commit c26b2fd0a4)
2026-09-23 21:19:22 +02:00
Igor Serganov 032470a5d3 hscontrol: cancel tailsql on graceful shutdown
Serve stored a Background context for tailsql and called
context.Done() during signal shutdown. Done only returns the
done channel and does not cancel, so tailsql never stopped
when Headscale shut down.

Create a cancellable child of the Serve context, run
`runTailSQLService` in the listener errgroup so its error is
surfaced, and call the cancel func on shutdown - tailsql
unblocks on ctx.Done.

(cherry picked from commit a9f80d8802)
2026-09-23 21:19:22 +02:00
Paulo Luna 33db8c6b29 fix(metrics): collect metrics for non-OPTIONS requests
Fix the inverted HTTP metrics skip condition, which caused the collector to ignore all requests except OPTIONS.

Apply the correction to both the main and Noise routers so regular HTTP traffic is included in http_requests_total and http_request_duration_seconds.

(cherry picked from commit 63ce8f2295)
2026-09-23 21:19:22 +02:00
Igor Bernstein 00babf430a docs: reword the tvOS warning to match the new step order
The steps now open the Tailscale app first to install the VPN
configuration, which contradicted the warning against opening the app
after installation. The actual constraint is not signing in before the
headscale URL is set.

(cherry picked from commit cbe30304dc)
2026-09-23 21:19:22 +02:00
Igor Bernstein e3dba1fdf9 templates: apply the tvOS setup reorder to the served /apple page
The instructions are duplicated between the docs and the HTML page
headscale serves at /apple. Reorder the tvOS steps there to match, so
the VPN configuration is installed before the alternate coordination
server URL is set.

(cherry picked from commit efe947507b)
2026-09-23 21:19:22 +02:00
igorbernstein b9b2beb781 Update AppleTV configuration steps
The instructions didn't work for tvOS 26.6 / Tailscale 1.102.2. When the `ALTERNATE COORDINATION SERVER URL` is set, the `Install VPN Configuration` breaks.
Clicking does nothing and the tvOS app logs showed `addUser: backendManager does not exist`.

Instead I first started tailscale and installed the vpn profile and then add the headscale url and then signed. Which seemed to have worked

(cherry picked from commit fad937c062)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 5aff68b5b9 mkdocs: bump version
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
(cherry picked from commit 565fd254d0)
v0.29.3
2026-07-29 14:35:31 +02:00
Kristoffer Dalby 235a57ec31 CHANGELOG: add 0.29.3
Updates #3385

(cherry picked from commit 8bb26c5967)
2026-07-29 14:35:31 +02:00
Kristoffer Dalby bdc3e996de hscontrol: prefer completed auth over expired ctx in followup wait
waitForFollowup selected on ctx.Done() and the verdict channel with equal
priority; when both were ready, select picked at random and discarded a
successful registration as a spurious 401 timeout. Check for a completed
verdict first, race the deadline only if none is ready.

Fixes #3385

(cherry picked from commit d28a6b111a)
2026-07-29 14:35:31 +02:00
Igor Serganov 5fb514e6e0 poll: do not cancel ephemeral GC until Connect succeeds
With node.ephemeral.inactivity_timeout set, ephemeral nodes are
usually deleted after they go offline, but under reconnect churn some
departed nodes stayed in the node list as disconnected indefinitely
until removed manually or until Headscale restarted.

Ephemeral cleanup is timer-based via EphemeralGarbageCollector, not a
periodic LastSeen scan. serveLongPoll cancelled any pending GC timer
at the very start of a long-poll attempt and only rescheduled on a
clean disconnect after Connect. If a reconnect cancelled the timer and
then failed before Connect (for example an UpdateNodeFromMapRequest
error), the deferred cleanup saw connectGen == 0 and returned without
Schedule. The node remained offline with no deletion timer and no
reconciler to recover it.

Cancel the ephemeral GC timer only after a successful Connect, so a
failed reconnect leaves an already-armed inactivity timer intact.
Successful reconnects still cancel GC once the node is online, and a
later disconnect reschedules as before.

Add TestFailedReconnectDoesNotCancelEphemeralGC to lock in the
ordering, plus IsScheduled and DeleteNodeFromStoreForTest helpers for
the test.

Fixes #3382

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit cfd845cb53)
2026-07-29 14:35:31 +02:00
Kristoffer Dalby 4a1e77359d policy,state: authorize reauth tags against the authenticating user
Re-authenticating a tagged node with --advertise-tags checked the tag-owned
node, not the authenticating user, so every tag was rejected.

Fixes #3374

(cherry picked from commit 6275e3a356)
2026-07-29 14:35:31 +02:00
Kristoffer Dalby 1fccdb18bd state: apply a new pre-auth key's tags on re-registration
Re-registering a tagged node with a different key discarded the new key's
tags and left a stale auth-key reference; retag on key change and persist it.

Fixes #3370

(cherry picked from commit fc16cc6905)
2026-07-29 14:35:31 +02:00
Kristoffer Dalby d202883200 state: do not expire tagged nodes on logout
Tagged nodes never expire, but handleLogout stamped a past expiry on them,
leaving them stuck expired and unable to re-authenticate.

Fixes #3371

(cherry picked from commit 1ed5693fa4)
2026-07-29 14:35:31 +02:00
Kristoffer Dalby 9609a0b87d hscontrol: gate /key on supported capability version
/key handed out the Noise public key for any v>=39, a floor unrelated
to the handshake's capver.MinSupportedCapabilityVersion. Reject below
the supported floor, matching /ts2021, and drop the stale constant.

Fixes #3380

(cherry picked from commit 5b6e1e17be)
2026-07-29 14:35:31 +02:00
Arpit Jain fba84ca232 auth: check machine key on the followup registration path
waitForFollowup returns nodeToRegisterResponse for a completed
registration without checking that the Noise session polling for the
result was started with the machine key that opened the registration.
That response carries the registering user's User and Login, so the auth
ID in the followup URL is the only thing protecting it.

handleRegister and handleLogout both call machineKeyMismatch before
handing back a node, so this is the one path of the three that does not.
The key is already available: HandleNodeFromAuthPath resolves the node
from the MachineKey cached in RegistrationData, so on the normal path the
node and the session agree and the check is a no-op.

The auth ID is 96 bits of randomness and is not guessable, so this is not
reachable by brute force. It is logged at info level when a registration
is created, which makes log access the realistic way to obtain one.

The existing followup_registration_success case built its node with
CreateNodeForTest, which picks a random machine key that no real
registration would produce. Set the registering machine key so the
fixture matches the production path.

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
(cherry picked from commit 0ce3356b89)
2026-07-29 14:35:31 +02:00
Florian Preinstorfer 089d6c4109 Explicitly select lunr as search provider
Keep the existing search provider instead (and the separator) option.

(cherry picked from commit 7a1ee34f71)
2026-07-29 14:35:31 +02:00
Kristoffer Dalby 12928418b8 build: bump Go toolchain to 1.26.5
Tailscale HEAD go.mod now requires go >= 1.26.5; build images pinned
1.26.4. Bump go.mod, the four Go Dockerfiles, and the nixpkgs pin
(staging-next-26.05 ships go_1_26 1.26.5; unstable still lags).

(cherry picked from commit 8eec2dbdc2)
2026-07-29 14:35:31 +02:00
Kristoffer Dalby 8eea89488c CHANGELOG: drop unreleased 0.30.0 stub
Release-branch only; the stub tracks unreleased main work.
v0.29.2
2026-07-01 14:47:39 +00:00
Florian Preinstorfer f885d87827 Fix invalid ip syntax
The "ip" field needs to be a list.

(cherry picked from commit a066a06bef)
2026-07-01 14:33:43 +00:00
Kristoffer Dalby 3ac33cf1d5 CHANGELOG: shorten 0.29.2 invalid-name entry, set date
Updates #3346

(cherry picked from commit a84945f134)
2026-07-01 14:33:43 +00:00
Kristoffer Dalby f708c5b010 CHANGELOG: note /ts2021 WebSocket GET fix
Updates #3357

(cherry picked from commit fef80f3eb0)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby 8f4e69d2a6 integration: add TS2021 WebSocket tests to CI matrix
Generated by gh-action-integration-generator.

Updates #3357

(cherry picked from commit 01ef350d5f)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby e7851ef881 integration: test /ts2021 WebSocket GET with a real WASM client
A raw coder/websocket dial and the real tailscale.com js/wasm control client under Node, both against headscale alongside normal Tailscale clients.

Updates #3357

(cherry picked from commit f4fba32dc6)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby 1ec7b7fb72 hscontrol: register /ts2021 for WebSocket GET
The chi migration dropped GET; JS/WASM control clients open /ts2021 as a WebSocket GET and were rejected with 405 before reaching NoiseUpgradeHandler.

Fixes #3357

(cherry picked from commit fc6f216b61)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby 735742e3ee CHANGELOG: note 0.29.2 invalid-name map fix
Updates #3346

(cherry picked from commit de9db9c811)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby fd154fdb66 state: log nodes with map-breaking data at startup
Scan a node-health check registry at boot and log each node whose name
can't form a valid FQDN, with the rename fix. Log-only, no mutation.

Updates #3346

(cherry picked from commit 4946d1c88d)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby 9c9206a92b state: reject renames whose FQDN exceeds the hostname limit
A valid label can still overflow 255 chars under a long base_domain; gate
RenameNode with the new types.ValidateGivenName.

Updates #3346

(cherry picked from commit c497612c99)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby 5fb76eb231 poll: return an HTTP error on long-poll setup failure
A bare return sent an empty 200 the client read as "unexpected EOF" and
retried forever; emit a real error instead.

Updates #3346

(cherry picked from commit 4e4512c4b7)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby ec67197368 mapper: skip peers with invalid names instead of failing the map
A peer whose GivenName fails GetFQDN aborted the whole map for every node
that could see it. Drop and log it; SSH policy errors degrade too.

Fixes #3346

(cherry picked from commit 08956d51a4)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby 9d1327458f mapper,policy: add reconnect-storm and lock-concurrency regression tests
TestInitialMapNotStarvedByReconnectStorm reproduces the #3346 stall;
TestPolicyManagerConcurrentReads guards the RLock cache access under -race.

Updates #3346

(cherry picked from commit d528686f14)
2026-07-01 14:33:03 +00:00
Kristoffer Dalby d4f2acf3ab policy: take RLock for reads so map generation runs concurrently
One exclusive mutex serialized every policy read, so a mass reconnect on
autogroup:self/via/relay policies stalled clients into "unexpected EOF"
retries. Per-node caches become xsync.Maps for lazy population under RLock.

Fixes #3346

(cherry picked from commit 6f317c7576)
2026-07-01 14:32:49 +00:00
Kristoffer Dalby 636f660caf db: preserve user_id on untagged nodes with tags='null'
A nil tags slice marshals to JSON `null`; the clear-tagged migration
read that as tagged and cleared user_id. Exclude it, and recover
already-detached nodes from their pre-auth key.

Fixes #3323
v0.29.1
2026-06-18 10:22:27 +00:00
Kristoffer Dalby b0c221f3a1 changelog: set 0.29 date
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
v0.29.0
2026-06-17 11:09:20 +02:00
Kristoffer Dalby 68a6d3cf17 db: drop ambiguous machine-key getter, match precisely in test helper
The First()-by-machine-key getter returned an undefined node when a machine
key mapped to several nodes. It was used only by RegisterNodeForTest; match on
(machine_key, user_id) there instead and remove the getter.

Updates #3312
v0.29.0-beta.4
2026-06-15 20:29:15 +02:00
Kristoffer Dalby 1689478485 state: return all nodes for a machine key, reject ambiguous ownership
Collapse the single-pick machine-key lookups onto GetNodesByMachineKeyAllUsers
so callers see every node sharing a machine key and reject the ambiguous or
impossible cases (tagged and user-owned coexistence; a tagged key with several
user-owned candidates) instead of mutating an arbitrarily-picked node.

Updates #3312
2026-06-15 20:29:15 +02:00
Kristoffer Dalby a1d3e98255 state: allow key expiry to be set on tagged nodes
Tagged nodes disable key expiry by default but can still have one set
explicitly, and changing tags leaves expiry unchanged, matching Tailscale.

Updates #3312
2026-06-15 20:29:15 +02:00
Kristoffer Dalby b83bf3f993 state: serialise registration per machine key
Concurrent registrations of one machine key each saw no existing node and
created their own, duplicating nodes and IPs. Hold a per-machine lock across
the find-then-create section.

Updates #3312
2026-06-15 20:29:15 +02:00