This interprets HiddenNetworkDevices per watch session and sends GUI visibility metadata for peers (without filtering them). We refresh the visibility snapshot when policy or peer membership changes.
Updates tailscale/corp#44104
Signed-off-by: kari-ts <kari@tailscale.com>
The immediate use case is feature/conn25 identifying the connector peer
node that responds to a DoH request.
Updates tailscale/corp#46169
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
Adds tests that only a 404 map response wraps ErrNodeRemoved, that the
node-removed warning and NeedsLogin state are cleared by a new netmap or
a profile switch, and that a node restarted after it was deleted shows
the warning and reaches NeedsLogin.
Updates #18700
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
When a node is deleted, control answers its map requests with a 404 and
the client keeps retrying in its current state, so watchers like
containerboot can't tell a deleted node from a lost connection. This
wraps that error in ErrNodeRemoved, and LocalBackend then moves to
NeedsLogin and sets a node-removed health warning, both cleared on the
next netmap or profile switch. It repurposes #18701 as a stopgap until a
broader fix, such as structured errors from control, exists.
Updates #18700
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Updates tailscale/corp#49775
Track authoritative NXDOMAINs for at least 60 seconds on macOS and iOS (where our short SOA ttls are not properly respected), and flush when a cached name becomes resolvable. Compile a no-op tracker elsewhere and avoid duplicate VPN reconfiguration on link changes.
On a fresh install the first netmap arrives before the first netcheck
report, so auto exit node resolution installed the blackhole
ipn.UnresolvedExitNodeID and nothing re-resolved it once the report
arrived. Refresh in setNetInfo while the exit node is still unresolved.
Fixes#18199
Signed-off-by: Brendan Creane <bcreane@gmail.com>
Since 7e609b258 (v1.102), quad-100 looks up MagicDNS names in
nodeBackend.nodeByName, which also holds short-name keys for in-process
dials. So a query for "foo." matched peer "foo.tail-scale.ts.net" and
was answered with its Tailscale IP. 1ec348784 fixed that for MagicDNS
off; this fixes it for MagicDNS on, too, matching pre-1.102 behavior.
The OS applies the search domain to short names before they reach
quad-100, so a single-label query is a root zone name, not a tailnet
name, and must be forwarded (e.g. to LAN mDNS/LLMNR or a global
nameserver). Short names still work for in-process dials via
NodeByName.
Updates #20789
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ic8199a96cf79178210c9a85e0cebfb46567a894a
Injected packets now arrive on a queue of their own rather than being
multiplexed onto queue 0. Wrapper.Read is deprecated. It still reads the
first kernel-backed queue, but from now on no single queue of a Wrapper
sees every packet.
Deletes the poll goroutine, the bufferConsumed token, the vectorOutbound
channel, tunVectorReadResult, a full-size slab, and the copy of every
accepted packet between the two slabs.
wireguard-go now runs an extra TUN reader goroutine, a net wash compared
to running our poll routine.
Measured on c8gn.2xlarge, Ubuntu 24.04, Kernel v6.14, tailscaled under
load from two peers, each running iperf3 -P8.
tx mean rx mean cores
main 7.00 8.84 3.49
branch 8.94 8.86 3.95
Bumps wireguard-go pin to tailscale/wireguard-go@e61b6b7811.
Updates tailscale/corp#37878
Change-Id: Ide0e8a9f345371b2747fad59dd2bf963c8af5b63
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
This resolves unscoped policy requests to the connected user's effective
policy instead of the default scope.
Tested by changing the device-scoped AlwaysOn policy at runtime and
running tailscale syspolicy reload. Cofnirmed tailscaled immediately
enforced the updated policy with tailscale down
Updates tailscale/corp#41463
Signed-off-by: kari <kari@tailscale.com>
This fetches the effective policy snapshot for the user scope instead of
using the default policy scope, ensuring that policy change
notifications reflect the correct user-scoped effective policy.
Updates tailscale/corp#41463t
Signed-off-by: kari <kari@tailscale.com>
LocalBackend named appc.AppConnector in its appConnector field, in the
field's constructor call, and in the AppConnector accessor, so even
builds with ts_omit_appconnectors linked the appc package and its
dependencies. The buildfeatures.HasAppConnectors checks let the linker
drop the code but could not remove the import.
Add a build-tag-gated type for the field: appcAppConnector is an alias
for appc.AppConnector by default, and in ts_omit_appconnectors builds it
is an empty struct with no-op versions of the methods local.go calls, so
the shared code still type checks. The constructor call moves behind a
newAppConnector helper in the same gated file, and the AppConnector
accessor keeps its *appc.AppConnector signature there, with a nil-
returning version in the omitted build.
This takes tailscale.com/appc out of depaware-min.txt and
depaware-minbox.txt. Add a DepChecker test to keep it out; it also sets
ts_omit_conn25, since conn25 uses appc as well.
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ia7e2d9c4f1b8e6a3d5c0f2b7e9a1c4d6f8b0e2a5
A JSON null for a service or for an endpoint target unmarshals as a nil
pointer, and loadConfigV0 dereferenced it without a check. This made
"tailscale serve set-config" panic. Return an error that names the
service or endpoint instead.
Fixes#21534
Signed-off-by: Brendan Creane <bcreane@gmail.com>
dnsConfigForNetmap called appc.AppDNSRoutes directly to synthesize the
conn25 split DNS routes from the self node's capability map, which was
the one place node_backend.go depended on the appc package and was
marked with a TODO to make it a hook.
Add ipnext.Hooks.ExtraDNSRoutes, which returns extension-supplied split
DNS routes. authReconfigLocked calls it and passes the result into
dnsConfigForNetmap, which adds the routes alongside the netmap's own
routes with the same UseWithExitNode handling as before.
The hook takes no arguments: the conn25 extension computes the routes
in its OnSelfChange handler and clears them on a switch to a different
node, and it already tracks the AppConnector.Advertise pref from
ProfileStateChange. Both of those hooks fire before the reconfig that
consults ExtraDNSRoutes, so the state is current. The conditions are
unchanged: no routes when this node itself advertises as an app
connector, and none without the experimental capability.
Updates tailscale/corp#37125
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I6c2e9f4b8a1d3e7f5c0b2a9d8e6f4c3b1a7d5e9f
The NL prefix is one of the last references to the "Network Lock" name
which we're cleaning up.
Updates tailscale/corp#37904
Change-Id: I751194e0b49447e97a65ed6975e3da680226ce85
Signed-off-by: Alex Chan <alexc@tailscale.com>
updates tailscale/corp#33007
When a selected exit node can't carry internet traffic due to a misconfiguration (wrong ID, node
deleted, routes removed, etc), 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 -- 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. If an admin fat-fingers the
exit node name in an IT policy, for example, it's easy to break every node with zero feedback.
This adds an exit-node-unavailable health warning covering every way the selection can fail to carry
traffic (short of reachability which is a separate concern), reported via exitnodehealth.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.
To test this, set a forced exit node policy with some random ip or node id. The warning has a 5 second
threshold. It should clear as soon as you change to a proper exit node.
Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
Several features with ts_omit build tags were still linked into
minimal builds because LocalBackend called into them unconditionally,
so the linker couldn't prove them unreachable. Guard those call sites
with their buildfeatures constants:
* app connectors: only subscribe to route update and store events
when app connectors are included; appc is their only publisher.
This also drops AdvertiseRoute and UnadvertiseRoute.
* client update: only subscribe to the tailnet default auto-update
event when auto-updates are supported, as that's all it affects.
* syspolicy: only register the policy change watch when system
policy support is included.
* advertise routes and exit nodes: only validate AdvertiseRoutes
when one of the two features is included.
* netstack: only netstack consults the intercepted TCP ports, so
don't build the port matching func without it.
* ssh: don't check SSH prefs, report the "SSH on but unusable"
health message, or intercept port 22 without SSH support. The
RunSSH pref is still accepted without effect, as before; port 22
was previously intercepted by netstack builds without SSH.
* web client: don't track whether the web client should run when
it's not included.
* tailnet lock: peers marked UnsignedPeerAPIOnly are only special
in that tailnet lock skips their signature checks, and the packet
filter check guards that exemption. Without tailnet lock, all
peers from control are trusted anyway, so skip the check.
This reduces the size of a build_dist.sh --min tailscaled binary on
linux/amd64 by 65,536 bytes (0.5%), about 28 KB of it in ipnlocal and
most of the rest in eventbus and slices generic instantiations for the
events no longer subscribed to.
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I8f4d4c0779e84f46f3b076d0fb35514c69a52d8f
Peer-change watchers upsert PeersChanged, so peers missing from a full
netmap were never removed from their view.
Updates #15660
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
A peer removal sent with any MapResponse field that forces a full netmap
rebuild never reaches IPN bus watchers; these tests fail.
Updates #15660
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
FreeBSD is a weak-host stack like Linux, so a LAN-adjacent machine can
send a SYN for a node's Tailscale IP to the node's NIC and get the
kernel's SYN-ACK from the peerapi port, confirming which tailnet
identity that MAC address belongs to. Linux closes this with
SO_BINDTODEVICE and macOS/iOS with IP_BOUND_IF, but FreeBSD has no
per-socket interface bind (only IP_RECVIF and SO_SETFIB).
Instead, treat FreeBSD like Android: when netstack is present, don't
create a kernel-level peerapi listener at all and use the fake listener,
since netstack already intercepts peerapi connections from peers in
userspace and the kernel socket only ever served the local host. When
netstack is compiled out (ts_omit_netstack), the kernel listener is the
only way to serve peers, so keep it and log that it must be restricted
with pf.
Along the way, tailscaled's own pf source NAT rule on FreeBSD turned out
to rewrite the source of such a SYN-ACK to the LAN IP and a random port,
so a plain connect() from the LAN never completes. The SYN-ACK still
leaks, though, so the natlab test now watches for SYN-ACKs on the
attacker's NIC with tcpdump instead of checking for a completed
handshake, and gains a FreeBSD variant. With the fix disabled, that
variant fails on the leaked SYN-ACK from the peerapi port.
Fixes#21419
Updates tailscale/corp#48248
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I0d2de4ebc9bae28450d500bd7b6ddbfa70f9052f
The debug-log endpoint was gated on PermitRead, which any local user connecting to the world-writable tailscaled socket passes. Although forged log entries are always possible, we should limit the ability for local users to influence that flow with respects to a given node.
This change requires PermitWrite for debug-log, matching the trust level of every other mutating debug endpoint. Also add a buildfeatures.HasDebug guard to debug-dial-types for parity with serveDebug.
Fixestailscale/corp#48143
Change-Id: I0c0044b6b44fe7cbfb6734ac18bca3dc36eaffbf
Signed-off-by: Mike Jensen <mikej@tailscale.com>
On Linux, serve's kernel listeners are now bound to the tunnel interface, which
stops LAN-adjacent hosts completing a handshake with the listener (see #21420).
That bind drops packets that arrive on another interface addressed to the node's
Tailscale IP, which breaks the Kubernetes operator's
`tailscale.com/experimental-forward-cluster-traffic-via-ingress` ingress feature.
Add `TS_SERVE_ALLOW_ALL_INTERFACES`, which makes serve's listener skip the
interface bind so those packets are answered again. It applies only to serve
listeners, not the web client listener, which shares the same code but stays
bound. It only affects serve; the peerapi listener stays bound too. This is a
targeted opt-out for the operator to set on the affected proxies. Enabling it
re-exposes the serve listener to the local network.
Updates tailscale/corp#48248
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
The store path was hardcoded to %ProgramData%\Tailscale\audit-log.json on
Windows, so a tailscaled run by a regular user with its own --statedir
logged "[unexpected] failed to create audit log store ... Access is denied"
on every profile switch. Use the backend's TailscaleVarRoot when it is
known, falling back to the platform default otherwise. For the Windows
service the state directory is %ProgramData%\Tailscale, so its path is
unchanged; an explicit SetStoreFilePath still wins.
Updates #2791
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I5d7f9b1c3e5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e
Registering the session state callback enumerates the existing desktop
sessions, which fails for a tailscaled run by an unprivileged user
(WTSEnumerateSessions returns "No more data is available"). That was
reported as "init failed", which reads like a bug. Such a tailscaled has
no other users' sessions to track, so report it as skipping the extension,
the same way an unavailable session manager already is.
Updates #2791
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I7f9b1d3e5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b
The portlist extension publishes a PortlistServices event, but the
LocalBackend subscriber accepted the underlying slice type instead. The
event bus matches event types exactly, so endpoint updates were dropped
before reaching Hostinfo.Services.
Add a regression test covering the event-to-Hostinfo path.
Fixes#20192
Signed-off-by: YewFence <hello@yewfence.dev>
Linux is a weak-host stack, so a LAN-adjacent machine can complete a
TCP handshake with a node's peerapi listener by sending a packet to
the node's Tailscale IP, with no credentials and no tailnet
membership.
macOS and iOS already bind the listener to the tunnel interface, and
Windows is protected by its strong host model, so Linux tun mode was
the only platform that leaked.
Bind the Linux listener to the tunnel interface as well, so the
kernel only answers connections that arrive from the tunnel or from
the local host. A natlab VM test verifies that a same-LAN machine can
no longer complete the handshake, while local and peer peerapi keep
working.
FreeBSD has the same weak-host exposure but no per-socket equivalent,
so handling it there with pf is a TODO (#21419).
Updates tailscale/corp#48248
Reported-By: Samuel Keeley (@keeleysam)
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I5f8501b0938c9f7aa39c4c12ebddd988c72e89bf
If the network comes up during the few milliseconds NewLocalBackend
takes, tailscaled starts with its control client paused and never
unpauses: the interface state snapshot was taken before the netmon
subscription (kept late since #17252), so a change published in between
reached nobody. #21261 hit it on fast-booting NixOS microVMs, and it was
behind the natlab TestEasyEasy CI flake, where a gokrazy node's DHCP
lease landed in that window and "tailscale up" hung at "awaiting
unpause".
Re-read netmon's state after subscribing rather than subscribing before
the snapshot (as #21281 proposed), which would bring back the #17252
data race and let a fresh delta be overwritten by the stale snapshot.
netmon.New and the userspace engine had the same shape of gap between
their snapshot and their subscription; close those too.
Under CPU pressure the hang reproduced in 1 of 22 TestEasyEasy runs
before and 0 of 120 after.
Thanks to @c-vigo for the detailed diagnosis in #21261 and to @zzz-yu
for pointing out this problem and proposing a fix in #21281!
Fixes#21261
Updates #14902
Updates #19126
Updates #deflake
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ifa21f8055a85321a5afda7800140fc5045c6ce08
This method has been deprecated for four months; replace all uses with
the Peers or NoPeers equivalents.
Updates #12542
Change-Id: I7b5800d0d92775839dbfb6021751a5dfacc53d2f
Signed-off-by: Alex Chan <alexc@tailscale.com>
Add Node.StableTailnetID for control to send the current tailnet's
stable ID on the self node. Expose it as CurrentTailnet.StableID in
LocalAPI status and `tailscale status --json`, and display it in
`tailscale whoami`.
Bump CurrentCapabilityVersion to 148.
Updates #14375
RELNOTE: Show the current tailnet's stable ID in status JSON and whoami.
Change-Id: I0525ff735de8113c8d124045a94e00c19a5a02e2
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
The profile switch log lines passed cp.UserProfile().LoginName without
calling it, so they printed "%!q(func() string=0x7ff7141cd060)" in place
of the login name.
Updates #cleanup
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I4e6a8c0b2d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c
dnsQueryForName builds the query for GET /dns-query?q=<name>, the peerAPI's
interactive debug mode. The name comes from the peer's query string and the
only thing done to it is appending a trailing dot, but the query is built
with dnsmessage.MustNewName, which panics as soon as the name is longer than
255 bytes.
The panic happens before the query reaches the resolver, so the nameAllowed
filter never gets a say and only sourceAllowed has to be true: any of the
user's own untagged devices, and any peer an extension hook lets through,
such as a client using this node as an exit node with DNS proxying allowed.
http.Server recovers it, so tailscaled survives, but the peer's connection is
dropped instead of answered and the node logs a panic trace per request.
Just under the limit the name was mishandled too: a 255-byte name is accepted
by NewName but rejected by Question when it packs the name, and that error
was dropped, so Finish returned a well-formed query with no question in it
which was then handed to the resolver.
Have dnsQueryForName use NewName, check the error from Question, and return
the error from Finish. handleDNSQuery turns a name it cannot build a query
for into the 400 it already uses for the other malformed-request cases.
Fixes#21307
Change-Id: I7c1a4b7e4f9a1d2c3b5e8f0a6d4c2b9e1f3a7d50
Signed-off-by: leoca <leo.camus23@gmail.com>
The ts_omit_<name> build tags omit a feature at build time; there has
been no way to do the same at runtime. Some users (either proactively
or in response to a security announcement) might like a way to disable
a feature that's linked-in in their binaries that they're not using.
Then a mitigation announcement can say "set this env var" without
asking users to rebuild or wait for a new release.
This adds env var TS_DISABLE_FEATURE, a comma-separated list of
feature names to disable, and the listed set is reported by the
debug-optional-features LocalAPI endpoint next to the registered set.
The legacy per-feature knobs such as TS_DISABLE_SSH_SERVER and
TS_DISABLE_TAILDROP keep working independently.
A disabled feature behaves as if it had not been linked: it is absent
from feature.IsRegistered, its hooks are unset, and its extensions and
handlers are not registered. Three pieces make that happen:
* feature.Register now returns bool, false when disabled, and
feature packages gate their registration init on it. It was added
to the feature packages that never called it (including taildrop
and ssh), which also completes the picture reported by
debug-optional-features. taildrop, routecheck, favorites, and
serviceclientprefs had registration split across several inits and
now register from one gated init.
* ipnext.RegisterExtension ignores a disabled feature's extension.
* feature.Hook.Set and feature.Hooks.Add walk the call stack and
silently skip when the calling package under feature/<name> is
disabled. This covers sub-packages such as
feature/captiveportal/netcheckhook, which cannot call Register
themselves without colliding with their parent, and future
packages whose authors forget the gate.
ssh/tailssh's registrations moved from its inits into tailssh.Register,
called from feature/ssh's gated init. The aws and kube state stores and
syspolicy's Windows store registration are gated too.
feature/register_disable_test.go runs this test binary as a child
process (it links condregister, as tailscaled does) with
TS_DISABLE_FEATURE set to every registered feature at once, and fails
if any of them register anyway, so a feature that ignores the variable
cannot land.
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I720af6ccab844ae060a9dfd1539fee577fd483e3
The map response reader now caps a single message at 256 MiB on the wire
and 1 GiB after zstd decompression. The server-chosen uint32 size prefix
previously let a malicious control server make us allocate up to 4 GiB
before reading any body bytes, and the decoded size was unbounded, so a
small zstd frame could expand into gigabytes of JSON. A 16 MB cap has
been hit by real production traffic before, so both limits sit far above
plausible legitimate sizes. The size-prefixed read moved into a
readMapResponseMessage helper, and the newer control/tsp path already
enforced both kinds of bounds; this brings the long-poll path it
replaces in line, with more generous limits for large tailnets.
ts2021.Client.Do additionally caps every noise response body with
httpbody.LimitSize, so a malicious or buggy control server can't make us
buffer an unbounded response. Client.Do shadows the embedded
http.Client's Do method, so register, set-dns, set-device-attr,
audit-log, all DoNoiseRequest consumers (webclient, tailnet lock, SSH
actions, id-token, feature queries), and the debug CLI get the cap
without per-call-site changes, and future noise endpoints get it for
free.
The cap lives in the new util/httpbody package so other HTTP clients can
adopt the same convention: LimitSize looks the size limit up from
res.Request's context (a Response knows the Request that produced it),
falling back to DefaultMaxSize, 1 MiB, when the context carries no
override. It is like io.LimitReader except that reads past the limit
fail with an error wrapping httpbody.ErrTooLarge instead of silently
truncating, and a body of at most the limit, including one of exactly
the limit, reads back without error: the wrapper probes for EOF once the
limit is exhausted to tell an exactly-at-limit body from an oversize
one. The per-request override, httpbody.WithMaxSize, is a context key,
so transports pick it up with no API changes; LimitSizeTo applies an
explicit limit ignoring any override. Repeated LimitSize or LimitSizeTo
calls replace the previous limit rather than compounding it, so a later
call can raise or remove the limit an earlier one set.
Responses that stream an unbounded number of individually bounded
messages disable the cap with httpbody.WithMaxSize(ctx, 0): the
/machine/map long-poll and control/tsp's map session, whose messages are
already capped per-message (by readMapResponseMessage and decodeMsg, and
by tsp's framedReader and boundedReader). Their non-200 error bodies are
not message streams, so those are capped with LimitSizeTo instead.
The tailnet lock /tka/init/begin, /tka/sync/offer and /tka/affected-sigs
responses can carry per-node key signatures or missing AUMs, which at
100,000 peers reach tens of MB, so they raise the cap to 512 MiB. The
per-response io.LimitedReader decoders that silently truncated those
responses at 1 or 10 MiB are removed: the transport cap is now the
single enforcement point, and it reports oversize bodies instead of
truncating them.
The /key fetch over plain TLS switches from io.LimitReader to
httpbody.LimitSizeTo, so an oversized response reports the problem
instead of producing a confusing truncated-JSON error.
Thanks to Ben Carman for the report!
Updates tailscale/corp#48187
Reported-by: Ben Carman
Change-Id: Ibf95e1ab9e4f0d7ef8866e8c26e62ed2a514455a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
When a controlclient receives non-keepalive netmap, it updates the controlknobs
based on the capability map of the self node. We added a new knob based on
tailcfg.NodeAttrCacheNetworkMaps in be2f554dd3, and this ensures we correctly
propagate the attribute to the knob when coming up from a cache as well.
Updates #12639
Updates tailscale/projects#27
Change-Id: I40d34053c9743757f9fddca715379fc63a9ae6a0
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
Nothing reads ipn.Notify.NetMap anymore. The previous commit removed
its runtime (non-initial) emission, and every first-party client is
also off the initial one: the Win32 and WinUI GUIs and the Apple
bridge subscribe with InitialStatus or InitialState plus peer deltas,
Android no longer uses it, and the remaining in-tree subscribers that set
NotifyInitialNetMap (sniproxy and the kube helpers) only did so to get
the initial Notify.SelfChange and discarded the netmap that tailscaled
built, encoded, and shipped for them.
Delete the Notify.NetMap field and the NotifyInitialNetMap bit. The
bit value stays reserved under the name ObsoleteNotifyInitialNetMap and
ValidateNotifyWatchOpt rejects subscriptions that set it, like the
NotifyRateLimit bit removed in the previous commit. NotifyNoNetMap
remains accepted as a no-op because shipping GUIs still set it.
The blessed way to seed a watcher's view is NotifyInitialStatus, but
it unconditionally built O(peers) status entries, which is exactly the
waste this series is deleting for watchers that only care about the
self node. Size the initial status to the subscription instead:
Status.Peer is only populated when the watcher also set
NotifyPeerChanges or NotifyPeerPatches, since only peer-delta
subscribers need a peer baseline to apply deltas to. That matches its
existing first-party users (containerboot and the WinUI GUI both pair
InitialStatus with peer bits).
Migrate sniproxy and the kube helpers to NotifyInitialStatus: they
seed from InitialStatus.Self and react to the (ungated) runtime
Notify.SelfChange messages after that, so their initial message now
carries one PeerStatus instead of a full netmap.
Also drop doc comment references to LocalClient.NetMap, a method that
does not exist; on-demand fetches go through other LocalAPI methods
such as LocalClient.Status.
Updates #12542
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I242992a744c0ffd0be6f27e8c735aa69d5b23b5e
The WinUI GUI was the last consumer of the legacy Notify.NetMap field
on runtime (non-initial) IPN bus messages. It was converted to peer
deltas in tailscale/corp#47958, and the GUI and tailscaled ship
together as a unit on Windows, so tailscaled no longer needs to build
and emit full netmaps on the bus on any platform. Remove
goosGetsLegacyNetmapNotify and the code it gated. The initial netmap
(NotifyInitialNetMap) is unaffected; NotifyNoNetMap is now a no-op but
remains accepted for compatibility.
With no runtime netmaps left to rate limit, the NotifyRateLimit
subscription bit is meaningless, so remove the rateLimitingBusSender
machinery too. The bit value stays reserved under the name
ObsoleteNotifyRateLimit and ValidateNotifyWatchOpt now rejects
subscriptions that set it; previously it was only rejected in
combination with new-style delta bits.
Updates #12542
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ic6184ea549726de9d9d56f133aae21e53f62cbc5
Two fixes for DNS names from a malicious control server, from Ben
Carman's security review:
dnsname.ToFQDN now rejects names containing whitespace or control
characters. ToFQDN previously checked label lengths only, so a search
domain like "evil.com\nnameserver 6.6.6.6" passed validation and was
written verbatim into /etc/resolv.conf by the direct DNS manager, where
the injected line became a real nameserver. The same shape existed on
Windows, where a CRLF in an ExtraRecords name injected lines into the
hosts file. This is deliberately not RFC 1123 hostname validation (see
the existing comment about issue 2024): labels may still contain any
byte that isn't whitespace or a control character.
dnsConfigForNetmap now drops search domains and split-DNS route suffixes
that ToFQDN rejects. It previously logged the error but appended the
zero FQDN anyway, and FQDN.WithoutTrailingDot panics on the empty FQDN
when OS DNS config is written, so one over-long domain from control
crashed tailscaled on every netmap until control sent a valid one.
Thanks to Ben Carman for the report!
Updates tailscale/corp#48187
Reported-by: Ben Carman
Change-Id: I8de40cafacfb66e2b863f097794c42f3ca5c2da9
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
State keys written by dev-set-state-store are otherwise gated by their own handlers, for example serve-config requires a local admin before storing a _serve/<profile-id> key.
Writing such a key directly through dev-set-state-store skipped that check. Require`IsLocalAdmin` in the handler, the same check serve-config performs.
Credit to @johnnymiranda for reporting this issue.
Fixestailscale/corp#47886
Change-Id: Ie82f961b016f78895793d801929a4fa11ebf7fd8
Signed-off-by: Mike Jensen <mikej@tailscale.com>
Some customers rely on the ability for 4via6 subnet routers to expose
loopback and/or link-local addresses. In situations where the
administrator has deemed these to be safe, accept a list of allowed
addresses in an environment variable named TS_4VIA6_ALLOW_LOCAL.
Fixes#21019
Change-Id: I7b3fe863696ee67aa352c4bd11edcbc8836920e3
Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
isAddressValid rejected all non-masquerade destination addresses
whenever any masquerade address was set for the peer. With a v6-only
masquerade pair, the peer's client still dials the v4 (native, not
masqueraded) peerapi URL, so every fresh peerapi connection got a 403.
The bug was masked by HTTP keep-alive: peerNode is snapshotted per
connection, so connections established before the masquerade was
configured kept validating against the old node view. It surfaced when
a newer tailscale/go toolchain started closing idle netstack
connections, forcing fresh peerapi connections and failing the
TestNATPing v6=true NAT subtests with 403s.
A masquerade address for one family says nothing about the other
family, so require a masquerade address match only for the address
family it applies to, and fall through to the self-addresses check for
the other family.
Fixes#21194
Change-Id: Idd2ed82b81e805b47b6aa6af4b5938984c6fa208
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Instead of opening all DNS queries with conn25, only permit clients to
access domains of the apps that they have permission to use. Previous
changes ensure that clients report which app they are making a DNS query
for, simplifying the check.
Updates tailscale/corp#40076
Updates tailscale/corp#47585
Change-Id: I40fee220b3f190b2a0db9b3e6cc79f323ef16b73
Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
Refresh the expiry timer netmap from the live peer state before
reinstalling it, preventing delta updates from being rolled back.
Updates tailscale/corp#47686
Change-Id: Idc738acea82bab5a8ba772084a41e55b38a06bcc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
When applying netmap deltas, nodeBackend evicted its index entries
(nodeByAddr, nodeByKey, nodeByWGString, nodeByStableID, nodeByName)
derived from a node's last-known value without checking that the entry
still pointed at that node. Control can reassign a churning ephemeral
peer's Tailscale IP (or MagicDNS name) to a newer peer and deliver the
new peer's upsert before the old peer's removal, either in an earlier
MapResponse or reordered within one batch by the NodeID sort in
netmap.MutationsFromMapResponse. The removal then wiped the new
owner's entry.
The peers map itself stayed correct in every ordering, so WireGuard
kept the peer and handshakes succeeded, but WhoIs lookups by IP failed
until the next full netmap rebuilt the indexes. On App Connectors that
surfaced as "peerapi: unknown peer" and refused DNS connections from
affected clients, with a toggle of Tailscale (forcing a full netmap)
as the only recovery.
Make every index eviction conditional on the entry still mapping to
the node being removed or replaced, and add a regression test covering
the cross-batch, intra-batch, and upsert-eviction orderings.
Also add an end-to-end test in tstest/integration showing that a
MapResponse reusing an address is handled incrementally rather than as
a full netmap, and that LocalBackend.WhoIs still resolves the reused
address afterwards, which is the lookup PeerAPI makes before it
accepts a connection.
Updates tailscale/corp#47435
Co-authored-by: Brendan Creane <bcreane@gmail.com>
Signed-off-by: Brendan Creane <bcreane@gmail.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I3f8c2a9d41e07b6a5cd2e94f78b013c6ad2f5e91
Replace the allowed-IP-only peer callback result with wgcfg.PeerConfig.
It carries allowed IPs and an optional pre-shared key through lazy peer
creation and active peer synchronization.
Update wireguard-go for the new peer PSK APIs.
Updates tailscale/tailcat#84
Change-Id: Iacd9d2c74b0b64d690f3cbdf93918686ac6076d7
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Previously, the new disco keys entering the client via TSMP was routed
into controlClient to allow for deduplication and filtering of disco
keys and avoid control overwriting an active TSMP learned key with a
stale key.
A system supporting multiple disco keys was introduced to allow these
keys living side by side, along side a system for selecting an active
egress key with a bias towards keys learned via TSMP. On ingress any
known key is accepted.
This PR makes the switch to key routing, by sending new TSMP learned
disco keys directly into the userspace engine and in turn magicsock,
removing the need for a full layer of deduplication in the
controlClient.
Additionally, the system that previously fully reconfigured clients on
disco key updates coming from control is now using an optimistic
handshake througha newly introduced wireguard-go method,
ScheduleHandshakeOnUserSend implemented in:
https://github.com/tailscale/wireguard-go/pull/81
What this PR does not do is revert the mapSession back to being single
writer. Bringing the mapSession back to this state is desired, however
to ensure the plumbing is done right and easier to reason about, that
will be done in a separate PR.
Updates #20590
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Use net.JoinHostPort when expanding proxy targets so IPv6 loopback addresses retain the required brackets. Accept ::1 as an HTTP and TCP destination in both current and legacy serve implementations.
Fixes#8702
Signed-off-by: James Tucker <jftucker@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>