Commit Graph

8369 Commits (02cafbe1cadfcd82d22beb9138d4673169fcdc82)
 

Author SHA1 Message Date
Kristoffer Dalby e93c160a39 nix: update nix and use go 1.23
Updates #12912

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
3 months ago
Nick Khyl b48c8db69c ipn/ipnlocal: set WantRunning upon an interactive login, but not during a seamless renewal or a profile switch
The LocalBackend's state machine starts in NoState and soon transitions to NeedsLogin if there's no auto-start profile,
with the profileManager starting with a new empty profile. Notably, entering the NeedsLogin state blocks engine updates.
We expect the user to transition out of this state by logging in interactively, and we set WantRunning to true when
controlclient enters the StateAuthenticated state.

While our intention is correct, and completing an interactive login should set WantRunning to true, our assumption
that logging into the current Tailscale profile is the only way to transition out of the NeedsLogin state is not accurate.
Another common transition path includes an explicit profile switch (via LocalBackend.SwitchProfile) or an implicit switch
when a Windows user connects to the backend. This results in a bug where WantRunning is set to true even when it was
previously set to false, and the user expressed no intention of changing it.

A similar issue occurs when switching from (sic) a Tailnet that has seamlessRenewalEnabled, regardless of the current state
of the LocalBackend's state machine, and also results in unexpectedly set WantRunning. While this behavior is generally
undesired, it is also incorrect that it depends on the control knobs of the Tailnet we're switching from rather than
the Tailnet we're switching to. However, this issue needs to be addressed separately.

This PR updates LocalBackend.SetControlClientStatus to only set WantRunning to true in response to an interactive login
as indicated by a non-empty authURL.

Fixes #6668
Fixes #11280
Updates #12756

Signed-off-by: Nick Khyl <nickk@tailscale.com>
3 months ago
Brad Fitzpatrick 82c2c5c597 tstest/natlab/vnet: add more tests
This adds tests for DNS requests, and ignoring IPv6 packets on v4-only
networks.

No behavior changes. But some things are pulled out into functions.

And the mkPacket helpers previously just for tests are moved into
non-test code to be used elsewhere to reduce duplication, doing the
checksum stuff automatically.

Updates #13038

