summaryrefslogtreecommitdiffhomepage
path: root/feature
AgeCommit message (Collapse)AuthorFilesLines
2026-04-24feature/conn25: add the ability to return addresses to the IP PoolsFran Bull2-24/+156
This will be used as part of the address assignment expiry work. Updates tailscale/corp#39975 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-04-21feature/conn25: add expiresAt field to addrsFran Bull2-22/+112
And use it to allow overwrites of old address assignments in the conn25 client. The magic and transit address pools from which the addresses come are limited resources and we want to reuse them. This commit is a small part of that bigger need. We expect to follow soon: * Extending expiry if assignments are still in use. * Returning expired addresses back to the pools so they can be reallocated. Updates tailscale/corp#39975 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-04-21feature/conn25: move byConnKey from addrAssignments to clientFran Bull2-25/+25
addrAssignments is a table of addrs with lookup indices, representing the assignments of magic+destination+transit IP addresses the client has made dut to the domain being routed because of an app . byConnKey is a map of node public key to prefixes of transit IPs, so it is associated with, but not that data itself, and can be its own thing. Updates tailscale/corp#39975 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-04-20ipn/ipnlocal,tailcfg: add /debug/tka c2n endpoint (#19198)James 'zofrex' Sanderson3-0/+208
Updates tailscale/corp#35015 Signed-off-by: James Sanderson <jsanderson@tailscale.com>
2026-04-17feature/clientupdate: windows update should use tailscale.exe update (#19438)kari-ts1-0/+1
Currently, clientupdate.NewUpdater().Update() is called directly inside tailscaled, which fatals. There is also a failure that doesn't return, causing a panic. This fix allows us to use the same approach as startAutoUpdate, which is to find tailscale.exe and run tailscale.exe --update, though since it's calling the updater library directly, we get progress messages. Fixes tailscale/corp#40430s Signed-off-by: kari-ts <kari@tailscale.com>
2026-04-16appc,feature/conn25: prevent clients from forwarding DNS requests andMichael Ben-Ami2-110/+330
modifying DNS responses for domains they are also connectors for For Connectors 2025, determine if a client is configured as a connector and what domains it is a connector for. When acting as a client, don't install Split DNS routes to other connectors for those domains, and don't alter DNS responses for those domains. The responses are forwarded back to the original client, which in turn does the alteration, swapping the real IP for a Magic IP. A client is also a connector for a domain if it has tags that overlap with tags in the configured policy, and --advertise-connector=true in the prefs (not in the self-node Hostinfo from the netmap). We use the prefs as the source of truth because control only gets a copy from the prefs, and may drift. And the AppConnector field is currently zeroed out in the self-node Hostinfo from control. The extension adds a ProfileStateChange hook to process prefs changes, and the config type is split into prefs and nodeview sub-configs. Fixes tailscale/corp#39317 Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-04-07ipn/desktop: move behind feature/condregisterBrad Fitzpatrick1-0/+8
Move the ipn/desktop blank import from cmd/tailscaled/tailscaled_windows.go into feature/condregister/maybe_desktop_sessions.go, consistent with how all other modular features are registered. tailscaled already imports feature/condregister, so it still gets ipn/desktop on Windows. Updates #12614 Change-Id: I92418c4bf0e67f0ab40542e47584762ac0ffa2b2 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-07feature/conn25: add IPv6 supportFran Bull2-112/+374
Make the DNS handling portions of conn25 work with IPv6 addresses. Fixes tailscale/corp#37850 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-04-07ipn/localapi, cli, clientmetric: add ipnbus feature tag; fix omit.go stubBrad Fitzpatrick3-0/+27
Add a new "ipnbus" build feature tag so the watch-ipn-bus LocalAPI endpoint can be independently controlled, rather than being gated behind HasDebug || HasServe. Minimal/embedded builds that omit both debug and serve were getting 404s on watch-ipn-bus, breaking "tailscale up --authkey=..." and other CLI flows that depend on WatchIPNBus. In the CLI, check buildfeatures.HasIPNBus before attempting to watch the IPN bus in "tailscale up"/"tailscale login", and exit early with an informational message when the feature is omitted. Also add the missing NewCounterFunc stub to clientmetric/omit.go, which caused compilation errors when building with ts_omit_clientmetrics and netstack enabled. Fixes #19240 Change-Id: I2e3c69a72fc50fa02542b91b8a54859618a463d1 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-05cmd/vet: add subtestnames analyzer; fix all existing violationsBrad Fitzpatrick3-36/+36
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-03-27feature/conn25: install all the hooksMichael Ben-Ami4-76/+131
Install the previously uninstalled hooks for the filter and tstun intercepts. Move the DNS manager hook installation into Init() with all the others. Protect all implementations with a short-circuit if the node is not configured to use Connectors 2025. The short-circuit pattern replaces the previous pattern used in managing the DNS manager hook, of setting it to nil in response to CapMap changes. Fixes tailscale/corp#38716 Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-26feature/conn25: connect the ExtraWireguardAllowedIPs hook (#19140)George Jones1-0/+15
The hook calls into the client assigned addresses to return a view of the transit IPs associated with that connector. Fixes tailscale/corp#38125 Signed-off-by: George Jones <george@tailscale.com>
2026-03-26feature/conn25: Store transit ips by connector key (#19071)George Jones2-24/+334
The client needs to know the set of transit IPs that are assigned to each connector, so when we register transit IPs with the connector we also need to assign them to that connector in the addrAssignments. We identify the connector by node public key to match the peer information that is available when the ExtraWireguardAllowedIPs hook will be invoked. Fixes tailscale/corp#38127 Signed-off-by: George Jones <george@tailscale.com>
2026-03-25feature/conn25,ipn/ipnext,ipn/ipnlocal: add ExtraRouterConfigRoutes hookFran Bull1-1/+14
conn25 needs to add routes to the operating system to direct handling of the addresses in the magic IP range to the tailscale0 TUN and tailscaled. The way we do this for exit nodes and VIP services is that we add routes to the Routes field of router.Config, and then the config is passed to the WireGuard engine Reconfig. conn25 is implemented as an ipnext.Extension and so this commit adds a hook to ipnext.Hooks to allow any extension to provide routes to the config. The hook if provided is called in routerConfigLocked, similarly to exit nodes and VIP services. Fixes tailscale/corp#38123 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-03-24feature/conn25: call AuthReconfigAsync after address assignmentFran Bull2-7/+29
When the client of a connector assigns transit IP addresses for a connector we need to let wireguard know that packets for the transit IPs should be sent to the connector node. We do this by: * keeping a map of node -> transit IPs we've assigned for it * setting a callback hook within wireguard reconfig to ask us for these extra allowed IPs. * forcing wireguard to do a reconfig after we have assigned new transit IPs. And this commit is the last part: forcing the wireguard reconfig after a new address assignment. Fixes tailscale/corp#38124 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-03-24feature/*,net/tstun: add tundev_txq_drops clientmetric on LinuxJordan Whited6-0/+582
By polling RTM_GETSTATS via netlink. RTM_GETSTATS is a relatively efficient and targeted (single device) polling method available since Linux v4.7. The tundevstats "feature" can be extended to other platforms in the future, and it's trivial to add new rtnl_link_stats64 counters on Linux. Updates tailscale/corp#38181 Signed-off-by: Jordan Whited <jordan@tailscale.com>
2026-03-24feature/conn25: guard extension Init() and PeerAPI handler with opt-in env varMichael Ben-Ami1-0/+12
Fixes tailscale/corp#39003 Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-23feature/conn25: add packet filter allow functionsFran Bull2-1/+117
That will be able to be plugged into the hooks in wgengine/filter/filter.go to let connector packets flow. Fixes tailscale/corp#37144 Fixes tailscale/corp#37145 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-03-20feature/conn25: guard against an index out of bounds panic (#19066)Andrew Lytvynov1-0/+3
Updates #cleanup Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2026-03-20feature/conn25: implement IPMapperFran Bull1-15/+15
Rename variables to match their types after the server -> connector rename. Updates tailscale/corp#37144 Updates tailscale/corp#37145 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-03-20feature/conn25: implement IPMapperFran Bull2-32/+188
Give the datapath hooks the lookup functions they need. Updates tailscale/corp#37144 Updates tailscale/corp#37145 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-03-19feature/featuretags: skip TestAllOmitBuildTagsDeclared when not in a git repoBrad Fitzpatrick1-9/+6
This test was failing on Alpine's CI which had 'git' but wasn't in a git repo: https://github.com/tailscale/tailscale/commit/036b6a12621306da8368b167deb9858d4a8d6ce9#commitcomment-180001647 Updates #12614 Change-Id: Ic1b8856aaf020788a2a57e48738851e13ea85a93 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-18feature/conn25: add NATing support with flow cachingMichael Ben-Ami4-0/+877
Introduce a datapathHandler that implements hooks that will receive packets from the tstun.Wrapper. This commit does not wire those up just yet. Perform DNAT from Magic IP to Transit IP on outbound flows on clients, and reverse SNAT in the reverse direction. Perform DNAT from Transit IP to final destination IP on outbound flows on connectors, and reverse SNAT in the reverse direction. Introduce FlowTable to cache validated flows by 5-tuple for fast lookups after the first packet. Flow expiration is not covered, and is intended as future work before the feature is officially released. Fixes tailscale/corp#34249 Fixes tailscale/corp#35995 Co-authored-by: Fran Bull <fran@tailscale.com> Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-16feature/conn25: rewrite A records for connector domainsFran Bull2-68/+553
When we are mapping a dns response, if it is a connector domain, change the source IP addresses for our magic IP addresses. This will allow the tailscaled to DNAT the traffic for the domain to the connector. Updates tailscale/corp#34258 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-03-13feature/conn25: Update ConnectorTransitIPRequest handling (#18979)George Jones2-177/+409
Changed the mapping to store the transit IPs to be indexed by peer IP rather than NodeID because the data path only has access to the peer's IP. This change means that IPv4 transit IPs need to be indexed by the peer's IPv4 address, and IPv6 transit IPs need to be indexed by the peer's IPv6 address. It is an error if the peer does not have an address of the same family as the transit IP. It is also an error if the transit and destination IP families do not match. Added a check to ensure that the TransitIPRequest.App matches a configured app on the connector. Added additional TransitIPResponse codes to identify the new errors and change the exsting use of the Other code to use it's own specific code. Added logging for the error cases, since they generally indicate that a peer has constructed a bad request or that there is a config mismatch between the peer and the local netmap. Added a test framework for handleConnectorTransitIPRequest and moved the existing tests into the framework and added new tests. Fixes tailscale/corp#37143 Signed-off-by: George Jones <george@tailscale.com>
2026-03-10ipn/ipnlocal, feature/ssh: move SSH code out of LocalBackend to featureBrad Fitzpatrick2-0/+16
This makes tsnet apps not depend on x/crypto/ssh and locks that in with a test. It also paves the wave for tsnet apps to opt-in to SSH support via a blank feature import in the future. Updates #12614 Change-Id: Ica85628f89c8f015413b074f5001b82b27c953a9 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-09appc,feature/conn25: conn25: send address assignments to connectorFran Bull2-21/+284
After we intercept a DNS response and assign magic and transit addresses we must communicate the assignment to our connector so that it can direct traffic when it arrives. Use the recently added peerapi endpoint to send the addresses. Updates tailscale/corp#34258 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-03-09tailcfg: reintroduce UserProfile.GroupsGesa Stupperich1-2/+2
This change reintroduces UserProfile.Groups, a slice that contains the ACL-defined and synced groups that a user is a member of. The slice will only be non-nil for clients with the node attribute see-groups, and will only contain groups that the client is allowed to see as per the app payload of the see-groups node attribute. For example: ``` "nodeAttrs": [ { "target": ["tag:dev"], "app": { "tailscale.com/see-groups": [{"groups": ["group:dev"]}] } }, [...] ] ``` UserProfile.Groups will also be gated by a feature flag for the time being. Updates tailscale/corp#31529 Signed-off-by: Gesa Stupperich <gesa@tailscale.com>
2026-03-06all: use Go 1.26 things, run most gofix modernizersBrad Fitzpatrick5-12/+6
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-05types/ptr: deprecate ptr.To, use Go 1.26 newBrad Fitzpatrick2-28/+26
Updates #18682 Change-Id: I62f6aa0de2a15ef8c1435032c6aa74a181c25f8f Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-04feature/portlist: address case where poller misses CollectServices updatesBrad Fitzpatrick1-0/+13
This is a minimal hacky fix for a case where the portlist poller extension could miss updates to NetMap's CollectServices bool. Updates tailscale/corp#36813 Change-Id: I9b50de8ba8b09e4a44f9fbfe90c9df4d8ab4d586 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-03feature/conn25: stop adding multiple entries for same domain+dstFran Bull2-69/+139
We should only add one entry to our magic ips for each domain+dst and look up any existing entry instead of always creating a new one. Fixes tailscale/corp#34252 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-02-20appc,feature/conn25,net: Add DNS response interception for conn25Fran Bull4-5/+1077
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-08cmd/tailscale,feature/featuretags: make webbrowser and colorable deps omittableBrad Fitzpatrick5-2/+59
Add new "webbrowser" and "colorable" feature tags so that the github.com/toqueteos/webbrowser and mattn/go-colorable packages can be excluded from minbox builds. Updates #12614 Change-Id: Iabd38b242f5a56aa10ef2050113785283f4e1fe8 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-02-04feature/featuretags: add test that all ts_omit_foo tags are declaredBrad Fitzpatrick4-1/+71
Updates #12614 Change-Id: I49351fe0c463af0b8d940e8088d4748906a8aec3 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-02-04cmd/tailscaled,feature/conn25,feature/featuretags: add conn25 to featuretagsFran Bull4-0/+30
Package feature/conn25 is excludeable from a build via the featuretag. Test it is excluded for minimal builds. Updates #12614 Signed-off-by: Fran Bull <fran@tailscale.com>
2026-01-29cmd/tailscale/cli: allow fetching keys from AWS Parameter StoreAndrew Dunham4-0/+185
This allows fetching auth keys, OAuth client secrets, and ID tokens (for workload identity federation) from AWS Parameter Store by passing an ARN as the value. This is a relatively low-overhead mechanism for fetching these values from an external secret store without needing to run a secret service. Usage examples: # Auth key tailscale up \ --auth-key=arn:aws:ssm:us-east-1:123456789012:parameter/tailscale/auth-key # OAuth client secret tailscale up \ --client-secret=arn:aws:ssm:us-east-1:123456789012:parameter/tailscale/oauth-secret \ --advertise-tags=tag:server # ID token (for workload identity federation) tailscale up \ --client-id=my-client \ --id-token=arn:aws:ssm:us-east-1:123456789012:parameter/tailscale/id-token \ --advertise-tags=tag:server Updates tailscale/corp#28792 Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2026-01-23all: remove AUTHORS file and references to itWill Norris230-231/+231
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-14cmd,feature: add identity token auto generation for workload identity (#18373)Danni Popova2-5/+19
Adds the ability to detect what provider the client is running on and tries fetch the ID token to use with Workload Identity. Updates https://github.com/tailscale/corp/issues/33316 Signed-off-by: Danni Popova <danni@tailscale.com>
2026-01-08cmd,internal,feature: add workload idenity support to gitops pusherMario Minardi1-0/+1
Add support for authenticating the gitops-pusher using workload identity federation. Updates https://github.com/tailscale/corp/issues/34172 Signed-off-by: Mario Minardi <mario@tailscale.com>
2026-01-08feature/featuretags: make QR codes modular (#18358)Simon Law3-0/+27
QR codes are used by `tailscale up --qr` to provide an easy way to open a web-page without transcribing a difficult URI. However, there’s no need for this feature if the client will never be called interactively. So this PR adds the `ts_omit_qrcodes` build tag. Updates #18182 Signed-off-by: Simon Law <sfllaw@tailscale.com>
2025-12-18net/udprelay: expose peer relay metrics (#18218)Alex Valiushko1-1/+1
Adding both user and client metrics for peer relay forwarded bytes and packets, and the total endpoints gauge. User metrics: tailscaled_peer_relay_forwarded_packets_total{transport_in, transport_out} tailscaled_peer_relay_forwarded_bytes_total{transport_in, transport_out} tailscaled_peer_relay_endpoints_total{} Where the transport labels can be of "udp4" or "udp6". Client metrics: udprelay_forwarded_(packets|bytes)_udp(4|6)_udp(4|6) udprelay_endpoints RELNOTE: Expose tailscaled metrics for peer relay. Updates tailscale/corp#30820 Change-Id: I1a905d15bdc5ee84e28017e0b93210e2d9660259 Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
2025-12-09appc,feature: add the start of new conn25 app connectorFran Bull2-0/+92
When peers request an IP address mapping to be stored, the connector stores it in memory. Fixes tailscale/corp#34251 Signed-off-by: Fran Bull <fran@tailscale.com>
2025-12-02tsnet: enable node registration via federated identityGesa Stupperich2-37/+231
Updates: tailscale.com/corp#34148 Signed-off-by: Gesa Stupperich <gesa@tailscale.com>
2025-12-01feature/posture: log method and full URL for posture identity requestsAnton Tolchanov1-1/+1
Updates tailscale/corp#34676 Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2025-11-26feature/tpm: return opening errors from both /dev/tpmrm0 and /dev/tpm0 (#18071)Andrew Lytvynov1-1/+8
This might help users diagnose why TPM access is failing for tpmrm0. Fixes #18026 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2025-11-25cmd/tailscale/cli,ipn,all: make peer relay server port a *uint16Jordan Whited2-32/+32
In preparation for exposing its configuration via ipn.ConfigVAlpha, change {Masked}Prefs.RelayServerPort from *int to *uint16. This takes a defensive stance against invalid inputs at JSON decode time. 'tailscale set --relay-server-port' is currently the only input to this pref, and has always sanitized input to fit within a uint16. Updates tailscale/corp#34591 Signed-off-by: Jordan Whited <jordan@tailscale.com>
2025-11-25tailcfg, control/controlclient: start moving MapResponse.DefaultAutoUpdate ↵Brad Fitzpatrick2-0/+24
to a nodeattr And fix up the TestAutoUpdateDefaults integration tests as they weren't testing reality: the DefaultAutoUpdate is supposed to only be relevant on the first MapResponse in the stream, but the tests weren't testing that. They were instead injecting a 2nd+ MapResponse. This changes the test control server to add a hook to modify the first map response, and then makes the test control when the node goes up and down to make new map responses. Also, the test now runs on macOS where the auto-update feature being disabled would've previously t.Skipped the whole test. Updates #11502 Change-Id: If2319bd1f71e108b57d79fe500b2acedbc76e1a6 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-11-24cmd/tailscale,feature/relayserver,ipn: add relay-server-static-endpoints set ↵Jordan Whited2-14/+93
flag Updates tailscale/corp#31489 Updates #17791 Signed-off-by: Jordan Whited <jordan@tailscale.com>
2025-11-21feature/relayserver: don't publish from within a subscribe fn goroutineJordan Whited1-1/+6
Updates #17830 Signed-off-by: Jordan Whited <jordan@tailscale.com>