Change-Id: I4dd0b73c75b2b9567b4be3f05a2792999d83f6a3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Jordan Whited d21ebc28af
wgengine/netstack: implement netstack loopback (#13301)
When the TS_DEBUG_NETSTACK_LOOPBACK_PORT environment variable is set,
netstack will loop back (dnat to addressFamilyLoopback:loopbackPort)
TCP & UDP flows originally destined to localServicesIP:loopbackPort.
localServicesIP is quad-100 or the IPv6 equivalent.

Updates tailscale/corp#22713

Signed-off-by: Jordan Whited <jordan@tailscale.com>
3 months ago
Nick Khyl 80b2b45d60 ipn/ipnlocal: refactor and cleanup profileManager
In preparation for multi-user and unattended mode improvements, we are
refactoring and cleaning up `ipn/ipnlocal.profileManager`. The concept of the
"current user", which is only relevant on Windows, is being deprecated and will
soon be removed to allow more than one Windows user to connect and utilize
`LocalBackend` according to that user's access rights to the device and specific
Tailscale profiles.

We plan to pass the user's identity down to the `profileManager`, where it can
be used to determine the user's access rights to a given `LoginProfile`. While
the new permission model in `ipnauth` requires more work and is currently
blocked pending PR reviews, we are updating the `profileManager` to reduce its
reliance on the concept of a single OS user being connected to the backend at
the same time.

We extract the switching to the default Tailscale profile, which may also
trigger legacy profile migration, from `profileManager.SetCurrentUserID`. This
introduces `profileManager.DefaultUserProfileID`, which returns the default
profile ID for the current user, and `profileManager.SwitchToDefaultProfile`,
which is essentially a shorthand for `pm.SwitchProfile(pm.DefaultUserProfileID())`.
Both methods will eventually be updated to accept the user's identity and
utilize that user's default profile.

We make access checks more explicit by introducing the `profileManager.checkProfileAccess`
method. The current implementation continues to use `profileManager.currentUserID`
and `LoginProfile.LocalUserID` to determine whether access to a given profile
should be granted. This will be updated to utilize the `ipnauth` package and the
new permissions model once it's ready. We also expand access checks to be used
more widely in the `profileManager`, not just when switching or listing
profiles. This includes access checks in methods like `SetPrefs` and, most notably,
`DeleteProfile` and `DeleteAllProfiles`, preventing unprivileged Windows users
from deleting Tailscale profiles owned by other users on the same device,
including profiles owned by local admins.

We extract `profileManager.ProfilePrefs` and `profileManager.SetProfilePrefs`
methods that can be used to get and set preferences of a given `LoginProfile` if
`profileManager.checkProfileAccess` permits access to it.

We also update `profileManager.setUnattendedModeAsConfigured` to always enable
unattended mode on Windows if `Prefs.ForceDaemon` is true in the current
`LoginProfile`, even if `profileManager.currentUserID` is `""`. This facilitates
enabling unattended mode via `tailscale up --unattended` even if
`tailscale-ipn.exe` is not running, such as when a Group Policy or MDM-deployed
script runs at boot time, or when Tailscale is used on a Server Code or otherwise
headless Windows environments. See #12239, #2137, #3186 and
https://github.com/tailscale/tailscale/pull/6255#issuecomment-2016623838 for
details.

Fixes #12239
Updates tailscale/corp#18342
Updates #3186
Updates #2137

Signed-off-by: Nick Khyl <nickk@tailscale.com>
3 months ago
Brad Fitzpatrick 73b3c8fc8c tstest/natlab/vnet: add IPv6 all-nodes support
This adds support for sending packets to 33:33:00:00:01 at IPv6
multicast address ff02::1 to send to all nodes.

Nothing in Tailscale depends on this (yet?), but it makes debugging in
VMs behind natlab easier (e.g. you can ping all nodes), and other
things might depend on this in the future.

Mostly I'm trying to flesh out the IPv6 support in natlab now that we
can write vnet tests.

Updates #13038

Change-Id: If590031fcf075690ca35c7b230a38c3e72e621eb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Nick Khyl 961ee321e8 ipn/{ipnauth,ipnlocal,ipnserver,localapi}: start baby step toward moving access checks from the localapi.Handler to the LocalBackend
Currently, we use PermitRead/PermitWrite/PermitCert permission flags to determine which operations are allowed for a LocalAPI client.
These checks are performed when localapi.Handler handles a request. Additionally, certain operations (e.g., changing the serve config)
requires the connected user to be a local admin. This approach is inherently racey and is subject to TOCTOU issues.
We consider it to be more critical on Windows environments, which are inherently multi-user, and therefore we prevent more than one
OS user from connecting and utilizing the LocalBackend at the same time. However, the same type of issues is also applicable to other
platforms when switching between profiles that have different OperatorUser values in ipn.Prefs.

We'd like to allow more than one Windows user to connect, but limit what they can see and do based on their access rights on the device
(e.g., an local admin or not) and to the currently active LoginProfile (e.g., owner/operator or not), while preventing TOCTOU issues on Windows
and other platforms. Therefore, we'd like to pass an actor from the LocalAPI to the LocalBackend to represent the user performing the operation.
The LocalBackend, or the profileManager down the line, will then check the actor's access rights to perform a given operation on the device
and against the current (and/or the target) profile.

This PR does not change the current permission model in any way, but it introduces the concept of an actor and includes some preparatory
work to pass it around. Temporarily, the ipnauth.Actor interface has methods like IsLocalSystem and IsLocalAdmin, which are only relevant
to the current permission model. It also lacks methods that will actually be used in the new model. We'll be adding these gradually in the next
PRs and removing the deprecated methods and the Permit* flags at the end of the transition.

Updates tailscale/corp#18342

Signed-off-by: Nick Khyl <nickk@tailscale.com>
3 months ago
Brad Fitzpatrick 8b23ba7d05 tstest/natlab/vnet: add qemu + Virtualization.framework protocol tests
To test how virtual machines connect to the natlab vnet code.

Updates #13038

Change-Id: Ia4fd4b0c1803580ee7d94cc9878d777ad4f24f82
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick ff1d0aa027 tstest/natlab/vnet: start adding tests
And refactor some of vnet.go for testability.

The only behavioral change (with a new test) is that ethernet
broadcasts no longer get sent back to the sender.

Updates #13038

Change-Id: Ic2e7e7d6d8805b7b7f2b5c52c2c5ba97101cef14
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Jordan Whited 31cdbd68b1
net/tstun: fix gvisor inbound GSO packet injection (#13283)
buffs[0] was not sized to hold pkt with GSO, resulting in a panic.

Updates tailscale/corp#22511

Signed-off-by: Jordan Whited <jordan@tailscale.com>
3 months ago
Kristoffer Dalby a2c42d3cd4 usermetric: add initial user-facing metrics
This commit adds a new usermetric package and wires
up metrics across the tailscale client.

Updates tailscale/corp#22075

Co-authored-by: Anton Tolchanov <anton@tailscale.com>
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
3 months ago
Kristoffer Dalby 06c31f4e91 tsweb/varz: remove pprof
Updates tailscale/corp#22075

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
3 months ago
Jordan Whited bfcb3562e6
wgengine/netstack: re-enable gVisor GSO on Linux (#13269)
This was previously disabled in 8e42510 due to missing GSO-awareness in
tstun, which was resolved in d097096.

Updates tailscale/corp#22511

Signed-off-by: Jordan Whited <jordan@tailscale.com>
3 months ago
Jordan Whited d097096ddc
net/tstun,wgengine/netstack: make inbound synthetic packet injection GSO-aware (#13266)
Updates tailscale/corp#22511

Signed-off-by: Jordan Whited <jordan@tailscale.com>
3 months ago
Jordan Whited 6d4973e1e0
wgengine/netstack: use types/logger.Logf instead of stdlib log.Printf (#13267)
Updates #cleanup

Signed-off-by: Jordan Whited <jordan@tailscale.com>
3 months ago
Brad Fitzpatrick f99f970dc1 tstest/natlab/vnet: rename some things for clarity
The bad naming (which had only been half updated with the IPv6
changes) tripped me up in the earlier change.

Updates #13038

Change-Id: I65ce07c167e8219d35b87e1f4bf61aab4cac31ff
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 0157000cab tstest/natlab: fix IPv6 tests, remove TODOs
The reason they weren't working was because the cmd/tta agent in the
guest was dialing out to the test and the vnet couldn't map its global
unicast IPv6 address to a node as it was just using a
map[netip.Addr]*node and blindly trusting the *node was
populated. Instead, it was nil, so the agent connection fetching
didn't work for its RoundTripper and the test could never drive the
node. That map worked for IPv4 but for IPv6 we need to use the method
that takes into account the node's IPv6 SLAAC address. Most call sites
had been converted but I'd missed that one.

Also clean up some debug, and prohibit nodes' link-local unicast
addresses from dialing 2000::/3 directly for now. We can allow that to
be configured opt-in later (some sort of IPv6 NAT mode. Whatever it's
called.) That mode was working on accident, but was confusing: Linux
would do source address selection from link local for the first few
seconds and then after SLAAC and DAD, switch to using the global
unicast source address. Be consistent for now and force it to use the
global unicast.

Updates #13038

Change-Id: I85e973aaa38b43c14611943ff45c7c825ee9200a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 9f7683e2a1 logpolicy: extend the gokrazy/natlab wait-for-network delay for IPv6
Really we need to fix logpolicy + bootstrapDNS to not be so aggressive,
but this is a quick workaround meanwhile.

Without this, tailscaled starts immediately while IPv6 DAD is
happening for a couple seconds and logpolicy freaks out without the
network available and starts spamming stderr about bootstrap DNS
options. But we see that regularly anyway from people whose wifi is
down. So we need to fix the general case. This is not that fix.

Updates #13038

Change-Id: Iba7e536d08e59d34abded1d279f88fdc9c46d94d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 2636a83d0e cmd/tta: pull out test driver dialing into a type, fix bugs
There were a few places it could get wedged (notably the dial without
a timeout).

And add a knob for verbose debug logs.

And keep two idle connections always.

Updates #13038

Change-Id: I952ad182d7111481d97a83c12aa2ff4bfdc55fe8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 6dd1af0d1e tstest/natlab: refactor HandleEthernetPacketForRouter a bit
Move all the UDP handling to its own func to remove a bunch of "if
isUDP" checks in a bunch of blocks.

Updates #13038

Change-Id: If71d71b49e57651d15bd307a2233c43751cc8639
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 3a8cfbc381 tstest/natlab: be more paranoid about IP versions from gvisor
I didn't actually see this, but added this while debugging something
and figured it'd be good to keep.

Updates #13038

Change-Id: I67934c8a329e0233f79c3b08516fd6bad6bfe22a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick e0bdd5d058 tstest/natlab: simplify a defer
Updates #13038

Change-Id: I4d38701491523c64c81767b0838010609e683a9f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Will Norris cccacff564 types/opt: add BoolFlag for setting Bool value as a flag
Updates tailscale/corp#22578

Signed-off-by: Will Norris <will@tailscale.com>
3 months ago
James Tucker 8af50fa97c ipn/ipnlocal: update routes on link change with ExitNodeAllowLANAccess
On a major link change the LAN routes may change, so on linkChange where
ChangeDelta.Major, we need to call authReconfig to ensure that new
routes are observed and applied.

Updates tailscale/corp#22574

Signed-off-by: James Tucker <james@tailscale.com>
3 months ago
Brad Fitzpatrick b78df4d48a tstest/natlab/vnet: add start of IPv6 support
Updates #13038

Change-Id: Ic3d095f167daf6c7129463e881b18f2e0d5693f5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Maisem Ali 31b5239a2f tstest/natlab/vnet: flush and sync pcap file after every packet
So that we can view the pcap as we debug interactively.

Updates #13038

Signed-off-by: Maisem Ali <maisem@tailscale.com>
3 months ago
Jordan Whited 978306565d
tstest/integration: change log.Fatal() to t.Fatal() (#13253)
Updates #cleanup

Signed-off-by: Jordan Whited <jordan@tailscale.com>
3 months ago
Jordan Whited 367bfa607c
tstest/integration: exercise TCP DNS queries against quad-100 (#13231)
Updates tailscale/corp#22511

Signed-off-by: Jordan Whited <jordan@tailscale.com>
3 months ago
Jordan Whited 641693d61c
ipn/ipnlocal: install IPv6 service addr route (#13252)
This is the equivalent of quad-100, but for IPv6. This is technically
already contained in the Tailscale IPv6 ULA prefix, but that is only
installed when remote peers are visible via control with contained
addrs. The service addr should always be reachable.

Updates #1152

Signed-off-by: Jordan Whited <jordan@tailscale.com>
3 months ago
Brad Fitzpatrick 475ab1fb67 cmd/vnet: omit log spam when backend status hasn't changed
Updates #13038

Change-Id: I9cc67cf18ba44ff66ba03cda486d5e111e395ce7
3 months ago
Brad Fitzpatrick e5fd36ad78 tstest/natlab: respect NATTable interface's invalid-means-drop everywhere
And sprinkle some more docs around.

Updates #13038

Change-Id: Ia2dcf567b68170481cc2094d64b085c6b94a778a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Nick Khyl 03acab2639 cmd/cloner, cmd/viewer, util/codegen: add support for aliases of cloneable types
We have several checked type assertions to *types.Named in both cmd/cloner and cmd/viewer.
As Go 1.23 updates the go/types package to produce Alias type nodes for type aliases,
these type assertions no longer work as expected unless the new behavior is disabled
with gotypesalias=0.

In this PR, we add codegen.NamedTypeOf(t types.Type), which functions like t.(*types.Named)
but also unrolls type aliases. We then use it in place of type assertions in the cmd/cloner and
cmd/viewer packages where appropriate.

We also update type switches to include *types.Alias alongside *types.Named in relevant cases,
remove *types.Struct cases when switching on types.Type.Underlying and update the tests
with more cases where type aliases can be used.

Updates #13224
Updates #12912

Signed-off-by: Nick Khyl <nickk@tailscale.com>
3 months ago
Nick Khyl a9dc6e07ad util/codegen, cmd/cloner, cmd/viewer: update codegen.LookupMethod to support alias type nodes
Go 1.23 updates the go/types package to produce Alias type nodes for type aliases, unless disabled with gotypesalias=0.
This new default behavior breaks codegen.LookupMethod, which uses checked type assertions to types.Named and
types.Interface, as only named types and interfaces have methods.

In this PR, we update codegen.LookupMethod to perform method lookup on the right-hand side of the alias declaration
and clearly switch on the supported type nodes types. We also improve support for various edge cases, such as when an alias
is used as a type parameter constraint, and add tests for the LookupMethod function.

Additionally, we update cmd/viewer/tests to include types with aliases used in type fields and generic type constraints.

Updates #13224
Updates #12912

Signed-off-by: Nick Khyl <nickk@tailscale.com>
3 months ago
Brad Fitzpatrick aa42ae9058 tstest/natlab: make a new virtualIP type in prep for IPv6 support
All the magic service names with virtual IPs will need IPv6 variants.

Pull this out in prep.

Updates #13038

Change-Id: I53b5eebd0679f9fa43dc0674805049258c83a0de
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 5a99940dfa tstest/natlab/vnet: explicitly ignore PCP and SSDP UDP queries
So we don't log about them when verbose logging is enabled.

Updates #13038

Change-Id: I925bc3a23e6c93d60dd4fb4bf6a4fdc5a326de95
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 3b70968c25 cmd/vnet: add --blend and --pcap flags
Updates #13038

Change-Id: Id16ea9eb94447a3d9651215f04b2525daf10b3eb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 3904e4d175 cmd/tta, tstest/natlab/vnet: remove unneeded port 124 log hack, add log buffer
The natlab Test Agent (tta) still had its old log streaming hack in
place where it dialed out to anything on TCP port 124 and those logs
were streamed to the host running the tests. But we'd since added gokrazy
syslog streaming support, which made that redundant.

So remove all the port 124 stuff. And then make sure we log to stderr
so gokrazy logs it to syslog.

Also, keep the first 1MB of logs in memory in tta too, exported via
localhost:8034/logs for interactive debugging. That was very useful
during debugging when I added IPv6 support. (which is coming in future
PRs)

Updates #13038

Change-Id: Ieed904a704410b9031d5fd5f014a73412348fa7f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Flakes Updater d862898fd3 go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
3 months ago
Brad Fitzpatrick b091264c0a cmd/systray: set ipn.NotifyNoPrivateKeys, permit non-operator use
Otherwise you get "Access denied: watch IPN bus access denied, must
set ipn.NotifyNoPrivateKeys when not running as admin/root or
operator".

This lets a non-operator at least start the app and see the status, even
if they can't change everything. (the web UI is unaffected by operator)

A future change can add a LocalAPI call to check permissions and guide
people through adding a user as an operator (perhaps the web client
can do that?)

Updates #1708

Change-Id: I699e035a251b4ebe14385102d5e7a2993424c4b7
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Will Norris 3c66ee3f57 cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.

This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.

This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.

Updates #1708

Signed-off-by: Will Norris <will@tailscale.com>
3 months ago
Flakes Updater 6280c44be1 go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
3 months ago
Jonathan Nobels 1191eb0e3d tstest/natlab: add unix address to writer for dgram mode
updates tailcale/corp#22371

For dgram mode, we need to store the write addresses of
the client socket(s) alongside the writer functions and
the write operation needs to use WriteToUnix.

Unix also has multiple clients writing to the same socket,
so the serve method is modified to handle packets from
multiple mac addresses.

Cleans up a bit of cruft from the initial tailmac tooling
commit.

Now all the macOS packets are belong to us.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
3 months ago
Percy Wegmann 743d296073 update to github.com/tailscale/netlink library that doesn't require vishvananda/netlink
Fixes #12298

Signed-off-by: Percy Wegmann <percy@tailscale.com>
3 months ago
Percy Wegmann d00d6d6dc2 go.mod: update to github.com/tailscale/netlink library that doesn't require vishvananda/netlink
After the upstream PR is merged, we can point directly at github.com/vishvananda/netlink
and retire github.com/tailscale/netlink.

See https://github.com/vishvananda/netlink/pull/1006

Updates #12298

Signed-off-by: Percy Wegmann <percy@tailscale.com>
3 months ago
Brad Fitzpatrick e54c81d1d0 types/views: add Slice.All iterator
And convert a few callers as an example, but nowhere near all.

Updates #12912

Change-Id: I5eaa12a29a6cd03b58d6f1072bd27bc0467852f2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Flakes Updater aedfb82876 go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
3 months ago
Ilarion Kovalchuk 0cb7eb9b75 net/dns: updated gonotify dependency to v2 that supports closable context
Signed-off-by: Ilarion Kovalchuk <illarion.kovalchuk@gmail.com>
3 months ago
Brad Fitzpatrick 696711cc17 all: switch to and require Go 1.23
Updates #12912

Change-Id: Ib4ae26eb5fb68ad2216cab4913811b94f7eed5b6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Brad Fitzpatrick 0ff474ff37 all: fix new lint warnings from bumping staticcheck
In prep for updating to new staticcheck required for Go 1.23.

Updates #12912

Change-Id: If77892a023b79c6fa798f936fc80428fd4ce0673
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
3 months ago
Percy Wegmann 4637ac732e ipn/ipnlocal: remember last notified taildrive shares and only notify if they've changed
Fixes #13195

Signed-off-by: Percy Wegmann <percy@tailscale.com>
3 months ago