Compare commits

...

116 Commits

Author SHA1 Message Date
Tom DNetto
4df98808da cmd/derper: add default-off debug endpoint for profiling
Updates https://github.com/tailscale/corp/issues/11492
Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-06-07 13:06:12 -07:00
Andrea Gottardo
1c4a047ad0 version: detect tvOS by checking XPC_SERVICE_NAME (#8295)
Another change needed working towards #8282.

Updates https://github.com/tailscale/tailscale/issues/8282

Signed-off-by: Andrea Gottardo <andrea@tailscale.com>
2023-06-07 12:19:31 -07:00
Marwan Sulaiman
f8f0b981ac portlist: remove async functionality
This PR removes all async functionality from the portlist package
which may be a breaking change for non-tailscale importers. The only
importer within this codebase (LocalBackend) is already using the synchronous
API so no further action needed.

Fixes #8171

Signed-off-by: Marwan Sulaiman <marwan@tailscale.com>
2023-06-07 10:22:09 -04:00
Andrea Gottardo
a353ae079b tool/gocross: handle TVOS_DEPLOYMENT_TARGET (#8292)
This is needed in order to build our network extension on tvOS. First step for #8282

Signed-off-by: Andrea Gottardo <andrea@tailscale.com>
Co-authored-by: Andrea Gottardo <andrea@tailscale.com>
2023-06-06 18:06:17 -07:00
Xe Iaso
43e230d4cd tsnet: document how to use Dir with multiple servers per process (#8286)
Followup from a conversation on the Gophers slack, also matches the
examples in the tsnet kb.

Closes #8287

Signed-off-by: Xe Iaso <xe@tailscale.com>
2023-06-06 15:20:00 -04:00
Marwan Sulaiman
5dd0b02133 portlist: add a synchronous Poll method
This is a follow up on PR #8172 that adds a synchronous Poll method
which allows for the Poller to be used as a zero value without needing
the constructor. The local backend is also changed to use the new API.
A follow up PR will remove the async functionality from the portlist package.

Updates #8171

Signed-off-by: Marwan Sulaiman <marwan@tailscale.com>
2023-06-05 23:10:42 -04:00
Derek Burdick
d3c8c3dd00 ssh/tailssh: Max Username Length 256 for linux
Max username length is increased to 256 on linux to match /usr/include/bits/local_lim.h

Fixes #8277

Signed-off-by: Derek Burdick <derek-burdick@users.noreply.github.com>
2023-06-05 18:04:30 -07:00
Denton Gentry
64f16f7f38 net/dnscache: use PreferGo on Windows.
Updates https://github.com/tailscale/tailscale/issues/5161

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-06-05 15:58:49 -07:00
Denton Gentry
6554a0cbec build_dist.sh: use $go consistently.
The invocation at the end unconditionally used
./tool/go, but the structuring on lines 14-17
sets up to use a different toolchain if the
platform requires it.

Fixes https://github.com/tailscale/tailscale/issues/8156

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-06-05 10:21:03 -07:00
License Updater
d17312265e licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply+license-updater@tailscale.com>
2023-06-05 09:54:12 -07:00
Denton Gentry
4321d1d6e9 scripts/installer.sh: add sle-micro-rancher.
Fixes https://github.com/tailscale/tailscale/issues/5633

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-06-04 17:05:51 -07:00
DJRHails
2492ca2900 words: remove misspelling of trex
Signed-off-by: DJRHails <hello@hails.info>
2023-06-04 14:18:36 -07:00
Dominic Black
570cb018da ipn/localapi: require only read permission for WatchIPNBus (#7798)
Allow calls to `WatchIPNBus` to be permformed by clients with
Readonly permissions. This brings it in line with the permissions
required for `Status`, which also exposes the similar information.

This allows clients to get realtime updates about the tailnet
in their own applications, without needing to actively poll the
`Status` endpoint.

Fixes https://github.com/tailscale/tailscale/issues/7797

Signed-off-by: Dominic Black <dom@encore.dev>
2023-06-04 09:05:21 -07:00
Heiko Rothe
dc1d8826a2 ipn/ipnlocal: [serve/funnel] add forwarded host and proto header (#8224)
This replicates the headers also sent by the golang reverse proxy by
default.

Fixes https://github.com/tailscale/tailscale/issues/7061

Signed-off-by: Heiko Rothe <me@heikorothe.com>
2023-06-04 09:04:33 -07:00
Denton Gentry
67882ad35d scripts/installer.sh: add BlendOS support.
Fixes https://github.com/tailscale/tailscale/issues/8100

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-06-04 08:31:58 -07:00
Andrew Dunham
07eacdfe92 ipn/ipnlocal: renew certificates based on lifetime
Instead of renewing certificates based on whether or not they're expired
at a fixed 14-day period in the future, renew based on whether or not
we're more than 2/3 of the way through the certificate's lifetime. This
properly handles shorter-lived certificates without issue.

Updates #8204

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I5e82a9cadc427c010d04ce58c7f932e80dd571ea
2023-06-02 12:36:32 -04:00
License Updater
d06fac0ede licenses: update win/apple licenses
Signed-off-by: License Updater <noreply+license-updater@tailscale.com>
2023-06-01 19:02:47 -07:00
Andrew Dunham
9d09c821f7 ipn/ipnlocal: add more logging during profile migration
Updates tailscale/corp#11883

Change-Id: I3a3ca8f25bfefca139115b25ec4161c069da7e4a
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-06-01 21:00:55 -04:00
Aaron Klotz
2aa8299c37 cmd/tailscaled, util/winutil: log our registry keys during tailscaled startup
In order to improve our ability to understand the state of policies and
registry settings when troubleshooting, we enumerate all values in all subkeys.
x/sys/windows does not already offer this, so we need to call RegEnumValue
directly.

For now we're just logging this during startup, however in a future PR I plan to
also trigger this code during a bugreport. I also want to log more than just
registry.

Fixes #8141

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2023-06-01 13:39:17 -06:00
Flakes Updater
88ee857bc8 go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
2023-06-01 09:11:17 -07:00
Vince Prignano
1a691ec5b2 cmd/k8s-operator: update controller-runtime to v0.15
Fixes #8170

Signed-off-by: Vince Prignano <vince@prigna.com>
2023-06-01 05:51:25 -07:00
Anton Tolchanov
6a156f6243 client/tailscale: support deauthorizing a device
This adds a new `SetAuthorized` method that allows setting device
authorization to true or false. I chose the method name to be consistent
with SetTags.

Updates https://github.com/tailscale/corp/issues/10160

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-06-01 12:46:28 +01:00
dependabot[bot]
525b9c806f .github: bump peter-evans/create-pull-request from 5.0.0 to 5.0.1
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](5b4a9f6a9e...284f54f989)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-30 09:56:55 -07:00
David Anderson
fc5b137d25 release/dist/synology: build hi3535 as armv5, not armv7
This platform is technically an armv7, but has no hardware floating
point unit. armv5 is the only target Go understands to lack floating
point, so use that.

Updates #6860

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-05-29 21:01:40 -07:00
David Anderson
32e0ba5e68 release/dist/synology: build synology packages with cmd/dist
Updates #8217

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-05-29 14:18:37 -07:00
Denton Gentry
399a80785e wgengine/netstack: use ping6 on BSD platforms
Various BSD-derived operating systems including macOS and FreeBSD
require that ping6 be used for IPv6 destinations. The "ping" command
does not understand an IPv6 destination.

FreeBSD 13.x and later do handle IPv6 in the regular ping command,
but also retain a ping6 command. We use ping6 on all versions of
FreeBSD.

Fixes https://github.com/tailscale/tailscale/issues/8225

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-05-28 09:50:21 -07:00
David Anderson
c0b4a54146 release/dist/cli: correctly handle absolute build outputs in manifest
Some builders return absolute paths to build products already. When that
happens, the manifest writing logic shouldn't tack on another absolute
prefix.

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-05-25 19:52:40 -07:00
Brad Fitzpatrick
c4fe9c536d go.toolchain.rev: bump, again
For https://go-review.googlesource.com/c/go/+/498398

Updates tailscale/go#63
Updates tailscale/go#64

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-25 15:50:24 -07:00
valscale
370b2c37e0 tka: fix go vet complaint on copy of lock value in tailchonk_test.go (#8208)
go vet complains when we copy a lock value. Create clone function that
copies everything but the lock value.

Fixes #8207

Signed-off-by: Val <valerie@tailscale.com>
2023-05-25 13:34:13 -07:00
Brad Fitzpatrick
cb94ddb7b8 go.toolchain.rev: bump
Updates tailscale/go#63
Updates tailscale/go#64

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-25 12:21:39 -07:00
Andrea Gottardo
66f97f4bea tka: provide authority StateID in NetworkLockStatus response (#8200)
Fixes #8201.

Signed-off-by: Andrea Gottardo <andrea@tailscale.com>
Co-authored-by: Andrea Gottardo <andrea@tailscale.com>
2023-05-24 13:36:25 -07:00
Marwan Sulaiman
e32e5c0d0c portlist: add Poller.IncludeLocalhost option
This PR parameterizes receiving loopback updates from the portlist package.
Callers can now include services bound to localhost if they want.
Note that this option is off by default still.

Fixes #8171

Signed-off-by: Marwan Sulaiman <marwan@tailscale.com>
2023-05-24 13:26:16 -04:00
Will Norris
3d180a16c3 VERSION.txt: this is v1.43.0
Signed-off-by: Will Norris <will@tailscale.com>
2023-05-24 09:03:59 -07:00
Charlotte Brandhorst-Satzkorn
4e86857313 ssh/tailssh: add ssh session recording failed event type
This change introduces a SSHSessionRecordingFailed event type
that is used when a session recording fails to start or fails during a
session, and the on failure indicates that it should fail open.

Updates tailscale/corp#9967

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-05-22 17:39:01 -07:00
salman aljammaz
745ee97973 api.md: update device authorized API docs to allow for deauth (#8162)
The authorize device API (/api/v2/device/{deviceID}/authorized)
will soon allow device deauthorisation.

Fixes corp#10160.

Signed-off-by: salman <salman@tailscale.com>
2023-05-22 12:52:40 +01:00
Brad Fitzpatrick
a4fd4fd845 ssh/tailssh: fix regression after LDAP support
58ab66ec51 added LDAP support
for #4945 by shelling out to getdent.

It was supposed to fall back to the old method when getdent wasn't
found, but some variable name confusion (uid vs username) meant the
old path wasn't calling the right lookup function (user.LookupId
instead of user.Lookup).

Which meant that changed probably also broke FreeBSD and macOS SSH
support in addition to the reported OpenWRT regression.

The gokrazy support didn't look right either.

Fixes #8180

Change-Id: I273bbe96fe98b2517fbf0335fd476b483c051554
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-21 07:53:25 -07:00
James Tucker
e3cb982139 words: shell-ebrating crustacean chaos
Hey team! I've been diving deep into the code ocean for the past few
hours, tackling those sneaky race conditions that were threatening our
database. It was quite the crabby situation, but fear not! It's friday
and I've emerged and I'm ready to shell-ebrate with some punny word
additions. 🎉

This commit introduces a shell-shocking array of crustaceans to our word
list. From the lively lobsters to the clever prawns.

Signed-off-by: James Tucker <james@tailscale.com>
2023-05-19 14:15:26 -07:00
valscale
5ae786988c derp: remove default logging of disconnecting clients (#8163)
~97% of the log messages derper outputs are related to the normal
non-error state of a client disconnecting in some manner. Add a
verbose logging feature that only logs these messages when enabled.

Fixes #8024

Signed-off-by: Val <valerie@tailscale.com>
2023-05-18 15:35:16 -07:00
Maisem Ali
0ca8bf1e26 ssh/tailssh: close tty on session close
We were only closing on side of the pty/tty pair.
Close the other side too.

Thanks to @fritterhoff for reporting and debugging the issue!

Fixes #8119

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-05-18 09:35:42 -07:00
Gabriel Martinez
03e848e3b5 cmd/k8s-operator: add support for priorityClassName
Updates #8155

Signed-off-by: Gabriel Martinez <gabrielmartinez@sisti.pt>
2023-05-17 15:28:06 -07:00
Derek Kaser
7c88eeba86 cmd/tailscale: allow Tailscale to work with Unraid web interface (#8062)
Updates tailscale/tailscale#8026

Signed-off-by: Derek Kaser <derek.kaser@gmail.com>
2023-05-17 13:26:39 -07:00
Sonia Appasamy
f0ee03dfaf cmd/tailscale/cli: [serve] add reset flag
Usage: `tailscale serve reset`

Fixes #8139

Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
2023-05-16 16:19:08 -04:00
Brad Fitzpatrick
4664318be2 client/tailscale: revert CreateKey API change, add Client.CreateKeyWithExpiry
The client/tailscale is a stable-ish API we try not to break. Revert
the Client.CreateKey method as it was and add a new
CreateKeyWithExpiry method to do the new thing. And document the
expiry field and enforce that the time.Duration can't be between in
range greater than 0 and less than a second.

Updates #7143
Updates #8124 (reverts it, effectively)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-12 21:05:12 -07:00
shayne
678bb92bb8 cmd/tailscale/cli: [up] fix CreateKey missing argument (#8124)
Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
2023-05-12 02:03:17 -04:00
Matt Brown
9b6e48658f client: allow the expiry time to be specified for new keys
Adds a parameter for create key that allows a number of seconds
(less than 90) to be specified for new keys.

Fixes https://github.com/tailscale/tailscale/issues/7965

Signed-off-by: Matthew Brown <matthew@bargrove.com>
2023-05-11 22:05:18 -07:00
Maisem Ali
85215ed58a cmd/k8s-operator: handle NotFound secrets
getSingleObject can return `nil, nil`, getDeviceInfo was not handling
that case which resulted in panics.

Fixes #7303

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-05-11 18:26:11 -07:00
Brad Fitzpatrick
b69059334b util/set: add a basic map-based Set type
We have two other types of Sets here. Add the basic obvious one too.

Needed for a change elsewhere.

Updates #cleanup

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-11 15:17:34 -07:00
Joe Tsai
84c99fe0d9 logtail: be less aggressive about re-uploads (#8117)
The retry logic was pathological in the following ways:

* If we restarted the logging service, any pending uploads
would be placed in a retry-loop where it depended on backoff.Backoff,
which was too aggresive. It would retry failures within milliseconds,
taking at least 10 retries to hit a delay of 1 second.

* In the event where a logstream was rate limited,
the aggressive retry logic would severely exacerbate the problem
since each retry would also log an error message.
It is by chance that the rate of log error spam
does not happen to exceed the rate limit itself.

We modify the retry logic in the following ways:

* We now respect the "Retry-After" header sent by the logging service.

* Lacking a "Retry-After" header, we retry after a hard-coded period of
30 to 60 seconds. This avoids the thundering-herd effect when all nodes
try reconnecting to the logging service at the same time after a restart.

* We do not treat a status 400 as having been uploaded.
This is simply not the behavior of the logging service.

Updates #tailscale/corp#11213

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2023-05-11 12:52:35 -07:00
James Tucker
da90fab899 net/netcheck: reenable TestBasic on Windows
This test was either fixed by intermediate changes or was mis-flagged as
failing during #7876 triage.

Updates #7876

Signed-off-by: James Tucker <jftucker@gmail.com>
2023-05-10 20:16:35 -07:00
James Tucker
ca49b29582 tsnet: reenable TestLoopbackSOCKS5 on Windows
This test was either fixed in the intermediate time or mis-flagged
during the #7876 triage, but is now passing.

Updates #7876

Signed-off-by: James Tucker <jftucker@gmail.com>
2023-05-10 20:15:41 -07:00
Brad Fitzpatrick
cb2fd5be92 cmd/tsconnect: fix forgotten API change for wasm
Fix regression from 6e967446e4

Updates #8036

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-09 21:23:29 -07:00
David Anderson
d27a6e1c53 tool/gocross: fix incorrect relpath usage in bootstrap script
The subshell in which gocross gets built cd's to the corp checkout dir
near the top, so all future references to corp repository files should
be simple relative paths, and not reference $repo_root. When $repo_root
is an absolute path, it doesn't matter and everything works out, but on
some OSes and shells and invocations, $repo_root is a completely relative
path that is invalidated by the "cd".

Fixes tailscale/corp#11183

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-05-09 16:19:46 -07:00
Brad Fitzpatrick
4f454f4122 util/codegen: support embedded fields
I noticed cmd/{cloner,viewer} didn't support structs with embedded
fields while working on a change in another repo. This adds support.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-09 15:40:17 -07:00
Will Norris
ea84fc9ad2 net/sockstats: wait before reporting battery usage
Wait 2 minutes before we start reporting battery usage. There is always
radio activity on initial startup, which gets reported as 100% high
power usage.  Let that settle before we report usage data.

Updates tailscale/corp#9230

Signed-off-by: Will Norris <will@tailscale.com>
2023-05-09 10:18:16 -07:00
salman
1ce08256c0 release/dist: add deb/rpm arch mappings for mipses
According to https://wiki.debian.org/SupportedArchitectures Debian does
not support big-endian mips64, so that one remains disabled.

According to https://fedoraproject.org/wiki/Architectures Fedora only
supports little-endian mips, so leaving both big-endian ones out too.

Updates #8005.

Signed-off-by: salman <salman@tailscale.com>
2023-05-09 17:31:13 +01:00
Craig Rodrigues
827abbeeaa cmd/k8s-operator: print version in startup logs
Fixes: #7813

Signed-off-by: Craig Rodrigues <rodrigc@crodrigues.org>
2023-05-08 18:56:21 -07:00
License Updater
d1ecb1f43b licenses: update win/apple licenses
Signed-off-by: License Updater <noreply+license-updater@tailscale.com>
2023-05-08 16:48:36 -07:00
Brad Fitzpatrick
a743b66f9d ssh/tailssh: move some user-related code into new user.go
The previous commit 58ab66e added ssh/tailssh/user.go as part of
working on #4945. So move some more user-related code over to it.

Updates #cleanup

Change-Id: I24de66df25ffb8f867e1a0a540d410f9ef16d7b0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-08 13:47:59 -07:00
Brad Fitzpatrick
58ab66ec51 ssh/tailssh: support LDAP users for Tailscale SSH
Fixes #4945

Change-Id: Ie013cb47684cb87928a44f92c66352310bfe53f1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-08 11:29:14 -07:00
Chenyang Gao
e8b06b2232 version: properly handle vcs.modified when it's "false"
Current code will set the "dirty" field of VersionInfo always "true"
if vcs.modified flag is there. No matter whether the flag is "true" or
"false".  It will make sense to set this field due to vcs.modified
value, not only the existence of the key.

Signed-off-by: Chenyang Gao <gps949@outlook.com>
2023-05-07 09:15:38 -07:00
License Updater
df8b1b2179 licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply+license-updater@tailscale.com>
2023-05-06 23:48:30 -07:00
License Updater
4d730e154c licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-05-06 16:33:37 -07:00
Chenyang Gao
b9fb8ac702 fix sys.Set(router) issue will crash the daemon in some OSs
Signed-off-by: Chenyang Gao <gps949@outlook.com>

in commit 6e96744, the tsd system type has been added.
Which will cause the daemon will crash on some  OSs (Windows, darwin and so on).
The root cause is that on those OSs, handleSubnetsInNetstack() will return true and set the conf.Router with a wrapper. 
Later in NewUserspaceEngine() it will do subsystem set and found that early set router mismatch to current value, then panic.
2023-05-06 14:11:43 -07:00
James Tucker
5c38f0979e tsweb/promvarz: fix repeated expvar definition in test
expvar can only be defined once, so running tests with a repeat counter
will fail if the variables are defined inside of the test function.

Observed failure:

```
--- FAIL: TestHandler (0.00s)
panic: Reuse of exported var name: gauge_promvarz_test_expvar
 [recovered]
        panic: Reuse of exported var name: gauge_promvarz_test_expvar

goroutine 9 [running]:
testing.tRunner.func1.2({0x100f267e0, 0x1400026e770})
        /usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
        /usr/local/go/src/testing/testing.go:1529 +0x364
panic({0x100f267e0, 0x1400026e770})
        /usr/local/go/src/runtime/panic.go:884 +0x1f4
log.Panicln({0x140000b8e20?, 0x1a?, 0x1400026e750?})
        /usr/local/go/src/log/log.go:398 +0x60
expvar.Publish({0x100e2b21d, 0x1a}, {0x100fd7a08?, 0x140000232c0})
        /usr/local/go/src/expvar/expvar.go:284 +0xc0
expvar.NewInt(...)
        /usr/local/go/src/expvar/expvar.go:304
tailscale.com/tsweb/promvarz.TestHandler(0x14000082b60)
        /Users/charlotte/ts-src/tailscale/tsweb/promvarz/promvarz_test.go:18 +0x5c
testing.tRunner(0x14000082b60, 0x100fd5858)
        /usr/local/go/src/testing/testing.go:1576 +0x104
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:1629 +0x370
FAIL    tailscale.com/tsweb/promvarz    0.149s
```

Fixes #8065
Signed-off-by: James Tucker <james@tailscale.com>
2023-05-06 09:53:09 -07:00
Charlotte Brandhorst-Satzkorn
024d48d9c1 tailcfg: bump capability version
This change bumps the capability version to 62, after support for
sending SSHEventNotificationRequests to control via noise for failure
events was introduced.

Updates tailscale/corp#9967

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-05-05 14:22:59 -07:00
Charlotte Brandhorst-Satzkorn
29ded8f9f9 ssh/tailssh,tailcfg: add connID to ssheventnotifyrequest and castheader
This change adds a ConnectionID field to both SSHEventNotifyRequest and
CastHeader that identifies the ID of a connection to the SSH server.

Updates tailscale/corp#9967

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-05-05 14:22:59 -07:00
Charlotte Brandhorst-Satzkorn
68307c1411 ssh/tailssh: send ssh event notifications on recording failures
This change sends an SSHEventNotificationRequest over noise when a
SSH session is set to fail closed and the session is unable to start
because a recorder is not available or a session is terminated because
connection to the recorder is ended. Each of these scenarios have their
own event type.

Updates tailscale/corp#9967

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-05-05 14:22:59 -07:00
Charlotte Brandhorst-Satzkorn
2804327074 tailcfg: update rename SSHFailureNotifyRequest and add EventType
This change renames SSHFailureNotifyRequest to SSHEventNotifyRequest
to better reflect the additional events we could add in the future.
This change also adds an EventType used to catagories the events.

Updates tailscale/corp#9967

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-05-05 14:22:59 -07:00
Charlotte Brandhorst-Satzkorn
8d3d48e000 ipn/ipnlocal: add NodeKey func to return the public node NodeKey
This change introduces a NodeKey func on localbackend that returns the
public node key.

Updates tailscale/corp#9967

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-05-05 14:22:59 -07:00
Brad Fitzpatrick
8864112a0c ipn/ipnlocal: bound how long cert fetchher checks for existing DNS records
It was supposed to be best effort but in some cases (macsys at least,
per @marwan-at-work) it hangs and exhausts the whole context.Context
deadline so we fail to make the SetDNS call to the server.

Updates #8067
Updates #3273 etc

Change-Id: Ie1f04abe9689951484748aecdeae312afbafdb0f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-05 12:31:45 -07:00
James Tucker
9ed3a061c3 net/netns: fix segv when no tailscale interface is found
`interfaces.Tailscale()` returns all zero values when it finds no
Tailscale interface and encounters no errors. The netns package was
treating no error as a signal that it would receive a non-zero pointer
value leading to nil pointer dereference.

Observed in:

```
--- FAIL: TestGetInterfaceIndex (0.00s)
    --- FAIL: TestGetInterfaceIndex/IP_and_port (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x1029eb7d8]

goroutine 7 [running]:
testing.tRunner.func1.2({0x102a691e0, 0x102bc05c0})
        /Users/raggi/.cache/tailscale-go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
        /Users/raggi/.cache/tailscale-go/src/testing/testing.go:1529 +0x384
panic({0x102a691e0, 0x102bc05c0})
        /Users/raggi/.cache/tailscale-go/src/runtime/panic.go:884 +0x204
tailscale.com/net/netns.getInterfaceIndex(0x14000073f28, 0x1028d0284?, {0x1029ef3b7, 0xa})
        /Users/raggi/src/github.com/tailscale/tailscale/net/netns/netns_darwin.go:114 +0x228
tailscale.com/net/netns.TestGetInterfaceIndex.func2(0x14000138000)
        /Users/raggi/src/github.com/tailscale/tailscale/net/netns/netns_darwin_test.go:37 +0x54
testing.tRunner(0x14000138000, 0x140000551b0)
        /Users/raggi/.cache/tailscale-go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
        /Users/raggi/.cache/tailscale-go/src/testing/testing.go:1629 +0x368
FAIL    tailscale.com/net/netns 0.824s
```

Fixes #8064

Signed-off-by: James Tucker <jftucker@gmail.com>
2023-05-05 11:31:43 -07:00
Brad Fitzpatrick
6e967446e4 tsd: add package with System type to unify subsystem init, discovery
This is part of an effort to clean up tailscaled initialization between
tailscaled, tailscaled Windows service, tsnet, and the mac GUI.

Updates #8036

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-04 14:21:59 -07:00
Derek Kaser
0d7303b798 various: add detection and Taildrop for Unraid
Updates tailscale/tailscale#8025

Signed-off-by: Derek Kaser <derek.kaser@gmail.com>
2023-05-04 13:40:13 -07:00
Flakes Updater
d1ce7a9b5e go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
2023-05-04 12:55:01 -07:00
James Tucker
5def4f4a1c go.mod: bump goreleaser deps
Periodic update for start of cycle. goreleaser is not updated to v2 yet,
but indirects updated.

Updates #8043

Signed-off-by: James Tucker <james@tailscale.com>
2023-05-04 12:32:24 -07:00
Flakes Updater
1c6ff310ae go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
2023-05-04 12:31:31 -07:00
James Tucker
48605226dd go.mod: bump gvisor
Periodic update for start of cycle.

Updates #8043

Signed-off-by: James Tucker <james@tailscale.com>
2023-05-04 12:30:27 -07:00
Maisem Ali
f46c1aede0 go.mod: bump k8s libs
The key is to update sigs.k8s.io/controller-runtime and let it update others.

Updates #8043

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-05-04 12:00:03 -07:00
Brad Fitzpatrick
73d128238e envknob: support tailscaled-env.txt on macOS too
Updates #3707

Co-authored-by: Marwan Sulaiman <marwan@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-04 10:27:59 -07:00
Anton Tolchanov
787fc41fa4 scripts/installer.sh: check connectivity with pkgs.tailscale.com
Installer script relies on pkgs.tailscale.com being reachable, both for
checking what Linux distros are supported, but also for actually
downloading repo configuration files, gpg keys and packages themselves.

This change adds a simple reachability check which will print an error
message when pkgs.tailscale.com is not reachable.

Fixes https://github.com/tailscale/corp/issues/8952

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-05-04 16:49:56 +02:00
Flakes Updater
5783adcc6f go.mod.sri: update SRI hash for go.mod changes
Signed-off-by: Flakes Updater <noreply+flakes-updater@tailscale.com>
2023-05-03 19:56:23 -07:00
License Updater
503b6dd8be licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply+license-updater@tailscale.com>
2023-05-03 19:56:00 -07:00
Brad Fitzpatrick
9e9ea6e974 go.mod: bump all deps possible that don't break the build
This holds back gvisor, kubernetes, goreleaser, and esbuild, which all
had breaking API changes.

Updates #8043
Updates #7381
Updates #8042 (updates u-root which adds deps)

Change-Id: I889759bea057cd3963037d41f608c99eb7466a5b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-03 19:52:54 -07:00
M. J. Fromberger
459744c9ea .github: mark bots for exemption by issuebot (#8041)
#cleanup

Change-Id: I13757ee20240166af72606d166e840ae3ee797d7
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
2023-05-03 18:36:19 -07:00
License Updater
7675d323fa licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-05-03 15:48:09 -07:00
dependabot[bot]
270942094f build(deps): bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.16+incompatible to 20.10.24+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v20.10.16...v20.10.24)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 15:40:46 -07:00
Maisem Ali
be190e990f ssh/tailssh: restore support for recording locally
We removed it earlier in 916aa782af, but we still want to support it for some time longer.

Updates tailscale/corp#9967

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-05-03 15:00:01 -07:00
Brad Fitzpatrick
4d7927047c wgengine/magicsock: annotate, skip flaky TestIsWireGuardOnlyPickEndpointByPing
Updates #8037
Updates #7826

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-03 14:58:28 -07:00
Charlotte Brandhorst-Satzkorn
ddb4040aa0 wgengine/magicsock: add address selection for wireguard only endpoints (#7979)
This change introduces address selection for wireguard only endpoints.
If a endpoint has not been used before, an address is randomly selected
to be used based on information we know about, such as if they are able
to use IPv4 or IPv6. When an address is initially selected, we also
initiate a new ICMP ping to the endpoints addresses to determine which
endpoint offers the best latency. This information is then used to
update which endpoint we should be using based on the best possible
route. If the latency is the same for a IPv4 and an IPv6 address, IPv6
will be used.

Updates #7826

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-05-02 17:49:56 -07:00
Brad Fitzpatrick
c1e6888fc7 derp: add a README.md with some docs
Updates #docs

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-05-02 13:42:25 -07:00
Maisem Ali
3ae7140690 net/tstun: handle exit nodes in NAT configs
In the case where the exit node requires SNAT, we would SNAT all traffic not just the
traffic meant to go through the exit node. This was a result of the default route being
added to the routing table which would match basically everything.

In this case, we need to account for all peers in the routing table not just the ones
that require NAT.

Fix and add a test.

Updates tailscale/corp#8020

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-05-02 13:03:30 -07:00
Andrew Dunham
bcf7b63d7e wgengine/magicsock: add hysteresis to endpoint selection
Avoid selecting an endpoint as "better" than the current endpoint if the
total latency improvement is less than 1%. This adds some hysteresis to
avoid flapping between endpoints for a minimal improvement in latency.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: If8312e1768ea65c4b4d4e13d8de284b3825d7a73
2023-05-02 08:56:16 -07:00
Tom DNetto
c5bf868940 ssh/tailssh: improve debug logging around revoked sessions
Updates https://github.com/tailscale/corp/issues/10943
Signed-off-by: Tom DNetto <tom@tailscale.com>
2023-05-01 14:10:16 -07:00
Andrew Dunham
42fd964090 control/controlclient: use dnscache.Resolver for Noise client
This passes the *dnscache.Resolver down from the Direct client into the
Noise client and from there into the controlhttp client. This retains
the Resolver so that it can share state across calls instead of creating
a new resolver.

Updates #4845
Updates #6110

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ia5d6af1870f3b5b5d7dd5685d775dcf300aec7af
2023-05-01 13:22:10 -07:00
License Updater
979d29b5f5 licenses: update win/apple licenses
Signed-off-by: License Updater <noreply+license-updater@tailscale.com>
2023-05-01 09:13:43 -07:00
Anton Tolchanov
1f4a34588b .github: test installer script in CI in docker
Every time we change `installer.sh`, run it in a few docker
containers based on different Linux distros, just as a simple test.

Also includes a few changes to the installer script itself to make
installation work in docker:
- install dnf config-manager command before running it
- run zypper in non-interactive mode
- update pacman indexes before installing packages

Updates https://github.com/tailscale/corp/issues/8952

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2023-04-29 08:48:19 +01:00
Denton Gentry
a82f275619 cmd/sniproxy: Set App name in tsnet hostinfo
Updates #1748
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-04-28 21:50:30 -07:00
James Tucker
b3c3a9f174 syncs: add Map.Len to get the length of the Map
I need this for a corp change where I have a set as a queue, and make a
different decisison if the set is empty.

Updates tailscale/corp#10344

Signed-off-by: James Tucker <james@tailscale.com>
2023-04-27 19:10:28 -07:00
ayanamist
042f82ea32 build_dist.sh: make cross-compilation friendly for env CC specified
Signed-off-by: ayanamist <ayanamist@gmail.com>
2023-04-27 18:51:59 -07:00
dependabot[bot]
633d08bd7b .github: Bump actions/setup-go from 3 to 4
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-27 17:21:59 -07:00
James Tucker
d35ce1add9 syncs: add documentation to Map.Range
Updates #cleanup

Signed-off-by: James Tucker <james@tailscale.com>
2023-04-27 17:07:07 -07:00
Charlotte Brandhorst-Satzkorn
c3ab36cb9d words: charlotte 1 spell-checking-linter 0 (#7993)
Can't have a dupe when the dupe is wrong. Clearly we need to up
our spell checking game. Did anyone say AI?

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-04-27 08:58:52 -07:00
James Tucker
8032b966a1 .github/workflows: add recency bias to action cache keys
The action cache restore process either matches the restore key pattern
exactly, or uses a matching prefix with the most recent date.

If the restore key is an exact match, then no updates are uploaded, but
if we've just computed tests executions for more recent code then we
will likely want to use those results in future runs.

Appending run_id to the cache key will give us an always new key, and
then we will be restore a recently uploaded cache that is more likely
has a higher overlap with the code being tested.

Updates #7975

Signed-off-by: James Tucker <james@tailscale.com>
2023-04-26 21:36:53 -07:00
Kyle Carberry
d78b334964 cmd/derper: disable http2
DERP doesn't support HTTP/2. If an HTTP/2 proxy was placed in front of
a DERP server requests would fail because the connection would
be initialized with HTTP/2, which the DERP client doesn't support.

Signed-off-by: Kyle Carberry <kyle@carberry.com>
2023-04-26 20:45:32 -07:00
Charlotte Brandhorst-Satzkorn
161d1d281a net/ping,netcheck: add v6 pinging capabilities to pinger (#7971)
This change adds a v6conn to the pinger to enable sending pings to v6
addrs.

Updates #7826

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2023-04-26 15:59:37 -07:00
Denton Gentry
1145b9751d VERSION.txt: this is v1.41.0
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2023-04-26 15:58:30 -07:00
Brad Fitzpatrick
1e876a3c1d ipn/ipnlocal: fix fmt format arg type mismatch in log line
It was printing like "v0xxxxxxxx" after version.Long became a func
in 8b2ae47c31.

Fixes #7976

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-04-26 14:28:09 -07:00
Maisem Ali
a8f10c23b2 cmd/tailscale/cli: [up] reuse --advertise-tags for OAuth key generation
We need to always specify tags when creating an AuthKey from an OAuth key.

Check for that, and reuse the `--advertise-tags` param.

Updates #7982

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-04-26 14:17:29 -07:00
Brad Fitzpatrick
b2b5379348 cmd/tailscale/cli: [up] change oauth authkey format
Updates #7982

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-04-26 13:36:17 -07:00
Brad Fitzpatrick
13de36303d cmd/tailscale/cli: [up] add experimental oauth2 authkey support
Updates #7982

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-04-26 12:25:42 -07:00
James Tucker
095d3edd33 ipn/ipnlocal: reenable profile tests on Windows
This fix does not seem ideal, but the test infrastructure using a local
goos doesn't seem to avoid all of the associated challenges, but is
somewhat deeply tied to the setup.

The core issue this addresses for now is that when run on Windows there
can be no code paths that attempt to use an invalid UID string, which on
Windows is described in [1].

For the goos="linux" tests, we now explicitly skip the affected
migration code if runtime.GOOS=="windows", and for the Windows test we
explicitly use the running users uid, rather than just the string
"user1". We also now make the case where a profile exists and has
already been migrated a non-error condition toward the outer API.

Updates #7876

[1] https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers

Signed-off-by: James Tucker <jftucker@gmail.com>
2023-04-26 11:33:38 -07:00
James Tucker
43819309e1 .github/workflows: split tests and benchmarks for caching
Benchmark flags prevent test caching, so benchmarks are now executed
independently of tests.

Fixes #7975

Signed-off-by: James Tucker <james@tailscale.com>
2023-04-26 10:49:38 -07:00
Maisem Ali
1b8a0dfe5e ssh/tailssh: also handle recording upload failure during writes
Previously we would error out when the recording server disappeared after the in memory
buffer filled up for the io.Copy. This makes it so that we handle failing open correctly
in that path.

Updates tailscale/corp#9967

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2023-04-25 19:40:46 -07:00
shayne
018a382729 cmd/tailscale/cli: [serve] fix MinGW path conversion (#7964)
Fixes #7963

Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
2023-04-25 13:07:17 -04:00
License Updater
2e07245384 licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2023-04-25 09:10:26 -07:00
License Updater
aa87e999dc licenses: update win/apple licenses
Signed-off-by: License Updater <noreply+license-updater@tailscale.com>
2023-04-25 09:10:05 -07:00
149 changed files with 5069 additions and 2165 deletions

View File

@@ -50,11 +50,11 @@ jobs:
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
- name: Send pull request
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
with:
token: ${{ steps.generate-token.outputs.token }}
author: License Updater <noreply@tailscale.com>
committer: License Updater <noreply@tailscale.com>
author: License Updater <noreply+license-updater@tailscale.com>
committer: License Updater <noreply+license-updater@tailscale.com>
branch: licenses/cli
commit-message: "licenses: update tailscale{,d} licenses"
title: "licenses: update tailscale{,d} licenses"

View File

@@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version-file: go.mod
cache: false

102
.github/workflows/installer.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: test installer.sh
on:
push:
branches:
- "main"
paths:
- scripts/installer.sh
pull_request:
branches:
- "*"
paths:
- scripts/installer.sh
jobs:
test:
strategy:
# Don't abort the entire matrix if one element fails.
fail-fast: false
# Don't start all of these at once, which could saturate Github workers.
max-parallel: 4
matrix:
image:
# This is a list of Docker images against which we test our installer.
# If you find that some of these no longer exist, please feel free
# to remove them from the list.
# When adding new images, please only use official ones.
- "debian:oldstable-slim"
- "debian:stable-slim"
- "debian:testing-slim"
- "debian:sid-slim"
- "ubuntu:18.04"
- "ubuntu:20.04"
- "ubuntu:22.04"
- "ubuntu:22.10"
- "ubuntu:23.04"
- "elementary/docker:stable"
- "elementary/docker:unstable"
- "parrotsec/core:lts-amd64"
- "parrotsec/core:latest"
- "kalilinux/kali-rolling"
- "kalilinux/kali-dev"
- "oraclelinux:9"
- "oraclelinux:8"
- "fedora:latest"
- "rockylinux:8.7"
- "rockylinux:9"
- "amazonlinux:latest"
- "opensuse/leap:latest"
- "opensuse/tumbleweed:latest"
- "archlinux:latest"
- "alpine:3.14"
- "alpine:latest"
- "alpine:edge"
deps:
# Run all images installing curl as a dependency.
- curl
include:
# Check a few images with wget rather than curl.
- { image: "debian:oldstable-slim", deps: "wget" }
- { image: "debian:sid-slim", deps: "wget" }
- { image: "ubuntu:23.04", deps: "wget" }
# Ubuntu 16.04 also needs apt-transport-https installed.
- { image: "ubuntu:16.04", deps: "curl apt-transport-https" }
- { image: "ubuntu:16.04", deps: "wget apt-transport-https" }
runs-on: ubuntu-latest
container:
image: ${{ matrix.image }}
options: --user root
steps:
- name: install dependencies (yum)
# tar and gzip are needed by the actions/checkout below.
run: yum install -y --allowerasing tar gzip ${{ matrix.deps }}
if: |
contains(matrix.image, 'centos')
|| contains(matrix.image, 'oraclelinux')
|| contains(matrix.image, 'fedora')
|| contains(matrix.image, 'amazonlinux')
- name: install dependencies (zypper)
# tar and gzip are needed by the actions/checkout below.
run: zypper --non-interactive install tar gzip
if: contains(matrix.image, 'opensuse')
- name: install dependencies (apt-get)
run: |
apt-get update
apt-get install -y ${{ matrix.deps }}
if: |
contains(matrix.image, 'debian')
|| contains(matrix.image, 'ubuntu')
|| contains(matrix.image, 'elementary')
|| contains(matrix.image, 'parrotsec')
|| contains(matrix.image, 'kalilinux')
- name: checkout
uses: actions/checkout@v3
- name: run installer
run: scripts/installer.sh
# Package installation can fail in docker because systemd is not running
# as PID 1, so ignore errors at this step. The real check is the
# `tailscale --version` command below.
continue-on-error: true
- name: check tailscale version
run: tailscale --version

View File

@@ -65,8 +65,9 @@ jobs:
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
restore-keys: |
${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}
${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-
- name: build all
run: ./tool/go build ${{matrix.buildflags}} ./...
@@ -89,7 +90,11 @@ jobs:
- name: build test wrapper
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
- name: test all
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper -bench=. -benchtime=1x ./...
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper
env:
GOARCH: ${{ matrix.goarch }}
- name: bench all
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper -test.bench=. -test.benchtime=1x -test.run=^$
env:
GOARCH: ${{ matrix.goarch }}
- name: check that no tracked files changed
@@ -131,8 +136,9 @@ jobs:
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
restore-keys: |
${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
${{ github.job }}-${{ runner.os }}-go-2-
- name: test
# Don't use -bench=. -benchtime=1x.
@@ -206,8 +212,9 @@ jobs:
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
restore-keys: |
${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}
${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-
- name: build all
run: ./tool/go build ./cmd/...
@@ -271,8 +278,9 @@ jobs:
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
restore-keys: |
${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
${{ github.job }}-${{ runner.os }}-go-2-
- name: build tsconnect client
run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli

View File

@@ -35,11 +35,11 @@ jobs:
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
- name: Send pull request
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
with:
token: ${{ steps.generate-token.outputs.token }}
author: Flakes Updater <noreply@tailscale.com>
committer: Flakes Updater <noreply@tailscale.com>
author: Flakes Updater <noreply+flakes-updater@tailscale.com>
committer: Flakes Updater <noreply+flakes-updater@tailscale.com>
branch: flakes
commit-message: "go.mod.sri: update SRI hash for go.mod changes"
title: "go.mod.sri: update SRI hash for go.mod changes"

View File

@@ -48,11 +48,10 @@ staticcheck: ## Run staticcheck.io checks
./tool/go run honnef.co/go/tools/cmd/staticcheck -- $$(./tool/go list ./... | grep -v tempfork)
spk: ## Build synology package for ${SYNO_ARCH} architecture and ${SYNO_DSM} DSM version
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o tailscale.spk --source=. --goarch=${SYNO_ARCH} --dsm-version=${SYNO_DSM}
./tool/go run ./cmd/dist build synology/dsm${SYNO_DSM}/${SYNO_ARCH}
spkall: ## Build synology packages for all architectures and DSM versions
mkdir -p spks
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o spks --source=. --goarch=all --dsm-version=all
./tool/go run ./cmd/dist build synology
pushspk: spk ## Push and install synology package on ${SYNO_HOST} host
echo "Pushing SPK to root@${SYNO_HOST} (env var SYNO_HOST) ..."

View File

@@ -1 +1 @@
1.39.0
1.43.0

5
api.md
View File

@@ -503,7 +503,8 @@ Returns the enabled and advertised subnet routes for a device.
POST /api/v2/device/{deviceID}/authorized
```
Authorize a device. This call marks a device as authorized for tailnets where device authorization is required.
Authorize a device.
This call marks a device as authorized or revokes its authorization for tailnets where device authorization is required, according to the `authorized` field in the payload.
This returns a successful 2xx response with an empty JSON object in the response body.
@@ -515,7 +516,7 @@ The ID of the device.
#### `authorized` (required in `POST` body)
Specify whether the device is authorized. Only 'true' is currently supported.
Specify whether the device is authorized.
``` jsonc
{

View File

@@ -16,7 +16,7 @@ if [ -n "${TS_USE_TOOLCHAIN:-}" ]; then
go="./tool/go"
fi
eval `GOOS=$($go env GOHOSTOS) GOARCH=$($go env GOHOSTARCH) $go run ./cmd/mkversion`
eval `CGO_ENABLED=0 GOOS=$($go env GOHOSTOS) GOARCH=$($go env GOHOSTARCH) $go run ./cmd/mkversion`
if [ "$1" = "shellvars" ]; then
cat <<EOF
@@ -49,4 +49,4 @@ while [ "$#" -gt 1 ]; do
esac
done
exec ./tool/go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"
exec $go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"

View File

@@ -12,7 +12,6 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"tailscale.com/types/opt"
)
@@ -213,8 +212,20 @@ func (c *Client) DeleteDevice(ctx context.Context, deviceID string) (err error)
// AuthorizeDevice marks a device as authorized.
func (c *Client) AuthorizeDevice(ctx context.Context, deviceID string) error {
return c.SetAuthorized(ctx, deviceID, true)
}
// SetAuthorized marks a device as authorized or not.
func (c *Client) SetAuthorized(ctx context.Context, deviceID string, authorized bool) error {
params := &struct {
Authorized bool `json:"authorized"`
}{Authorized: authorized}
data, err := json.Marshal(params)
if err != nil {
return err
}
path := fmt.Sprintf("%s/api/v2/device/%s/authorized", c.baseURL(), url.PathEscape(deviceID))
req, err := http.NewRequestWithContext(ctx, "POST", path, strings.NewReader(`{"authorized":true}`))
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewBuffer(data))
if err != nil {
return err
}

View File

@@ -68,12 +68,32 @@ func (c *Client) Keys(ctx context.Context) ([]string, error) {
}
// CreateKey creates a new key for the current user. Currently, only auth keys
// can be created. Returns the key itself, which cannot be retrieved again
// can be created. It returns the secret key itself, which cannot be retrieved again
// later, and the key metadata.
func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (string, *Key, error) {
//
// To create a key with a specific expiry, use CreateKeyWithExpiry.
func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (keySecret string, keyMeta *Key, _ error) {
return c.CreateKeyWithExpiry(ctx, caps, 0)
}
// CreateKeyWithExpiry is like CreateKey, but allows specifying a expiration time.
//
// The time is truncated to a whole number of seconds. If zero, that means no expiration.
func (c *Client) CreateKeyWithExpiry(ctx context.Context, caps KeyCapabilities, expiry time.Duration) (keySecret string, keyMeta *Key, _ error) {
// convert expirySeconds to an int64 (seconds)
expirySeconds := int64(expiry.Seconds())
if expirySeconds < 0 {
return "", nil, fmt.Errorf("expiry must be positive")
}
if expirySeconds == 0 && expiry != 0 {
return "", nil, fmt.Errorf("non-zero expiry must be at least one second")
}
keyRequest := struct {
Capabilities KeyCapabilities `json:"capabilities"`
}{caps}
Capabilities KeyCapabilities `json:"capabilities"`
ExpirySeconds int64 `json:"expirySeconds,omitempty"`
}{caps, int64(expirySeconds)}
bs, err := json.Marshal(keyRequest)
if err != nil {
return "", nil, err

View File

@@ -81,7 +81,7 @@ func (m *manualCertManager) TLSConfig() *tls.Config {
return &tls.Config{
Certificates: nil,
NextProtos: []string{
"h2", "http/1.1", // enable HTTP/2
"http/1.1",
},
GetCertificate: m.getCertificate,
}

View File

@@ -3,7 +3,9 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
filippo.io/edwards25519/field from filippo.io/edwards25519
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
@@ -13,7 +15,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
github.com/fxamacker/cbor/v2 from tailscale.com/tka
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil+
github.com/golang/protobuf/ptypes/timestamp from github.com/prometheus/client_model/go
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
@@ -64,7 +65,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
google.golang.org/protobuf/runtime/protoiface from github.com/golang/protobuf/proto+
google.golang.org/protobuf/runtime/protoimpl from github.com/golang/protobuf/proto+
google.golang.org/protobuf/types/descriptorpb from google.golang.org/protobuf/reflect/protodesc
google.golang.org/protobuf/types/known/timestamppb from github.com/golang/protobuf/ptypes/timestamp+
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
nhooyr.io/websocket from tailscale.com/cmd/derper+
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket

View File

@@ -12,15 +12,19 @@ import (
"expvar"
"flag"
"fmt"
"html"
"io"
"log"
"math"
"net"
"net/http"
"net/http/pprof"
"net/netip"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
@@ -33,6 +37,7 @@ import (
"tailscale.com/net/stun"
"tailscale.com/tsweb"
"tailscale.com/types/key"
"tailscale.com/version"
)
var (
@@ -55,6 +60,8 @@ var (
acceptConnLimit = flag.Float64("accept-connection-limit", math.Inf(+1), "rate limit for accepting new connection")
acceptConnBurst = flag.Int("accept-connection-burst", math.MaxInt, "burst limit for accepting new connection")
debugAddr = flag.String("debug_addr", "", "listening address for debug server")
)
var (
@@ -136,6 +143,7 @@ func main() {
if *dev {
*addr = ":3340" // above the keys DERP
*debugAddr = "localhost:8383"
log.Printf("Running in dev mode.")
tsweb.DevMode = true
}
@@ -222,6 +230,11 @@ func main() {
go serveSTUN(listenHost, *stunPort)
}
if *debugAddr != "" {
log.Printf("running debug server on %s", *debugAddr)
go runDebugServer(*debugAddr, s)
}
quietLogger := log.New(logFilter{}, "", 0)
httpsrv := &http.Server{
Addr: *addr,
@@ -513,3 +526,79 @@ func (logFilter) Write(p []byte) (int, error) {
log.Printf("%s", p)
return len(p), nil
}
func runDebugServer(addr string, s *derp.Server) {
mux := http.NewServeMux()
mux.Handle("/debug/vars", http.DefaultServeMux) // serve out expvar
mux.HandleFunc("/debug/varz", tsweb.VarzHandler) // expvar but in prometheus format
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
mux.Handle("/debug/pprofrate", http.HandlerFunc(handlePprofRate))
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(200)
fmt.Fprintf(w, `<html><body>
<h1>DERP</h1>
<p>
This is a Tailscale DERP debug server.
</p>
<ul>
<li>Version: <pre style="display: inline">%s</pre>
<li><a href="/debug/vars">/debug/vars</a> (JSON)</li>
<li><a href="/debug/varz">/debug/varz</a> (Prometheus)</li>
<li><a href="/debug/pprof">/debug/pprof</a></li>
<li><a href="/debug/pprof/goroutine?debug=1">/debug/pprof/goroutine?debug=1</a> (goroutine summary)</li>
</ul>
`, html.EscapeString(version.Long()))
}))
srv := &http.Server{
Addr: addr,
Handler: mux,
}
if err := srv.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
log.Fatal(err)
}
}
}
// handlePprofRate adjusts the pprof rate.
//
// $ curl -d block=0 http://localhost:8383/debug/pprofrate
// $ curl -d mutex=0 http://localhost:8383/debug/pprofrate
//
// See:
//
// - https://pkg.go.dev/runtime#SetMutexProfileFraction
// - https://pkg.go.dev/runtime#SetBlockProfileRate
//
// And corp#5277.
func handlePprofRate(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "method not allowed; want POST", http.StatusMethodNotAllowed)
return
}
if s := r.FormValue("block"); s != "" {
v, err := strconv.Atoi(s)
if err != nil {
http.Error(w, "bad block value", http.StatusBadRequest)
return
}
runtime.SetBlockProfileRate(v)
fmt.Fprintf(w, "block set to %v\n", v)
}
if s := r.FormValue("mutex"); s != "" {
v, err := strconv.Atoi(s)
if err != nil {
http.Error(w, "bad mutex value", http.StatusBadRequest)
return
}
old := runtime.SetMutexProfileFraction(v)
fmt.Fprintf(w, "mutex changed from %v to %v\n", old, v)
}
}

25
cmd/dist/dist.go vendored
View File

@@ -13,15 +13,38 @@ import (
"tailscale.com/release/dist"
"tailscale.com/release/dist/cli"
"tailscale.com/release/dist/synology"
"tailscale.com/release/dist/unixpkgs"
)
var synologyPackageCenter bool
func getTargets() ([]dist.Target, error) {
return unixpkgs.Targets(), nil
var ret []dist.Target
ret = append(ret, unixpkgs.Targets()...)
// Synology packages can be built either for sideloading, or for
// distribution by Synology in their package center. When
// distributed through the package center, apps can request
// additional permissions to use a tuntap interface and control
// the NAS's network stack, rather than be forced to run in
// userspace mode.
//
// Since only we can provide packages to Synology for
// distribution, we default to building the "sideload" variant of
// packages that we distribute on pkgs.tailscale.com.
ret = append(ret, synology.Targets(synologyPackageCenter)...)
return ret, nil
}
func main() {
cmd := cli.CLI(getTargets)
for _, subcmd := range cmd.Subcommands {
if subcmd.Name == "build" {
subcmd.FlagSet.BoolVar(&synologyPackageCenter, "synology-package-center", false, "build synology packages with extra metadata for the official package center")
}
}
if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil && !errors.Is(err, flag.ErrHelp) {
log.Fatal(err)
}

View File

@@ -29,9 +29,9 @@ func main() {
tags := flag.String("tags", "", "comma-separated list of tags to apply to the authkey")
flag.Parse()
clientId := os.Getenv("TS_API_CLIENT_ID")
clientID := os.Getenv("TS_API_CLIENT_ID")
clientSecret := os.Getenv("TS_API_CLIENT_SECRET")
if clientId == "" || clientSecret == "" {
if clientID == "" || clientSecret == "" {
log.Fatal("TS_API_CLIENT_ID and TS_API_CLIENT_SECRET must be set")
}
@@ -39,22 +39,22 @@ func main() {
log.Fatal("at least one tag must be specified")
}
baseUrl := os.Getenv("TS_BASE_URL")
if baseUrl == "" {
baseUrl = "https://api.tailscale.com"
baseURL := os.Getenv("TS_BASE_URL")
if baseURL == "" {
baseURL = "https://api.tailscale.com"
}
credentials := clientcredentials.Config{
ClientID: clientId,
ClientID: clientID,
ClientSecret: clientSecret,
TokenURL: baseUrl + "/api/v2/oauth/token",
TokenURL: baseURL + "/api/v2/oauth/token",
Scopes: []string{"device"},
}
ctx := context.Background()
tsClient := tailscale.NewClient("-", nil)
tsClient.HTTPClient = credentials.Client(ctx)
tsClient.BaseURL = baseUrl
tsClient.BaseURL = baseURL
caps := tailscale.KeyCapabilities{
Devices: tailscale.KeyDeviceCapabilities{

View File

@@ -25,7 +25,6 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/transport"
"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -38,7 +37,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/yaml"
"tailscale.com/client/tailscale"
"tailscale.com/hostinfo"
@@ -48,6 +46,7 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/util/dnsname"
"tailscale.com/version"
)
func main() {
@@ -64,6 +63,7 @@ func main() {
clientIDPath = defaultEnv("CLIENT_ID_FILE", "")
clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "")
image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest")
priorityClassName = defaultEnv("PROXY_PRIORITY_CLASS_NAME", "")
tags = defaultEnv("PROXY_TAGS", "tag:k8s")
shouldRunAuthProxy = defaultBool("AUTH_PROXY", false)
)
@@ -183,32 +183,33 @@ waitOnline:
// the cache that sits a few layers below the builder stuff, which will
// implicitly filter what parts of the world the builder code gets to see at
// all.
nsFilter := cache.ObjectSelector{
Field: fields.SelectorFromSet(fields.Set{"metadata.namespace": tsNamespace}),
nsFilter := cache.ByObject{
Field: client.InNamespace(tsNamespace).AsSelector(),
}
restConfig := config.GetConfigOrDie()
mgr, err := manager.New(restConfig, manager.Options{
NewCache: cache.BuilderWithOptions(cache.Options{
SelectorsByObject: map[client.Object]cache.ObjectSelector{
Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{
&corev1.Secret{}: nsFilter,
&appsv1.StatefulSet{}: nsFilter,
},
}),
},
})
if err != nil {
startlog.Fatalf("could not create manager: %v", err)
}
sr := &ServiceReconciler{
Client: mgr.GetClient(),
tsClient: tsClient,
defaultTags: strings.Split(tags, ","),
operatorNamespace: tsNamespace,
proxyImage: image,
logger: zlog.Named("service-reconciler"),
Client: mgr.GetClient(),
tsClient: tsClient,
defaultTags: strings.Split(tags, ","),
operatorNamespace: tsNamespace,
proxyImage: image,
proxyPriorityClassName: priorityClassName,
logger: zlog.Named("service-reconciler"),
}
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request {
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(_ context.Context, o client.Object) []reconcile.Request {
ls := o.GetLabels()
if ls[LabelManaged] != "true" {
return nil
@@ -228,14 +229,14 @@ waitOnline:
err = builder.
ControllerManagedBy(mgr).
For(&corev1.Service{}).
Watches(&source.Kind{Type: &appsv1.StatefulSet{}}, reconcileFilter).
Watches(&source.Kind{Type: &corev1.Secret{}}, reconcileFilter).
Watches(&appsv1.StatefulSet{}, reconcileFilter).
Watches(&corev1.Secret{}, reconcileFilter).
Complete(sr)
if err != nil {
startlog.Fatalf("could not create controller: %v", err)
}
startlog.Infof("Startup complete, operator running")
startlog.Infof("Startup complete, operator running, version: %s", version.Long())
if shouldRunAuthProxy {
cfg, err := restConfig.TransportConfig()
if err != nil {
@@ -278,11 +279,12 @@ const (
// ServiceReconciler is a simple ControllerManagedBy example implementation.
type ServiceReconciler struct {
client.Client
tsClient tsClient
defaultTags []string
operatorNamespace string
proxyImage string
logger *zap.SugaredLogger
tsClient tsClient
defaultTags []string
operatorNamespace string
proxyImage string
proxyPriorityClassName string
logger *zap.SugaredLogger
}
type tsClient interface {
@@ -566,6 +568,9 @@ func (a *ServiceReconciler) getDeviceInfo(ctx context.Context, svc *corev1.Servi
if err != nil {
return "", "", err
}
if sec == nil {
return "", "", nil
}
id = string(sec.Data["device_id"])
if id == "" {
return "", "", nil
@@ -589,6 +594,7 @@ func (a *ServiceReconciler) newAuthKey(ctx context.Context, tags []string) (stri
},
},
}
key, _, err := a.tsClient.CreateKey(ctx, caps)
if err != nil {
return "", err
@@ -633,6 +639,7 @@ func (a *ServiceReconciler) reconcileSTS(ctx context.Context, logger *zap.Sugare
ss.Spec.Template.ObjectMeta.Labels = map[string]string{
"app": string(parentSvc.UID),
}
ss.Spec.Template.Spec.PriorityClassName = a.proxyPriorityClassName
logger.Debugf("reconciling statefulset %s/%s", ss.GetNamespace(), ss.GetName())
return createOrUpdate(ctx, a.Client, a.operatorNamespace, &ss, func(s *appsv1.StatefulSet) { s.Spec = ss.Spec })
}

View File

@@ -64,7 +64,7 @@ func TestLoadBalancerClass(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
// Normally the Tailscale proxy pod would come up here and write its info
// into the secret. Simulate that, then verify reconcile again and verify
@@ -110,6 +110,8 @@ func TestLoadBalancerClass(t *testing.T) {
mustUpdate(t, fc, "default", "test", func(s *corev1.Service) {
s.Spec.Type = corev1.ServiceTypeClusterIP
s.Spec.LoadBalancerClass = nil
})
mustUpdateStatus(t, fc, "default", "test", func(s *corev1.Service) {
// Fake client doesn't automatically delete the LoadBalancer status when
// changing away from the LoadBalancer type, we have to do
// controller-manager's work by hand.
@@ -185,7 +187,7 @@ func TestAnnotations(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
want := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
@@ -282,7 +284,7 @@ func TestAnnotationIntoLB(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
// Normally the Tailscale proxy pod would come up here and write its info
// into the secret. Simulate that, since it would have normally happened at
@@ -326,7 +328,7 @@ func TestAnnotationIntoLB(t *testing.T) {
expectReconciled(t, sr, "default", "test")
// None of the proxy machinery should have changed...
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
// ... but the service should have a LoadBalancer status.
want = &corev1.Service{
@@ -398,7 +400,7 @@ func TestLBIntoAnnotation(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
// Normally the Tailscale proxy pod would come up here and write its info
// into the secret. Simulate that, then verify reconcile again and verify
@@ -447,6 +449,8 @@ func TestLBIntoAnnotation(t *testing.T) {
}
s.Spec.Type = corev1.ServiceTypeClusterIP
s.Spec.LoadBalancerClass = nil
})
mustUpdateStatus(t, fc, "default", "test", func(s *corev1.Service) {
// Fake client doesn't automatically delete the LoadBalancer status when
// changing away from the LoadBalancer type, we have to do
// controller-manager's work by hand.
@@ -455,7 +459,7 @@ func TestLBIntoAnnotation(t *testing.T) {
expectReconciled(t, sr, "default", "test")
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
want = &corev1.Service{
TypeMeta: metav1.TypeMeta{
@@ -522,7 +526,7 @@ func TestCustomHostname(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "reindeer-flotilla"))
expectEqual(t, fc, expectedSTS(shortName, fullName, "reindeer-flotilla", ""))
want := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
@@ -581,6 +585,51 @@ func TestCustomHostname(t *testing.T) {
expectEqual(t, fc, want)
}
func TestCustomPriorityClassName(t *testing.T) {
fc := fake.NewFakeClient()
ft := &fakeTSClient{}
zl, err := zap.NewDevelopment()
if err != nil {
t.Fatal(err)
}
sr := &ServiceReconciler{
Client: fc,
tsClient: ft,
defaultTags: []string{"tag:k8s"},
operatorNamespace: "operator-ns",
proxyImage: "tailscale/tailscale",
proxyPriorityClassName: "tailscale-critical",
logger: zl.Sugar(),
}
// Create a service that we should manage, and check that the initial round
// of objects looks right.
mustCreate(t, fc, &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
// The apiserver is supposed to set the UID, but the fake client
// doesn't. So, set it explicitly because other code later depends
// on it being set.
UID: types.UID("1234-UID"),
Annotations: map[string]string{
"tailscale.com/expose": "true",
"tailscale.com/hostname": "custom-priority-class-name",
},
},
Spec: corev1.ServiceSpec{
ClusterIP: "10.20.30.40",
Type: corev1.ServiceTypeClusterIP,
},
})
expectReconciled(t, sr, "default", "test")
fullName, shortName := findGenName(t, fc, "default", "test")
expectEqual(t, fc, expectedSTS(shortName, fullName, "custom-priority-class-name", "tailscale-critical"))
}
func expectedSecret(name string) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
@@ -629,7 +678,7 @@ func expectedHeadlessService(name string) *corev1.Service {
}
}
func expectedSTS(stsName, secretName, hostname string) *appsv1.StatefulSet {
func expectedSTS(stsName, secretName, hostname, priorityClassName string) *appsv1.StatefulSet {
return &appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{
Kind: "StatefulSet",
@@ -658,6 +707,7 @@ func expectedSTS(stsName, secretName, hostname string) *appsv1.StatefulSet {
},
Spec: corev1.PodSpec{
ServiceAccountName: "proxies",
PriorityClassName: priorityClassName,
InitContainers: []corev1.Container{
{
Name: "sysctler",
@@ -731,6 +781,21 @@ func mustUpdate[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, n
}
}
func mustUpdateStatus[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
t.Helper()
obj := O(new(T))
if err := client.Get(context.Background(), types.NamespacedName{
Name: name,
Namespace: ns,
}, obj); err != nil {
t.Fatalf("getting %q: %v", name, err)
}
update(obj)
if err := client.Status().Update(context.Background(), obj); err != nil {
t.Fatalf("updating %q: %v", name, err)
}
}
func expectEqual[T any, O ptrObject[T]](t *testing.T, client client.Client, want O) {
t.Helper()
got := O(new(T))
@@ -814,7 +879,6 @@ func (c *fakeTSClient) CreateKey(ctx context.Context, caps tailscale.KeyCapabili
k := &tailscale.Key{
ID: "key",
Created: time.Now(),
Expires: time.Now().Add(24 * time.Hour),
Capabilities: caps,
}
return "secret-authkey", k, nil

View File

@@ -18,6 +18,7 @@ import (
"golang.org/x/net/dns/dnsmessage"
"inet.af/tcpproxy"
"tailscale.com/client/tailscale"
"tailscale.com/hostinfo"
"tailscale.com/net/netutil"
"tailscale.com/tsnet"
"tailscale.com/types/nettype"
@@ -36,6 +37,8 @@ func main() {
log.Fatal("no ports")
}
hostinfo.SetApp("sniproxy")
var s server
defer s.ts.Close()

View File

@@ -66,7 +66,7 @@ func isSystemdSystem() bool {
return false
}
switch distro.Get() {
case distro.QNAP, distro.Gokrazy, distro.Synology:
case distro.QNAP, distro.Gokrazy, distro.Synology, distro.Unraid:
return false
}
_, err := exec.LookPath("systemctl")

View File

@@ -16,6 +16,7 @@ import (
"path"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
@@ -39,6 +40,7 @@ serve https:<port> <mount-point> <source> [off]
serve tcp:<port> tcp://localhost:<local-port> [off]
serve tls-terminated-tcp:<port> tcp://localhost:<local-port> [off]
serve status [--json]
serve reset
`),
LongHelp: strings.TrimSpace(`
*** BETA; all of this is subject to change ***
@@ -86,6 +88,13 @@ EXAMPLES
}),
UsageFunc: usageFunc,
},
{
Name: "reset",
Exec: e.runServeReset,
ShortHelp: "reset current serve/funnel config",
FlagSet: e.newFlags("serve-reset", nil),
UsageFunc: usageFunc,
},
},
}
}
@@ -412,6 +421,7 @@ func cleanMountPoint(mount string) (string, error) {
if mount == "" {
return "", errors.New("mount point cannot be empty")
}
mount = cleanMinGWPathConversionIfNeeded(mount)
if !strings.HasPrefix(mount, "/") {
mount = "/" + mount
}
@@ -422,6 +432,26 @@ func cleanMountPoint(mount string) (string, error) {
return "", fmt.Errorf("invalid mount point %q", mount)
}
// cleanMinGWPathConversionIfNeeded strips the EXEPATH prefix from the given
// path if the path is a MinGW(ish) (Windows) shell arg.
//
// MinGW(ish) (Windows) shells perform POSIX-to-Windows path conversion
// converting the leading "/" of any shell arg to the EXEPATH, which mangles the
// mount point. Strip the EXEPATH prefix if it exists. #7963
//
// "/C:/Program Files/Git/foo" -> "/foo"
func cleanMinGWPathConversionIfNeeded(path string) string {
// Only do this on Windows.
if runtime.GOOS != "windows" {
return path
}
if _, ok := os.LookupEnv("MSYSTEM"); ok {
exepath := filepath.ToSlash(os.Getenv("EXEPATH"))
path = strings.TrimPrefix(path, exepath)
}
return path
}
func expandProxyTarget(source string) (string, error) {
if !strings.Contains(source, "://") {
source = "http://" + source
@@ -683,3 +713,15 @@ func elipticallyTruncate(s string, max int) string {
}
return s[:max-3] + "..."
}
// runServeReset clears out the current serve config.
//
// Usage:
// - tailscale serve reset
func (e *serveEnv) runServeReset(ctx context.Context, args []string) error {
if len(args) != 0 {
return flag.ErrHelp
}
sc := new(ipn.ServeConfig)
return e.lc.SetServeConfig(ctx, sc)
}

View File

@@ -224,7 +224,10 @@ func TestServeConfigMutations(t *testing.T) {
command: cmd("https:443 bar https://127.0.0.1:8443"),
want: nil, // nothing to save
})
add(step{reset: true})
add(step{ // try resetting using reset command
command: cmd("reset"),
want: &ipn.ServeConfig{},
})
add(step{
command: cmd("https:443 / https+insecure://127.0.0.1:3001"),
want: &ipn.ServeConfig{

View File

@@ -13,11 +13,13 @@ import (
"fmt"
"log"
"net/netip"
"net/url"
"os"
"os/signal"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"syscall"
@@ -26,6 +28,9 @@ import (
shellquote "github.com/kballard/go-shellquote"
"github.com/peterbourgon/ff/v3/ffcli"
qrcode "github.com/skip2/go-qrcode"
"golang.org/x/oauth2/clientcredentials"
"tailscale.com/client/tailscale"
"tailscale.com/envknob"
"tailscale.com/health/healthmsg"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
@@ -663,6 +668,10 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE
if err != nil {
return err
}
authKey, err = resolveAuthKey(ctx, authKey, upArgs.advertiseTags)
if err != nil {
return err
}
if err := localClient.Start(ctx, ipn.Options{
AuthKey: authKey,
UpdatePrefs: prefs,
@@ -1102,3 +1111,96 @@ func anyPeerAdvertisingRoutes(st *ipnstate.Status) bool {
}
return false
}
func init() {
// Required to use our client API. We're fine with the instability since the
// client lives in the same repo as this code.
tailscale.I_Acknowledge_This_API_Is_Unstable = true
}
// resolveAuthKey either returns v unchanged (in the common case) or, if it
// starts with "tskey-client-" (as Tailscale OAuth secrets do) parses it like
//
// tskey-client-xxxx[?ephemeral=false&bar&preauthorized=BOOL&baseURL=...]
//
// and does the OAuth2 dance to get and return an authkey. The "ephemeral"
// property defaults to true if unspecified. The "preauthorized" defaults to
// false. The "baseURL" defaults to https://api.tailscale.com.
// The passed in tags are required, and must be non-empty. These will be
// set on the authkey generated by the OAuth2 dance.
func resolveAuthKey(ctx context.Context, v, tags string) (string, error) {
if !strings.HasPrefix(v, "tskey-client-") {
return v, nil
}
if !envknob.Bool("TS_EXPERIMENT_OAUTH_AUTHKEY") {
return "", errors.New("oauth authkeys are in experimental status")
}
if tags == "" {
return "", errors.New("oauth authkeys require --advertise-tags")
}
clientSecret, named, _ := strings.Cut(v, "?")
attrs, err := url.ParseQuery(named)
if err != nil {
return "", err
}
for k := range attrs {
switch k {
case "ephemeral", "preauthorized", "baseURL":
default:
return "", fmt.Errorf("unknown attribute %q", k)
}
}
getBool := func(name string, def bool) (bool, error) {
v := attrs.Get(name)
if v == "" {
return def, nil
}
ret, err := strconv.ParseBool(v)
if err != nil {
return false, fmt.Errorf("invalid attribute boolean attribute %s value %q", name, v)
}
return ret, nil
}
ephemeral, err := getBool("ephemeral", true)
if err != nil {
return "", err
}
preauth, err := getBool("preauthorized", false)
if err != nil {
return "", err
}
baseURL := "https://api.tailscale.com"
if v := attrs.Get("baseURL"); v != "" {
baseURL = v
}
credentials := clientcredentials.Config{
ClientID: "some-client-id", // ignored
ClientSecret: clientSecret,
TokenURL: baseURL + "/api/v2/oauth/token",
Scopes: []string{"device"},
}
tsClient := tailscale.NewClient("-", nil)
tsClient.HTTPClient = credentials.Client(ctx)
tsClient.BaseURL = baseURL
caps := tailscale.KeyCapabilities{
Devices: tailscale.KeyDeviceCapabilities{
Create: tailscale.KeyDeviceCreateCapabilities{
Reusable: false,
Ephemeral: ephemeral,
Preauthorized: preauth,
Tags: strings.Split(tags, ","),
},
},
}
authkey, _, err := tsClient.CreateKey(ctx, caps)
if err != nil {
return "", err
}
return authkey, nil
}

View File

@@ -61,6 +61,8 @@ type tmplData struct {
TUNMode bool
IsSynology bool
DSMVersion int // 6 or 7, if IsSynology=true
IsUnraid bool
UnraidToken string
IPNVersion string
}
@@ -441,6 +443,8 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
TUNMode: st.TUN,
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
DSMVersion: distro.DSMVersion(),
IsUnraid: distro.Get() == distro.Unraid,
UnraidToken: os.Getenv("UNRAID_CSRF_TOKEN"),
IPNVersion: versionShort,
}
exitNodeRouteV4 := netip.MustParsePrefix("0.0.0.0/0")

View File

@@ -116,10 +116,12 @@
<a class="text-xs text-gray-500 hover:text-gray-600" href="{{ .LicensesURL }}">Open Source Licenses</a>
</footer>
<script>(function () {
const advertiseExitNode = {{.AdvertiseExitNode}};
const advertiseExitNode = {{ .AdvertiseExitNode }};
const isUnraid = {{ .IsUnraid }};
const unraidCsrfToken = "{{ .UnraidToken }}";
let fetchingUrl = false;
var data = {
AdvertiseRoutes: "{{.AdvertiseRoutes}}",
AdvertiseRoutes: "{{ .AdvertiseRoutes }}",
AdvertiseExitNode: advertiseExitNode,
Reauthenticate: false,
ForceLogout: false
@@ -141,15 +143,27 @@ function postData(e) {
}
const nextUrl = new URL(window.location);
nextUrl.search = nextParams.toString()
const url = nextUrl.toString();
let body = JSON.stringify(data);
let contentType = "application/json";
if (isUnraid) {
const params = new URLSearchParams();
params.append("csrf_token", unraidCsrfToken);
params.append("ts_data", JSON.stringify(data));
body = params.toString();
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
}
const url = nextUrl.toString();
fetch(url, {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Content-Type": contentType,
},
body: JSON.stringify(data)
body: body
}).then(res => res.json()).then(res => {
fetchingUrl = false;
const err = res["error"];
@@ -158,7 +172,11 @@ function postData(e) {
}
const url = res["url"];
if (url) {
document.location.href = url;
if(isUnraid) {
window.open(url, "_blank");
} else {
document.location.href = url;
}
} else {
location.reload();
}

View File

@@ -3,7 +3,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
filippo.io/edwards25519/field from filippo.io/edwards25519
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
@@ -152,9 +154,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/net/icmp from tailscale.com/net/ping
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
golang.org/x/net/ipv4 from golang.org/x/net/icmp+
golang.org/x/net/ipv6 from golang.org/x/net/icmp
golang.org/x/net/ipv6 from golang.org/x/net/icmp+
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
golang.org/x/oauth2 from golang.org/x/oauth2/clientcredentials
golang.org/x/oauth2/clientcredentials from tailscale.com/cmd/tailscale/cli
golang.org/x/oauth2/internal from golang.org/x/oauth2+
golang.org/x/sync/errgroup from tailscale.com/derp+
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from tailscale.com/net/netns+

View File

@@ -3,7 +3,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
filippo.io/edwards25519/field from filippo.io/edwards25519
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
@@ -12,7 +14,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/aws-sdk-go-v2 from github.com/aws/aws-sdk-go-v2/internal/ini
L github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/middleware+
L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/ipn/store/awsstore
L github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/ssm+
L github.com/aws/aws-sdk-go-v2/aws/middleware from github.com/aws/aws-sdk-go-v2/aws/retry+
L github.com/aws/aws-sdk-go-v2/aws/protocol/query from github.com/aws/aws-sdk-go-v2/service/sts
L github.com/aws/aws-sdk-go-v2/aws/protocol/restjson from github.com/aws/aws-sdk-go-v2/service/ssm+
@@ -38,6 +40,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/aws-sdk-go-v2/internal/rand from github.com/aws/aws-sdk-go-v2/aws+
L github.com/aws/aws-sdk-go-v2/internal/sdk from github.com/aws/aws-sdk-go-v2/aws+
L github.com/aws/aws-sdk-go-v2/internal/sdkio from github.com/aws/aws-sdk-go-v2/credentials/processcreds
L github.com/aws/aws-sdk-go-v2/internal/shareddefaults from github.com/aws/aws-sdk-go-v2/config+
L github.com/aws/aws-sdk-go-v2/internal/strings from github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4
L github.com/aws/aws-sdk-go-v2/internal/sync/singleflight from github.com/aws/aws-sdk-go-v2/aws
L github.com/aws/aws-sdk-go-v2/internal/timeconv from github.com/aws/aws-sdk-go-v2/aws/retry
@@ -48,16 +51,19 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/aws-sdk-go-v2/service/sso from github.com/aws/aws-sdk-go-v2/config+
L github.com/aws/aws-sdk-go-v2/service/sso/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sso
L github.com/aws/aws-sdk-go-v2/service/sso/types from github.com/aws/aws-sdk-go-v2/service/sso
L github.com/aws/aws-sdk-go-v2/service/ssooidc from github.com/aws/aws-sdk-go-v2/config+
L github.com/aws/aws-sdk-go-v2/service/ssooidc/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssooidc
L github.com/aws/aws-sdk-go-v2/service/ssooidc/types from github.com/aws/aws-sdk-go-v2/service/ssooidc
L github.com/aws/aws-sdk-go-v2/service/sts from github.com/aws/aws-sdk-go-v2/config+
L github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sts
L github.com/aws/aws-sdk-go-v2/service/sts/types from github.com/aws/aws-sdk-go-v2/credentials/stscreds+
L github.com/aws/smithy-go from github.com/aws/aws-sdk-go-v2/aws/protocol/restjson+
L github.com/aws/smithy-go/auth/bearer from github.com/aws/aws-sdk-go-v2/aws
L github.com/aws/smithy-go/auth/bearer from github.com/aws/aws-sdk-go-v2/aws+
L github.com/aws/smithy-go/context from github.com/aws/smithy-go/auth/bearer
L github.com/aws/smithy-go/document from github.com/aws/aws-sdk-go-v2/service/ssm+
L github.com/aws/smithy-go/encoding from github.com/aws/smithy-go/encoding/json+
L github.com/aws/smithy-go/encoding/httpbinding from github.com/aws/aws-sdk-go-v2/aws/protocol/query+
L github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssm+
L github.com/aws/smithy-go/encoding/xml from github.com/aws/aws-sdk-go-v2/service/sts
L github.com/aws/smithy-go/internal/sync/singleflight from github.com/aws/smithy-go/auth/bearer
L github.com/aws/smithy-go/io from github.com/aws/aws-sdk-go-v2/feature/ec2/imds+
@@ -73,6 +79,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
github.com/fxamacker/cbor/v2 from tailscale.com/tka
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
@@ -93,7 +100,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/klauspost/compress/flate from nhooyr.io/websocket
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd
github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd+
github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd
github.com/klauspost/compress/zstd from tailscale.com/smallzstd
github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd
@@ -105,12 +112,17 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
L github.com/pierrec/lz4/v4 from github.com/u-root/uio/uio
L github.com/pierrec/lz4/v4/internal/lz4block from github.com/pierrec/lz4/v4+
L github.com/pierrec/lz4/v4/internal/lz4errors from github.com/pierrec/lz4/v4+
L github.com/pierrec/lz4/v4/internal/lz4stream from github.com/pierrec/lz4/v4
L github.com/pierrec/lz4/v4/internal/xxh32 from github.com/pierrec/lz4/v4/internal/lz4stream
W github.com/pkg/errors from github.com/tailscale/certstore
LD github.com/pkg/sftp from tailscale.com/ssh/tailssh
LD github.com/pkg/sftp/internal/encoding/ssh/filexfer from github.com/pkg/sftp
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
LD github.com/tailscale/golang-x-crypto/chacha20 from github.com/tailscale/golang-x-crypto/ssh
LD 💣 github.com/tailscale/golang-x-crypto/internal/subtle from github.com/tailscale/golang-x-crypto/chacha20
LD 💣 github.com/tailscale/golang-x-crypto/internal/alias from github.com/tailscale/golang-x-crypto/chacha20
LD github.com/tailscale/golang-x-crypto/ssh from tailscale.com/ipn/ipnlocal+
LD github.com/tailscale/golang-x-crypto/ssh/internal/bcrypt_pbkdf from github.com/tailscale/golang-x-crypto/ssh
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
@@ -245,7 +257,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnauth+
tailscale.com/net/netutil from tailscale.com/ipn/ipnlocal+
tailscale.com/net/packet from tailscale.com/net/tstun+
tailscale.com/net/ping from tailscale.com/net/netcheck
tailscale.com/net/ping from tailscale.com/net/netcheck+
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
tailscale.com/net/routetable from tailscale.com/doctor/routetable
@@ -270,7 +282,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
tailscale.com/tka from tailscale.com/ipn/ipnlocal+
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/tstime from tailscale.com/wgengine/magicsock
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
tailscale.com/tstime from tailscale.com/wgengine/magicsock+
💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+
tailscale.com/tstime/rate from tailscale.com/wgengine/filter+
tailscale.com/tsweb/varz from tailscale.com/cmd/tailscaled

View File

@@ -18,7 +18,7 @@ import (
func configureTaildrop(logf logger.Logf, lb *ipnlocal.LocalBackend) {
dg := distro.Get()
switch dg {
case distro.Synology, distro.TrueNAS, distro.QNAP:
case distro.Synology, distro.TrueNAS, distro.QNAP, distro.Unraid:
// See if they have a "Taildrop" share.
// See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319
path, err := findTaildropDir(dg)
@@ -42,6 +42,8 @@ func findTaildropDir(dg distro.Distro) (string, error) {
return findTrueNASTaildropDir(name)
case distro.QNAP:
return findQnapTaildropDir(name)
case distro.Unraid:
return findUnraidTaildropDir(name)
}
return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg)
}
@@ -103,3 +105,25 @@ func findQnapTaildropDir(name string) (string, error) {
}
return "", fmt.Errorf("shared folder %q not found", name)
}
// findUnraidTaildropDir looks for a directory linked at
// /var/lib/tailscale/Taildrop. This is a symlink to the
// path specified by the user in the Unraid Web UI
func findUnraidTaildropDir(name string) (string, error) {
dir := fmt.Sprintf("/var/lib/tailscale/%s", name)
_, err := os.Stat(dir)
if err != nil {
return "", fmt.Errorf("symlink %q not found", name)
}
fullpath, err := filepath.EvalSymlinks(dir)
if err != nil {
return "", fmt.Errorf("symlink %q to shared folder not valid", name)
}
fi, err := os.Stat(fullpath)
if err == nil && fi.IsDir() {
return dir, nil // return the symlink
}
return "", fmt.Errorf("shared folder %q not found", name)
}

View File

@@ -50,6 +50,7 @@ import (
"tailscale.com/safesocket"
"tailscale.com/smallzstd"
"tailscale.com/syncs"
"tailscale.com/tsd"
"tailscale.com/tsweb/varz"
"tailscale.com/types/flagtype"
"tailscale.com/types/logger"
@@ -330,12 +331,16 @@ var debugMux *http.ServeMux
func run() error {
var logf logger.Logf = log.Printf
sys := new(tsd.System)
netMon, err := netmon.New(func(format string, args ...any) {
logf(format, args...)
})
if err != nil {
return fmt.Errorf("netmon.New: %w", err)
}
sys.Set(netMon)
pol := logpolicy.New(logtail.CollectionNode, netMon)
pol.SetVerbosityLevel(args.verbose)
@@ -386,10 +391,10 @@ func run() error {
debugMux = newDebugMux()
}
return startIPNServer(context.Background(), logf, pol.PublicID, netMon)
return startIPNServer(context.Background(), logf, pol.PublicID, sys)
}
func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) error {
func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) error {
ln, err := safesocket.Listen(args.socketpath)
if err != nil {
return fmt.Errorf("safesocket.Listen: %v", err)
@@ -415,7 +420,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
}
}()
srv := ipnserver.New(logf, logID, netMon)
srv := ipnserver.New(logf, logID, sys.NetMon.Get())
if debugMux != nil {
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)
}
@@ -433,7 +438,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
return
}
}
lb, err := getLocalBackend(ctx, logf, logID, netMon)
lb, err := getLocalBackend(ctx, logf, logID, sys)
if err == nil {
logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond))
srv.SetLocalBackend(lb)
@@ -457,31 +462,28 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
return nil
}
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) (_ *ipnlocal.LocalBackend, retErr error) {
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) (_ *ipnlocal.LocalBackend, retErr error) {
if logPol != nil {
logPol.Logtail.SetNetMon(netMon)
logPol.Logtail.SetNetMon(sys.NetMon.Get())
}
socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr)
dialer := &tsdial.Dialer{Logf: logf} // mutated below (before used)
e, onlyNetstack, err := createEngine(logf, netMon, dialer)
sys.Set(dialer)
onlyNetstack, err := createEngine(logf, sys)
if err != nil {
return nil, fmt.Errorf("createEngine: %w", err)
}
if _, ok := e.(wgengine.ResolvingEngine).GetResolver(); !ok {
panic("internal error: exit node resolver not wired up")
}
if debugMux != nil {
if ig, ok := e.(wgengine.InternalsGetter); ok {
if _, mc, _, ok := ig.GetInternals(); ok {
debugMux.HandleFunc("/debug/magicsock", mc.ServeHTTPDebug)
}
if ms, ok := sys.MagicSock.GetOK(); ok {
debugMux.HandleFunc("/debug/magicsock", ms.ServeHTTPDebug)
}
go runDebugServer(debugMux, args.debug)
}
ns, err := newNetstack(logf, dialer, e)
ns, err := newNetstack(logf, sys)
if err != nil {
return nil, fmt.Errorf("newNetstack: %w", err)
}
@@ -489,6 +491,7 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
ns.ProcessSubnets = onlyNetstack || handleSubnetsInNetstack()
if onlyNetstack {
e := sys.Engine.Get()
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
_, ok := e.PeerForIP(ip)
return ok
@@ -519,16 +522,15 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
tshttpproxy.SetSelfProxy(addrs...)
}
e = wgengine.NewWatchdog(e)
opts := ipnServerOpts()
store, err := store.New(logf, statePathOrDefault())
if err != nil {
return nil, fmt.Errorf("store.New: %w", err)
}
sys.Set(store)
lb, err := ipnlocal.NewLocalBackend(logf, logID, store, dialer, e, opts.LoginFlags)
lb, err := ipnlocal.NewLocalBackend(logf, logID, sys, opts.LoginFlags)
if err != nil {
return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err)
}
@@ -554,21 +556,21 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
//
// onlyNetstack is true if the user has explicitly requested that we use netstack
// for all networking.
func createEngine(logf logger.Logf, netMon *netmon.Monitor, dialer *tsdial.Dialer) (e wgengine.Engine, onlyNetstack bool, err error) {
func createEngine(logf logger.Logf, sys *tsd.System) (onlyNetstack bool, err error) {
if args.tunname == "" {
return nil, false, errors.New("no --tun value specified")
return false, errors.New("no --tun value specified")
}
var errs []error
for _, name := range strings.Split(args.tunname, ",") {
logf("wgengine.NewUserspaceEngine(tun %q) ...", name)
e, onlyNetstack, err = tryEngine(logf, netMon, dialer, name)
onlyNetstack, err = tryEngine(logf, sys, name)
if err == nil {
return e, onlyNetstack, nil
return onlyNetstack, nil
}
logf("wgengine.NewUserspaceEngine(tun %q) error: %v", name, err)
errs = append(errs, err)
}
return nil, false, multierr.New(errs...)
return false, multierr.New(errs...)
}
// handleSubnetsInNetstack reports whether netstack should handle subnet routers
@@ -593,21 +595,23 @@ func handleSubnetsInNetstack() bool {
var tstunNew = tstun.New
func tryEngine(logf logger.Logf, netMon *netmon.Monitor, dialer *tsdial.Dialer, name string) (e wgengine.Engine, onlyNetstack bool, err error) {
func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack bool, err error) {
conf := wgengine.Config{
ListenPort: args.port,
NetMon: netMon,
Dialer: dialer,
ListenPort: args.port,
NetMon: sys.NetMon.Get(),
Dialer: sys.Dialer.Get(),
SetSubsystem: sys.Set,
}
onlyNetstack = name == "userspace-networking"
netstackSubnetRouter := onlyNetstack // but mutated later on some platforms
netns.SetEnabled(!onlyNetstack)
if args.birdSocketPath != "" && createBIRDClient != nil {
log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath)
conf.BIRDClient, err = createBIRDClient(args.birdSocketPath)
if err != nil {
return nil, false, fmt.Errorf("createBIRDClient: %w", err)
return false, fmt.Errorf("createBIRDClient: %w", err)
}
}
if onlyNetstack {
@@ -620,44 +624,55 @@ func tryEngine(logf logger.Logf, netMon *netmon.Monitor, dialer *tsdial.Dialer,
// TODO(bradfitz): add a Synology-specific DNS manager.
conf.DNS, err = dns.NewOSConfigurator(logf, "") // empty interface name
if err != nil {
return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
}
}
} else {
dev, devName, err := tstunNew(logf, name)
if err != nil {
tstun.Diagnose(logf, name, err)
return nil, false, fmt.Errorf("tstun.New(%q): %w", name, err)
return false, fmt.Errorf("tstun.New(%q): %w", name, err)
}
conf.Tun = dev
if strings.HasPrefix(name, "tap:") {
conf.IsTAP = true
e, err := wgengine.NewUserspaceEngine(logf, conf)
return e, false, err
if err != nil {
return false, err
}
sys.Set(e)
return false, err
}
r, err := router.New(logf, dev, netMon)
r, err := router.New(logf, dev, sys.NetMon.Get())
if err != nil {
dev.Close()
return nil, false, fmt.Errorf("creating router: %w", err)
return false, fmt.Errorf("creating router: %w", err)
}
d, err := dns.NewOSConfigurator(logf, devName)
if err != nil {
dev.Close()
r.Close()
return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
}
conf.DNS = d
conf.Router = r
if handleSubnetsInNetstack() {
conf.Router = netstack.NewSubnetRouterWrapper(conf.Router)
netstackSubnetRouter = true
}
sys.Set(conf.Router)
}
e, err = wgengine.NewUserspaceEngine(logf, conf)
e, err := wgengine.NewUserspaceEngine(logf, conf)
if err != nil {
return nil, onlyNetstack, err
return onlyNetstack, err
}
return e, onlyNetstack, nil
e = wgengine.NewWatchdog(e)
sys.Set(e)
sys.NetstackRouter.Set(netstackSubnetRouter)
return onlyNetstack, nil
}
func newDebugMux() *http.ServeMux {
@@ -687,12 +702,8 @@ func runDebugServer(mux *http.ServeMux, addr string) {
}
}
func newNetstack(logf logger.Logf, dialer *tsdial.Dialer, e wgengine.Engine) (*netstack.Impl, error) {
tunDev, magicConn, dns, ok := e.(wgengine.InternalsGetter).GetInternals()
if !ok {
return nil, fmt.Errorf("%T is not a wgengine.InternalsGetter", e)
}
return netstack.Create(logf, tunDev, e, magicConn, dialer, dns)
func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
return netstack.Create(logf, sys.Tun.Get(), sys.Engine.Get(), sys.MagicSock.Get(), sys.Dialer.Get(), sys.DNSManager.Get())
}
// mustStartProxyListeners creates listeners for local SOCKS and HTTP

View File

@@ -47,6 +47,7 @@ import (
"tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/net/tstun"
"tailscale.com/tsd"
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/util/winutil"
@@ -125,6 +126,10 @@ var syslogf logger.Logf = logger.Discard
// At this point we're still the parent process that
// Windows started.
func runWindowsService(pol *logpolicy.Policy) error {
go func() {
winutil.LogSupportInfo(log.Printf)
}()
if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 {
syslog, err := eventlog.Open(serviceName)
if err == nil {
@@ -292,13 +297,15 @@ func beWindowsSubprocess() bool {
}
}()
sys := new(tsd.System)
netMon, err := netmon.New(log.Printf)
if err != nil {
log.Printf("Could not create netMon: %v", err)
netMon = nil
log.Fatalf("Could not create netMon: %v", err)
}
sys.Set(netMon)
publicLogID, _ := logid.ParsePublicID(logID)
err = startIPNServer(ctx, log.Printf, publicLogID, netMon)
err = startIPNServer(ctx, log.Printf, publicLogID, sys)
if err != nil {
log.Fatalf("ipnserver: %v", err)
}

View File

@@ -37,6 +37,7 @@ import (
"tailscale.com/safesocket"
"tailscale.com/smallzstd"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/wgengine"
"tailscale.com/wgengine/netstack"
"tailscale.com/words"
@@ -96,19 +97,19 @@ func newIPN(jsConfig js.Value) map[string]any {
logtail := logtail.NewLogger(c, log.Printf)
logf := logtail.Logf
sys := new(tsd.System)
sys.Set(store)
dialer := &tsdial.Dialer{Logf: logf}
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Dialer: dialer,
Dialer: dialer,
SetSubsystem: sys.Set,
})
if err != nil {
log.Fatal(err)
}
sys.Set(eng)
tunDev, magicConn, dnsManager, ok := eng.(wgengine.InternalsGetter).GetInternals()
if !ok {
log.Fatalf("%T is not a wgengine.InternalsGetter", eng)
}
ns, err := netstack.Create(logf, tunDev, eng, magicConn, dialer, dnsManager)
ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
if err != nil {
log.Fatalf("netstack.Create: %v", err)
}
@@ -121,10 +122,11 @@ func newIPN(jsConfig js.Value) map[string]any {
dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
return ns.DialContextTCP(ctx, dst)
}
sys.NetstackRouter.Set(true)
logid := lpc.PublicID
srv := ipnserver.New(logf, logid, nil /* no netMon */)
lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, controlclient.LoginEphemeral)
lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginEphemeral)
if err != nil {
log.Fatalf("ipnlocal.NewLocalBackend: %v", err)
}

View File

@@ -9,7 +9,7 @@ import (
"net/netip"
)
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone --clone-only-type=OnlyGetClone
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded --clone-only-type=OnlyGetClone
type StructWithoutPtrs struct {
Int int
@@ -61,3 +61,8 @@ type StructWithSlices struct {
type OnlyGetClone struct {
SinViewerPorFavor bool
}
type StructWithEmbedded struct {
A *StructWithPtrs
StructWithSlices
}

View File

@@ -211,3 +211,22 @@ func (src *OnlyGetClone) Clone() *OnlyGetClone {
var _OnlyGetCloneCloneNeedsRegeneration = OnlyGetClone(struct {
SinViewerPorFavor bool
}{})
// Clone makes a deep copy of StructWithEmbedded.
// The result aliases no memory with the original.
func (src *StructWithEmbedded) Clone() *StructWithEmbedded {
if src == nil {
return nil
}
dst := new(StructWithEmbedded)
*dst = *src
dst.A = src.A.Clone()
dst.StructWithSlices = *src.StructWithSlices.Clone()
return dst
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _StructWithEmbeddedCloneNeedsRegeneration = StructWithEmbedded(struct {
A *StructWithPtrs
StructWithSlices
}{})

View File

@@ -14,7 +14,7 @@ import (
"tailscale.com/types/views"
)
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded
// View returns a readonly view of StructWithPtrs.
func (p *StructWithPtrs) View() StructWithPtrsView {
@@ -325,3 +325,59 @@ var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
Prefixes []netip.Prefix
Data []byte
}{})
// View returns a readonly view of StructWithEmbedded.
func (p *StructWithEmbedded) View() StructWithEmbeddedView {
return StructWithEmbeddedView{ж: p}
}
// StructWithEmbeddedView provides a read-only view over StructWithEmbedded.
//
// Its methods should only be called if `Valid()` returns true.
type StructWithEmbeddedView struct {
// ж is the underlying mutable value, named with a hard-to-type
// character that looks pointy like a pointer.
// It is named distinctively to make you think of how dangerous it is to escape
// to callers. You must not let callers be able to mutate it.
ж *StructWithEmbedded
}
// Valid reports whether underlying value is non-nil.
func (v StructWithEmbeddedView) Valid() bool { return v.ж != nil }
// AsStruct returns a clone of the underlying value which aliases no memory with
// the original.
func (v StructWithEmbeddedView) AsStruct() *StructWithEmbedded {
if v.ж == nil {
return nil
}
return v.ж.Clone()
}
func (v StructWithEmbeddedView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
func (v *StructWithEmbeddedView) UnmarshalJSON(b []byte) error {
if v.ж != nil {
return errors.New("already initialized")
}
if len(b) == 0 {
return nil
}
var x StructWithEmbedded
if err := json.Unmarshal(b, &x); err != nil {
return err
}
v.ж = &x
return nil
}
func (v StructWithEmbeddedView) A() StructWithPtrsView { return v.ж.A.View() }
func (v StructWithEmbeddedView) StructWithSlices() StructWithSlicesView {
return v.ж.StructWithSlices.View()
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _StructWithEmbeddedViewNeedsRegeneration = StructWithEmbedded(struct {
A *StructWithPtrs
StructWithSlices
}{})

View File

@@ -61,6 +61,7 @@ import (
type Direct struct {
httpc *http.Client // HTTP client used to talk to tailcontrol
dialer *tsdial.Dialer
dnsCache *dnscache.Resolver
serverURL string // URL of the tailcontrol server
timeNow func() time.Time
lastPrintMap time.Time
@@ -199,6 +200,14 @@ func NewDirect(opts Options) (*Direct, error) {
opts.Logf = log.Printf
}
dnsCache := &dnscache.Resolver{
Forward: dnscache.Get().Forward, // use default cache's forwarder
UseLastGood: true,
LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, opts.NetMon),
Logf: opts.Logf,
NetMon: opts.NetMon,
}
httpc := opts.HTTPTestClient
if httpc == nil && runtime.GOOS == "js" {
// In js/wasm, net/http.Transport (as of Go 1.18) will
@@ -208,13 +217,6 @@ func NewDirect(opts Options) (*Direct, error) {
httpc = http.DefaultClient
}
if httpc == nil {
dnsCache := &dnscache.Resolver{
Forward: dnscache.Get().Forward, // use default cache's forwarder
UseLastGood: true,
LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, opts.NetMon),
Logf: opts.Logf,
NetMon: opts.NetMon,
}
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
@@ -250,6 +252,7 @@ func NewDirect(opts Options) (*Direct, error) {
onControlTime: opts.OnControlTime,
c2nHandler: opts.C2NHandler,
dialer: opts.Dialer,
dnsCache: dnsCache,
dialPlan: opts.DialPlan,
}
if opts.Hostinfo == nil {
@@ -1509,7 +1512,16 @@ func (c *Direct) getNoiseClient() (*NoiseClient, error) {
return nil, err
}
c.logf("creating new noise client")
nc, err := NewNoiseClient(k, serverNoiseKey, c.serverURL, c.dialer, c.logf, c.netMon, dp)
nc, err := NewNoiseClient(NoiseOpts{
PrivKey: k,
ServerPubKey: serverNoiseKey,
ServerURL: c.serverURL,
Dialer: c.dialer,
DNSCache: c.dnsCache,
Logf: c.logf,
NetMon: c.netMon,
DialPlan: dp,
})
if err != nil {
return nil, err
}

View File

@@ -19,6 +19,7 @@ import (
"golang.org/x/net/http2"
"tailscale.com/control/controlbase"
"tailscale.com/control/controlhttp"
"tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg"
@@ -158,6 +159,7 @@ type NoiseClient struct {
sfDial singleflight.Group[struct{}, *noiseConn]
dialer *tsdial.Dialer
dnsCache *dnscache.Resolver
privKey key.MachinePrivate
serverPubKey key.MachinePublic
host string // the host part of serverURL
@@ -179,13 +181,39 @@ type NoiseClient struct {
connPool map[int]*noiseConn // active connections not yet closed; see noiseConn.Close
}
// NoiseOpts contains options for the NewNoiseClient function. All fields are
// required unless otherwise specified.
type NoiseOpts struct {
// PrivKey is this node's private key.
PrivKey key.MachinePrivate
// ServerPubKey is the public key of the server.
ServerPubKey key.MachinePublic
// ServerURL is the URL of the server to connect to.
ServerURL string
// Dialer's SystemDial function is used to connect to the server.
Dialer *tsdial.Dialer
// DNSCache is the caching Resolver to use to connect to the server.
//
// This field can be nil.
DNSCache *dnscache.Resolver
// Logf is the log function to use. This field can be nil.
Logf logger.Logf
// NetMon is the network monitor that, if set, will be used to get the
// network interface state. This field can be nil; if so, the current
// state will be looked up dynamically.
NetMon *netmon.Monitor
// DialPlan, if set, is a function that should return an explicit plan
// on how to connect to the server.
DialPlan func() *tailcfg.ControlDialPlan
}
// NewNoiseClient returns a new noiseClient for the provided server and machine key.
// serverURL is of the form https://<host>:<port> (no trailing slash).
//
// netMon may be nil, if non-nil it's used to do faster interface lookups.
// dialPlan may be nil
func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string, dialer *tsdial.Dialer, logf logger.Logf, netMon *netmon.Monitor, dialPlan func() *tailcfg.ControlDialPlan) (*NoiseClient, error) {
u, err := url.Parse(serverURL)
func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
u, err := url.Parse(opts.ServerURL)
if err != nil {
return nil, err
}
@@ -205,16 +233,18 @@ func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic,
httpPort = "80"
httpsPort = "443"
}
np := &NoiseClient{
serverPubKey: serverPubKey,
privKey: privKey,
serverPubKey: opts.ServerPubKey,
privKey: opts.PrivKey,
host: u.Hostname(),
httpPort: httpPort,
httpsPort: httpsPort,
dialer: dialer,
dialPlan: dialPlan,
logf: logf,
netMon: netMon,
dialer: opts.Dialer,
dnsCache: opts.DNSCache,
dialPlan: opts.DialPlan,
logf: opts.Logf,
netMon: opts.NetMon,
}
// Create the HTTP/2 Transport using a net/http.Transport
@@ -373,6 +403,7 @@ func (nc *NoiseClient) dial() (*noiseConn, error) {
ControlKey: nc.serverPubKey,
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
Dialer: nc.dialer.SystemDial,
DNSCache: nc.dnsCache,
DialPlan: dialPlan,
Logf: nc.logf,
NetMon: nc.netMon,

View File

@@ -74,7 +74,12 @@ func (tt noiseClientTest) run(t *testing.T) {
defer hs.Close()
dialer := new(tsdial.Dialer)
nc, err := NewNoiseClient(clientPrivate, serverPrivate.Public(), hs.URL, dialer, nil, nil, nil)
nc, err := NewNoiseClient(NoiseOpts{
PrivKey: clientPrivate,
ServerPubKey: serverPrivate.Public(),
ServerURL: hs.URL,
Dialer: dialer,
})
if err != nil {
t.Fatal(err)
}

View File

@@ -374,6 +374,22 @@ func (a *Dialer) dialURL(ctx context.Context, u *url.URL, addr netip.Addr) (*Cli
}, nil
}
// resolver returns a.DNSCache if non-nil or a new *dnscache.Resolver
// otherwise.
func (a *Dialer) resolver() *dnscache.Resolver {
if a.DNSCache != nil {
return a.DNSCache
}
return &dnscache.Resolver{
Forward: dnscache.Get().Forward,
LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon),
UseLastGood: true,
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
NetMon: a.NetMon,
}
}
// tryURLUpgrade connects to u, and tries to upgrade it to a net.Conn. If addr
// is valid, then no DNS is used and the connection will be made to the
// provided address.
@@ -392,13 +408,7 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr,
NetMon: a.NetMon,
}
} else {
dns = &dnscache.Resolver{
Forward: dnscache.Get().Forward,
LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon),
UseLastGood: true,
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
NetMon: a.NetMon,
}
dns = a.resolver()
}
var dialer dnscache.DialContextFunc

View File

@@ -67,6 +67,11 @@ type Dialer struct {
// If not specified, this defaults to net.Dialer.DialContext.
Dialer dnscache.DialContextFunc
// DNSCache is the caching Resolver used by this Dialer.
//
// If not specified, a new Resolver is created per attempt.
DNSCache *dnscache.Resolver
// Logf, if set, is a logging function to use; if unset, logs are
// dropped.
Logf logger.Logf

61
derp/README.md Normal file
View File

@@ -0,0 +1,61 @@
# DERP
This directory (and subdirectories) contain the DERP code. The server itself is
in `../cmd/derper`.
DERP is a packet relay system (client and servers) where peers are addressed
using WireGuard public keys instead of IP addresses.
It relays two types of packets:
* "Disco" discovery messages (see `../disco`) as the a side channel during [NAT
traversal](https://tailscale.com/blog/how-nat-traversal-works/).
* Encrypted WireGuard packets as the fallback of last resort when UDP is blocked
or NAT traversal fails.
## DERP Map
Each client receives a "[DERP
Map](https://pkg.go.dev/tailscale.com/tailcfg#DERPMap)" from the coordination
server describing the DERP servers the client should try to use.
The client picks its home "DERP home" based on latency. This is done to keep
costs low by avoid using cloud load balancers (pricey) or anycast, which would
necessarily require server-side routing between DERP regions.
Clients pick their DERP home and report it to the coordination server which
shares it to all the peers in the tailnet. When a peer wants to send a packet
and it doesn't already have a WireGuard session open, it sends disco messages
(some direct, and some over DERP), trying to do the NAT traversal. The client
will make connections to multiple DERP regions as needed. Only the DERP home
region connection needs to be alive forever.
## DERP Regions
Tailscale runs 1 or more DERP nodes (instances of `cmd/derper`) in various
geographic regions to make sure users have low latency to their DERP home.
Regions generally have multiple nodes per region "meshed" (routing to each
other) together for redundancy: it allows for cloud failures or upgrades without
kicking users out to a higher latency region. Instead, clients will reconnect to
the next node in the region. Each node in the region is required to to be meshed
with every other node in the region and forward packets to the other nodes in
the region. Packets are forwarded only one hop within the region. There is no
routing between regions. The assumption is that the mesh TCP connections are
over a VPC that's very fast, low latency, and not charged per byte. The
coordination server assigns the list of nodes in a region as a function of the
tailnet, so all nodes within a tailnet should generally be on the same node and
not require forwarding. Only after a failure do clients of a particular tailnet
get split between nodes in a region and require inter-node forwarding. But over
time it balances back out. There's also an admin-only DERP frame type to force
close the TCP connection of a particular client to force them to reconnect to
their primary if the operator wants to force things to balance out sooner.
(Using the `(*derphttp.Client).ClosePeer` method, as used by Tailscale's
internal rarely-used `cmd/derpprune` maintenance tool)
We generally run a minimum of three nodes in a region not for quorum reasons
(there's no voting) but just because two is too uncomfortably few for cascading
failure reasons: if you're running two nodes at 51% load (CPU, memory, etc) and
then one fails, that makes the second one fail. With three or more nodes, you
can run each node a bit hotter.

View File

@@ -498,7 +498,7 @@ func (s *Server) registerClient(c *sclient) {
switch set := set.(type) {
case nil:
s.clients[c.key] = singleClient{c}
c.debug("register single client")
c.debugLogf("register single client")
case singleClient:
s.dupClientKeys.Add(1)
s.dupClientConns.Add(2) // both old and new count
@@ -514,7 +514,7 @@ func (s *Server) registerClient(c *sclient) {
},
sendHistory: []*sclient{old},
}
c.debug("register duplicate client")
c.debugLogf("register duplicate client")
case *dupClientSet:
s.dupClientConns.Add(1) // the gauge
s.dupClientConnTotal.Add(1) // the counter
@@ -522,7 +522,7 @@ func (s *Server) registerClient(c *sclient) {
set.set[c] = true
set.last = c
set.sendHistory = append(set.sendHistory, c)
c.debug("register another duplicate client")
c.debugLogf("register another duplicate client")
}
if _, ok := s.clientsMesh[c.key]; !ok {
@@ -555,7 +555,7 @@ func (s *Server) unregisterClient(c *sclient) {
case nil:
c.logf("[unexpected]; clients map is empty")
case singleClient:
c.logf("removed connection")
c.debugLogf("removed connection")
delete(s.clients, c.key)
if v, ok := s.clientsMesh[c.key]; ok && v == nil {
delete(s.clientsMesh, c.key)
@@ -563,7 +563,7 @@ func (s *Server) unregisterClient(c *sclient) {
}
s.broadcastPeerStateChangeLocked(c.key, false)
case *dupClientSet:
c.debug("removed duplicate client")
c.debugLogf("removed duplicate client")
if set.removeClient(c) {
s.dupClientConns.Add(-1)
} else {
@@ -712,9 +712,12 @@ func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, rem
if clientInfo != nil {
c.info = *clientInfo
if envknob.Bool("DERP_PROBER_DEBUG_LOGS") && clientInfo.IsProber {
c.debugLogging = true
c.debug = true
}
}
if s.debug {
c.debug = true
}
s.registerClient(c)
defer s.unregisterClient(c)
@@ -727,6 +730,12 @@ func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, rem
return c.run(ctx)
}
func (s *Server) debugLogf(format string, v ...any) {
if s.debug {
s.logf(format, v...)
}
}
// for testing
var (
timeSleep = time.Sleep
@@ -744,16 +753,20 @@ func (c *sclient) run(ctx context.Context) error {
defer func() {
cancelSender()
if err := grp.Wait(); err != nil && !c.s.isClosed() {
c.logf("sender failed: %v", err)
if errors.Is(err, context.Canceled) {
c.debugLogf("sender canceled by reader exiting")
} else {
c.logf("sender failed: %v", err)
}
}
}()
for {
ft, fl, err := readFrameHeader(c.br)
c.debug("read frame type %d len %d err %v", ft, fl, err)
c.debugLogf("read frame type %d len %d err %v", ft, fl, err)
if err != nil {
if errors.Is(err, io.EOF) {
c.logf("read EOF")
c.debugLogf("read EOF")
return nil
}
if c.s.isClosed() {
@@ -910,7 +923,7 @@ func (c *sclient) handleFrameForwardPacket(ft frameType, fl uint32) error {
return nil
}
dst.debug("received forwarded packet from %s via %s", srcKey.ShortString(), c.key.ShortString())
dst.debugLogf("received forwarded packet from %s via %s", srcKey.ShortString(), c.key.ShortString())
return c.sendPkt(dst, pkt{
bs: contents,
@@ -960,7 +973,7 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
if fwd != nil {
s.packetsForwardedOut.Add(1)
err := fwd.ForwardPacket(c.key, dstKey, contents)
c.debug("SendPacket for %s, forwarding via %s: %v", dstKey.ShortString(), fwd, err)
c.debugLogf("SendPacket for %s, forwarding via %s: %v", dstKey.ShortString(), fwd, err)
if err != nil {
// TODO:
return nil
@@ -974,10 +987,10 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
c.requestPeerGoneWriteLimited(dstKey, contents, PeerGoneReasonNotHere)
}
s.recordDrop(contents, c.key, dstKey, reason)
c.debug("SendPacket for %s, dropping with reason=%s", dstKey.ShortString(), reason)
c.debugLogf("SendPacket for %s, dropping with reason=%s", dstKey.ShortString(), reason)
return nil
}
c.debug("SendPacket for %s, sending directly", dstKey.ShortString())
c.debugLogf("SendPacket for %s, sending directly", dstKey.ShortString())
p := pkt{
bs: contents,
@@ -987,8 +1000,8 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
return c.sendPkt(dst, p)
}
func (c *sclient) debug(format string, v ...any) {
if c.debugLogging {
func (c *sclient) debugLogf(format string, v ...any) {
if c.debug {
c.logf(format, v...)
}
}
@@ -1011,7 +1024,8 @@ const (
func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, reason dropReason) {
s.packetsDropped.Add(1)
s.packetsDroppedReasonCounters[reason].Add(1)
if disco.LooksLikeDiscoWrapper(packetBytes) {
looksDisco := disco.LooksLikeDiscoWrapper(packetBytes)
if looksDisco {
s.packetsDroppedTypeDisco.Add(1)
} else {
s.packetsDroppedTypeOther.Add(1)
@@ -1024,9 +1038,7 @@ func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, r
msg := fmt.Sprintf("drop (%s) %s -> %s", srcKey.ShortString(), reason, dstKey.ShortString())
s.limitedLogf(msg)
}
if s.debug {
s.logf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, disco.LooksLikeDiscoWrapper(packetBytes))
}
s.debugLogf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, looksDisco)
}
func (c *sclient) sendPkt(dst *sclient, p pkt) error {
@@ -1044,13 +1056,13 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
select {
case <-dst.done:
s.recordDrop(p.bs, c.key, dstKey, dropReasonGoneDisconnected)
dst.debug("sendPkt attempt %d dropped, dst gone", attempt)
dst.debugLogf("sendPkt attempt %d dropped, dst gone", attempt)
return nil
default:
}
select {
case sendQueue <- p:
dst.debug("sendPkt attempt %d enqueued", attempt)
dst.debugLogf("sendPkt attempt %d enqueued", attempt)
return nil
default:
}
@@ -1066,7 +1078,7 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
// contended queue with racing writers. Give up and tail-drop in
// this case to keep reader unblocked.
s.recordDrop(p.bs, c.key, dstKey, dropReasonQueueTail)
dst.debug("sendPkt attempt %d dropped, queue full")
dst.debugLogf("sendPkt attempt %d dropped, queue full")
return nil
}
@@ -1304,8 +1316,7 @@ type sclient struct {
canMesh bool // clientInfo had correct mesh token for inter-region routing
isDup atomic.Bool // whether more than 1 sclient for key is connected
isDisabled atomic.Bool // whether sends to this peer are disabled due to active/active dups
debugLogging bool
debug bool // turn on for verbose logging
// Owned by run, not thread-safe.
br *bufio.Reader
@@ -1593,7 +1604,7 @@ func (c *sclient) sendPacket(srcKey key.NodePublic, contents []byte) (err error)
c.s.packetsSent.Add(1)
c.s.bytesSent.Add(int64(len(contents)))
}
c.debug("sendPacket from %s: %v", srcKey.ShortString(), err)
c.debugLogf("sendPacket from %s: %v", srcKey.ShortString(), err)
}()
c.setWriteDeadline()

View File

@@ -457,13 +457,24 @@ var applyDiskConfigErr error
// ApplyDiskConfigError returns the most recent result of ApplyDiskConfig.
func ApplyDiskConfigError() error { return applyDiskConfigErr }
// ApplyDiskConfig returns a platform-specific config file of environment keys/values and
// applies them. On Linux and Unix operating systems, it's a no-op and always returns nil.
// If no platform-specific config file is found, it also returns nil.
// ApplyDiskConfig returns a platform-specific config file of environment
// keys/values and applies them. On Linux and Unix operating systems, it's a
// no-op and always returns nil. If no platform-specific config file is found,
// it also returns nil.
//
// It exists primarily for Windows and macOS to make it easy to apply
// environment variables to a running service in a way similar to modifying
// /etc/default/tailscaled on Linux.
//
// It exists primarily for Windows to make it easy to apply environment variables to
// a running service in a way similar to modifying /etc/default/tailscaled on Linux.
// On Windows, you use %ProgramData%\Tailscale\tailscaled-env.txt instead.
//
// On macOS, use one of:
//
// - ~/Library/Containers/io.tailscale.ipn.macsys/Data/tailscaled-env.txt
// for standalone macOS GUI builds
// - ~/Library/Containers/io.tailscale.ipn.macos.network-extension/Data/tailscaled-env.txt
// for App Store builds
// - /etc/tailscale/tailscaled-env.txt for tailscaled-on-macOS (homebrew, etc)
func ApplyDiskConfig() (err error) {
var f *os.File
defer func() {
@@ -512,9 +523,15 @@ func getPlatformEnvFile() string {
return "/etc/tailscale/tailscaled-env.txt"
}
case "darwin":
// TODO(bradfitz): figure this out. There are three ways to run
// Tailscale on macOS (tailscaled, GUI App Store, GUI System Extension)
// and we should deal with all three.
if version.IsSandboxedMacOS() { // the two GUI variants (App Store or separate download)
// This will be user-visible as ~/Library/Containers/$VARIANT/Data/tailscaled-env.txt
// where $VARIANT is "io.tailscale.ipn.macsys" for macsys (downloadable mac GUI builds)
// or "io.tailscale.ipn.macos.network-extension" for App Store builds.
return filepath.Join(os.Getenv("HOME"), "tailscaled-env.txt")
} else {
// Open source / homebrew variable, running tailscaled-on-macOS.
return "/etc/tailscale/tailscaled-env.txt"
}
}
return ""
}

View File

@@ -115,4 +115,4 @@
in
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
}
# nix-direnv cache busting line: sha256-lirn07XE3JOS6oiwZBMwxzywkbXHowOJUMWWLrZtccY=
# nix-direnv cache busting line: sha256-l2uIma2oEdSN0zVo9BOFJF2gC3S60vXwTLVadv8yQPo=

280
go.mod
View File

@@ -3,98 +3,98 @@ module tailscale.com
go 1.20
require (
filippo.io/mkcert v1.4.3
github.com/Microsoft/go-winio v0.6.0
filippo.io/mkcert v1.4.4
github.com/Microsoft/go-winio v0.6.1
github.com/akutz/memconn v0.1.0
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
github.com/andybalholm/brotli v1.0.3
github.com/andybalholm/brotli v1.0.5
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
github.com/aws/aws-sdk-go-v2 v1.17.3
github.com/aws/aws-sdk-go-v2/config v1.11.0
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.4
github.com/aws/aws-sdk-go-v2/service/s3 v1.21.0
github.com/aws/aws-sdk-go-v2/service/ssm v1.35.0
github.com/aws/aws-sdk-go-v2 v1.18.0
github.com/aws/aws-sdk-go-v2/config v1.18.22
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.3
github.com/coreos/go-iptables v0.6.0
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/creack/pty v1.1.17
github.com/dave/jennifer v1.4.1
github.com/dblohm7/wingoes v0.0.0-20221124203957-6ac47ab19aa5
github.com/creack/pty v1.1.18
github.com/dave/jennifer v1.6.1
github.com/dblohm7/wingoes v0.0.0-20230426155039-111c8c3b57c8
github.com/dsnet/try v0.0.3
github.com/evanw/esbuild v0.14.53
github.com/frankban/quicktest v1.14.3
github.com/frankban/quicktest v1.14.5
github.com/fxamacker/cbor/v2 v2.4.0
github.com/go-json-experiment/json v0.0.0-20221017203807-c5ed296b8c92
github.com/go-logr/zapr v1.2.3
github.com/go-json-experiment/json v0.0.0-20230321051131-ccbac49a6929
github.com/go-logr/zapr v1.2.4
github.com/go-ole/go-ole v1.2.6
github.com/godbus/dbus/v5 v5.0.6
github.com/godbus/dbus/v5 v5.1.0
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/golangci/golangci-lint v1.52.2
github.com/google/go-cmp v0.5.9
github.com/google/go-containerregistry v0.9.0
github.com/google/go-containerregistry v0.14.0
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c
github.com/google/uuid v1.3.0
github.com/goreleaser/nfpm v1.10.3
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3
github.com/hdevalence/ed25519consensus v0.1.0
github.com/iancoleman/strcase v0.2.0
github.com/illarion/gonotify v1.0.1
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b
github.com/jsimonetti/rtnetlink v1.3.2
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.15.4
github.com/klauspost/compress v1.16.5
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.17
github.com/mdlayher/genetlink v1.2.0
github.com/mdlayher/netlink v1.7.1
github.com/mattn/go-isatty v0.0.18
github.com/mdlayher/genetlink v1.3.2
github.com/mdlayher/netlink v1.7.2
github.com/mdlayher/sdnotify v1.0.0
github.com/miekg/dns v1.1.43
github.com/miekg/dns v1.1.54
github.com/mitchellh/go-ps v1.0.0
github.com/peterbourgon/ff/v3 v3.1.2
github.com/peterbourgon/ff/v3 v3.3.0
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.4
github.com/prometheus/client_golang v1.14.0
github.com/prometheus/common v0.41.0
github.com/pkg/sftp v1.13.5
github.com/prometheus/client_golang v1.15.1
github.com/prometheus/common v0.42.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17
github.com/tailscale/golang-x-crypto v0.0.0-20221115211329-17a3db2c30d2
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
github.com/tailscale/mkctr v0.0.0-20220601142259-c0b937af2e89
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
github.com/tailscale/wireguard-go v0.0.0-20230410165232-af172621b4dd
github.com/tc-hib/winres v0.1.6
github.com/tc-hib/winres v0.2.0
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
github.com/u-root/u-root v0.11.0
github.com/vishvananda/netlink v1.2.1-beta.2
go.uber.org/zap v1.24.0
go4.org/mem v0.0.0-20210711025021-927187094b94
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
golang.org/x/crypto v0.6.0
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
golang.org/x/mod v0.9.0
golang.org/x/net v0.8.0
golang.org/x/oauth2 v0.5.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.6.0
golang.org/x/term v0.6.0
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
golang.org/x/tools v0.7.0
go4.org/mem v0.0.0-20220726221520-4f986261bf13
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
golang.org/x/mod v0.10.0
golang.org/x/net v0.10.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sync v0.2.0
golang.org/x/sys v0.8.0
golang.org/x/term v0.8.0
golang.org/x/time v0.3.0
golang.org/x/tools v0.9.1
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard/windows v0.5.3
gvisor.dev/gvisor v0.0.0-20230328175328-162ed5ef888d
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f
honnef.co/go/tools v0.4.3
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
inet.af/wf v0.0.0-20220728202103-50d96caab2f6
k8s.io/api v0.25.0
k8s.io/apimachinery v0.25.0
k8s.io/client-go v0.25.0
inet.af/wf v0.0.0-20221017222439-36129f591884
k8s.io/api v0.27.2
k8s.io/apimachinery v0.27.2
k8s.io/client-go v0.27.2
nhooyr.io/websocket v1.8.7
sigs.k8s.io/controller-runtime v0.13.1
sigs.k8s.io/controller-runtime v0.15.0
sigs.k8s.io/yaml v1.3.0
software.sslmate.com/src/go-pkcs12 v0.2.0
)
@@ -102,37 +102,38 @@ require (
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/Abirdcfly/dupword v0.0.11 // indirect
github.com/Antonboom/errname v0.1.9 // indirect
github.com/Antonboom/nilnil v0.1.3 // indirect
github.com/Antonboom/nilnil v0.1.4 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/Djarvur/go-err113 v0.1.0 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/OpenPeeDeeP/depguard v1.1.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/ashanbrown/forbidigo v1.5.1 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.6.4 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.0 // indirect
@@ -141,41 +142,40 @@ require (
github.com/bombsimon/wsl/v3 v3.4.0 // indirect
github.com/breml/bidichk v0.2.4 // indirect
github.com/breml/errchkjson v0.3.1 // indirect
github.com/butuzov/ireturn v0.1.1 // indirect
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
github.com/butuzov/ireturn v0.2.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/daixiang0/gci v0.10.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingaikin/go-header v0.4.3 // indirect
github.com/docker/cli v20.10.16+incompatible // indirect
github.com/docker/cli v23.0.5+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.16+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/docker/docker v23.0.5+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/esimonov/ifshort v1.0.4 // indirect
github.com/ettle/strcase v0.1.1 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/gliderlabs/ssh v0.3.3 // indirect
github.com/go-critic/go-critic v0.7.0 // indirect
github.com/go-critic/go-critic v0.8.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-git/go-git/v5 v5.4.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect
github.com/go-git/go-git/v5 v5.6.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.1.0 // indirect
@@ -187,7 +187,7 @@ require (
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect
@@ -197,13 +197,13 @@ require (
github.com/golangci/misspell v0.4.0 // indirect
github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1 // indirect
github.com/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
github.com/goreleaser/chglog v0.1.2 // indirect
github.com/goreleaser/chglog v0.4.2 // indirect
github.com/goreleaser/fileglob v0.3.1 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
@@ -214,9 +214,9 @@ require (
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jgautheron/goconst v1.5.1 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
@@ -226,10 +226,11 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/julz/importas v0.1.0 // indirect
github.com/junk1tm/musttag v0.5.0 // indirect
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kisielk/errcheck v1.6.3 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
@@ -237,18 +238,18 @@ require (
github.com/kunwardeep/paralleltest v1.0.6 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/ldez/gomoddirectives v0.2.3 // indirect
github.com/ldez/tagliatelle v0.4.0 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.1 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mdlayher/socket v0.4.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mgechev/revive v1.3.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
@@ -261,76 +262,79 @@ require (
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nishanths/exhaustive v0.9.5 // indirect
github.com/nishanths/exhaustive v0.10.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.9.0 // indirect
github.com/nunnatsa/ginkgolinter v0.11.2 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polyfloyd/go-errorlint v1.4.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/polyfloyd/go-errorlint v1.4.1 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/quasilyte/go-ruleguard v0.3.19 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/ryancurrah/gomodguard v1.3.0 // indirect
github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect
github.com/sassoftware/go-rpmutils v0.1.0 // indirect
github.com/sassoftware/go-rpmutils v0.2.0 // indirect
github.com/securego/gosec/v2 v2.15.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/sivchari/containedctx v1.0.2 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/nosnakecase v1.7.0 // indirect
github.com/sivchari/tenv v1.7.1 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
github.com/sonatard/noctx v0.0.2 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/spf13/viper v1.15.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.2 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
github.com/tetafro/godot v1.4.11 // indirect
github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e // indirect
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
github.com/timonwong/loggercheck v0.9.4 // indirect
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/ultraware/funlen v0.0.3 // indirect
github.com/ultraware/whitespace v0.0.5 // indirect
github.com/uudashr/gocognit v1.0.6 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.2.0 // indirect
gitlab.com/bosi/decorder v0.2.3 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 // indirect
golang.org/x/image v0.5.0 // indirect
golang.org/x/text v0.8.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect
golang.org/x/image v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
@@ -339,15 +343,15 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
k8s.io/apiextensions-apiserver v0.25.0 // indirect
k8s.io/component-base v0.25.0 // indirect
k8s.io/klog/v2 v2.70.1 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
mvdan.cc/gofumpt v0.4.0 // indirect
k8s.io/apiextensions-apiserver v0.27.2 // indirect
k8s.io/component-base v0.27.2 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
mvdan.cc/gofumpt v0.5.0 // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)

View File

@@ -1 +1 @@
sha256-lirn07XE3JOS6oiwZBMwxzywkbXHowOJUMWWLrZtccY=
sha256-l2uIma2oEdSN0zVo9BOFJF2gC3S60vXwTLVadv8yQPo=

1037
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
ddff070c02790cb571006e820e58cce9627569cf
480a0c381923c53e70ed5e72f9a9f79ce1884859

View File

@@ -95,6 +95,8 @@ func linuxVersionMeta() (meta versionMeta) {
propFile = "/etc.defaults/VERSION"
case distro.OpenWrt:
propFile = "/etc/openwrt_release"
case distro.Unraid:
propFile = "/etc/unraid-version"
case distro.WDMyCloud:
slurp, _ := os.ReadFile("/etc/version")
meta.DistroVersion = string(bytes.TrimSpace(slurp))
@@ -153,6 +155,8 @@ func linuxVersionMeta() (meta versionMeta) {
meta.DistroVersion = m["productversion"]
case distro.OpenWrt:
meta.DistroVersion = m["DISTRIB_RELEASE"]
case distro.Unraid:
meta.DistroVersion = m["version"]
}
return
}

View File

@@ -31,6 +31,7 @@ import (
"time"
"golang.org/x/crypto/acme"
"golang.org/x/exp/slices"
"tailscale.com/atomicfile"
"tailscale.com/envknob"
"tailscale.com/hostinfo"
@@ -100,11 +101,13 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
}
if pair, err := getCertPEMCached(cs, domain, now); err == nil {
future := now.AddDate(0, 0, 14)
if b.shouldStartDomainRenewal(cs, domain, future) {
shouldRenew, err := shouldStartDomainRenewal(domain, now, pair)
if err != nil {
logf("error checking for certificate renewal: %v", err)
} else if shouldRenew {
logf("starting async renewal")
// Start renewal in the background.
go b.getCertPEM(context.Background(), cs, logf, traceACME, domain, future)
go b.getCertPEM(context.Background(), cs, logf, traceACME, domain, now)
}
return pair, nil
}
@@ -117,18 +120,41 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
return pair, nil
}
func (b *LocalBackend) shouldStartDomainRenewal(cs certStore, domain string, future time.Time) bool {
func shouldStartDomainRenewal(domain string, now time.Time, pair *TLSCertKeyPair) (bool, error) {
renewMu.Lock()
defer renewMu.Unlock()
now := time.Now()
if last, ok := lastRenewCheck[domain]; ok && now.Sub(last) < time.Minute {
// We checked very recently. Don't bother reparsing &
// validating the x509 cert.
return false
return false, nil
}
lastRenewCheck[domain] = now
_, err := getCertPEMCached(cs, domain, future)
return errors.Is(err, errCertExpired)
block, _ := pem.Decode(pair.CertPEM)
if block == nil {
return false, fmt.Errorf("parsing certificate PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return false, fmt.Errorf("parsing certificate: %w", err)
}
certLifetime := cert.NotAfter.Sub(cert.NotBefore)
if certLifetime < 0 {
return false, fmt.Errorf("negative certificate lifetime %v", certLifetime)
}
// Per https://github.com/tailscale/tailscale/issues/8204, check
// whether we're more than 2/3 of the way through the certificate's
// lifetime, which is the officially-recommended best practice by Let's
// Encrypt.
renewalDuration := certLifetime * 2 / 3
renewAt := cert.NotBefore.Add(renewalDuration)
if now.After(renewAt) {
return true, nil
}
return false, nil
}
// certStore provides a way to perist and retrieve TLS certificates.
@@ -361,17 +387,16 @@ func (b *LocalBackend) getCertPEM(ctx context.Context, cs certStore, logf logger
}
key := "_acme-challenge." + domain
// Do a best-effort lookup to see if we've already created this DNS name
// in a previous attempt. Don't burn too much time on it, though. Worst
// case we ask the server to create something that already exists.
var resolver net.Resolver
var ok bool
txts, _ := resolver.LookupTXT(ctx, key)
for _, txt := range txts {
if txt == rec {
ok = true
logf("TXT record already existed")
break
}
}
if !ok {
lookupCtx, lookupCancel := context.WithTimeout(ctx, 500*time.Millisecond)
txts, _ := resolver.LookupTXT(lookupCtx, key)
lookupCancel()
if slices.Contains(txts, rec) {
logf("TXT record already existed")
} else {
logf("starting SetDNS call...")
err = b.SetDNS(ctx, key, rec)
if err != nil {

View File

@@ -6,12 +6,19 @@
package ipnlocal
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"embed"
"encoding/pem"
"math/big"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.org/x/exp/maps"
"tailscale.com/ipn/store/mem"
)
@@ -100,3 +107,94 @@ func TestCertStoreRoundTrip(t *testing.T) {
})
}
}
func TestShouldStartDomainRenewal(t *testing.T) {
reset := func() {
renewMu.Lock()
defer renewMu.Unlock()
maps.Clear(lastRenewCheck)
}
mustMakePair := func(template *x509.Certificate) *TLSCertKeyPair {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
b, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
if err != nil {
panic(err)
}
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: b,
})
return &TLSCertKeyPair{
Cached: false,
CertPEM: certPEM,
KeyPEM: []byte("unused"),
}
}
now := time.Unix(1685714838, 0)
subject := pkix.Name{
Organization: []string{"Tailscale, Inc."},
Country: []string{"CA"},
Province: []string{"ON"},
Locality: []string{"Toronto"},
StreetAddress: []string{"290 Bremner Blvd"},
PostalCode: []string{"M5V 3L9"},
}
testCases := []struct {
name string
notBefore time.Time
lifetime time.Duration
want bool
wantErr string
}{
{
name: "should renew",
notBefore: now.AddDate(0, 0, -89),
lifetime: 90 * 24 * time.Hour,
want: true,
},
{
name: "short-lived renewal",
notBefore: now.AddDate(0, 0, -7),
lifetime: 10 * 24 * time.Hour,
want: true,
},
{
name: "no renew",
notBefore: now.AddDate(0, 0, -59), // 59 days ago == not 2/3rds of the way through 90 days yet
lifetime: 90 * 24 * time.Hour,
want: false,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
reset()
ret, err := shouldStartDomainRenewal("example.com", now, mustMakePair(&x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: subject,
NotBefore: tt.notBefore,
NotAfter: tt.notBefore.Add(tt.lifetime),
}))
if tt.wantErr != "" {
if err == nil {
t.Errorf("wanted error, got nil")
} else if err.Error() != tt.wantErr {
t.Errorf("got err=%q, want %q", err.Error(), tt.wantErr)
}
} else {
if ret != tt.want {
t.Errorf("got ret=%v, want %v", ret, tt.want)
}
}
})
}
}

View File

@@ -60,6 +60,7 @@ import (
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tka"
"tailscale.com/tsd"
"tailscale.com/types/dnstype"
"tailscale.com/types/empty"
"tailscale.com/types/key"
@@ -137,10 +138,11 @@ type LocalBackend struct {
logf logger.Logf // general logging
keyLogf logger.Logf // for printing list of peers on change
statsLogf logger.Logf // for printing peers stats on change
e wgengine.Engine
sys *tsd.System
e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys
pm *profileManager
store ipn.StateStore
dialer *tsdial.Dialer // non-nil
store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys
dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys
backendLogID logid.PublicID
unregisterNetMon func()
unregisterHealthWatch func()
@@ -267,10 +269,10 @@ type clientGen func(controlclient.Options) (controlclient.Client, error)
// but is not actually running.
//
// If dialer is nil, a new one is made.
func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStore, dialer *tsdial.Dialer, e wgengine.Engine, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
if e == nil {
panic("ipn.NewLocalBackend: engine must not be nil")
}
func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
e := sys.Engine.Get()
store := sys.StateStore.Get()
dialer := sys.Dialer.Get()
pm, err := newProfileManager(store, logf)
if err != nil {
@@ -290,10 +292,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
osshare.SetFileSharingEnabled(false, logf)
ctx, cancel := context.WithCancel(context.Background())
portpoll, err := portlist.NewPoller()
if err != nil {
logf("skipping portlist: %s", err)
}
portpoll := new(portlist.Poller)
b := &LocalBackend{
ctx: ctx,
@@ -301,10 +300,11 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
logf: logf,
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
sys: sys,
e: e,
pm: pm,
store: store,
dialer: dialer,
store: store,
pm: pm,
backendLogID: logID,
state: ipn.NoState,
portpoll: portpoll,
@@ -313,7 +313,8 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
loginFlags: loginFlags,
}
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, e.GetNetMon())
netMon := sys.NetMon.Get()
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon)
if err != nil {
log.Printf("error setting up sockstat logger: %v", err)
}
@@ -330,7 +331,6 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
b.statusChanged = sync.NewCond(&b.statusLock)
b.e.SetStatusCallback(b.setWgengineStatus)
netMon := e.GetNetMon()
b.prevIfState = netMon.InterfaceState()
// Call our linkChange code once with the current state, and
// then also whenever it changes:
@@ -339,14 +339,9 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange)
wiredPeerAPIPort := false
if ig, ok := e.(wgengine.InternalsGetter); ok {
if tunWrap, _, _, ok := ig.GetInternals(); ok {
tunWrap.PeerAPIPort = b.GetPeerAPIPort
wiredPeerAPIPort = true
}
}
if !wiredPeerAPIPort {
if tunWrap, ok := b.sys.Tun.GetOK(); ok {
tunWrap.PeerAPIPort = b.GetPeerAPIPort
} else {
b.logf("[unexpected] failed to wire up PeerAPI port for engine %T", e)
}
@@ -464,6 +459,7 @@ func (b *LocalBackend) GetComponentDebugLogging(component string) time.Time {
}
// Dialer returns the backend's dialer.
// It is always non-nil.
func (b *LocalBackend) Dialer() *tsdial.Dialer {
return b.dialer
}
@@ -644,7 +640,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
defer b.mu.Unlock()
sb.MutateStatus(func(s *ipnstate.Status) {
s.Version = version.Long()
s.TUN = !wgengine.IsNetstack(b.e)
s.TUN = !b.sys.IsNetstack()
s.BackendState = b.state.String()
s.AuthURL = b.authURLSticky
if err := health.OverallError(); err != nil {
@@ -1315,8 +1311,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
hostinfo := hostinfo.New()
hostinfo.BackendLogID = b.backendLogID.String()
hostinfo.FrontendLogID = opts.FrontendLogID
hostinfo.Userspace.Set(wgengine.IsNetstack(b.e))
hostinfo.UserspaceRouter.Set(wgengine.IsNetstackRouter(b.e))
hostinfo.Userspace.Set(b.sys.IsNetstack())
hostinfo.UserspaceRouter.Set(b.sys.IsNetstackRouter())
if b.cc != nil {
// TODO(apenwarr): avoid the need to reinit controlclient.
@@ -1378,7 +1374,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
if b.portpoll != nil {
b.portpollOnce.Do(func() {
go b.portpoll.Run(b.ctx)
go b.readPoller()
// Give the poller a second to get results to
@@ -1401,7 +1396,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
var err error
isNetstack := wgengine.IsNetstackRouter(b.e)
isNetstack := b.sys.IsNetstackRouter()
debugFlags := controlDebugFlags
if isNetstack {
debugFlags = append([]string{"netstack"}, debugFlags...)
@@ -1423,7 +1418,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
HTTPTestClient: httpTestClient,
DiscoPublicKey: discoPublic,
DebugFlags: debugFlags,
NetMon: b.e.GetNetMon(),
NetMon: b.sys.NetMon.Get(),
Pinger: b,
PopBrowserURL: b.tellClientToBrowseToURL,
OnClientVersion: b.onClientVersion,
@@ -1813,11 +1808,30 @@ func dnsMapsEqual(new, old *netmap.NetworkMap) bool {
// readPoller is a goroutine that receives service lists from
// b.portpoll and propagates them into the controlclient's HostInfo.
func (b *LocalBackend) readPoller() {
n := 0
isFirst := true
ticker := time.NewTicker(portlist.PollInterval())
defer ticker.Stop()
initChan := make(chan struct{})
close(initChan)
for {
ports, ok := <-b.portpoll.Updates()
if !ok {
select {
case <-ticker.C:
case <-b.ctx.Done():
return
case <-initChan:
// Preserving old behavior: readPoller should
// immediately poll the first time, then wait
// for a tick after.
initChan = nil
}
ports, changed, err := b.portpoll.Poll()
if err != nil {
b.logf("error polling for open ports: %v", err)
return
}
if !changed {
continue
}
sl := []tailcfg.Service{}
for _, p := range ports {
@@ -1841,8 +1855,8 @@ func (b *LocalBackend) readPoller() {
b.doSetHostinfoFilterServices(hi)
n++
if n == 1 {
if isFirst {
isFirst = false
close(b.gotPortPollRes)
}
}
@@ -2479,7 +2493,7 @@ func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret ipn.EngineSt
// [GRINDER STATS LINES] - please don't remove (used for log parsing)
if peerStats.Len() > 0 {
b.keyLogf("[v1] peer keys: %s", strings.TrimSpace(peerKeys.String()))
b.statsLogf("[v1] v%v peers: %v", version.Long, strings.TrimSpace(peerStats.String()))
b.statsLogf("[v1] v%v peers: %v", version.Long(), strings.TrimSpace(peerStats.String()))
}
return ret
}
@@ -3317,14 +3331,12 @@ func (b *LocalBackend) initPeerAPIListener() {
directFileMode: b.directFileRoot != "",
directFileDoFinalRename: b.directFileDoFinalRename,
}
if re, ok := b.e.(wgengine.ResolvingEngine); ok {
if r, ok := re.GetResolver(); ok {
ps.resolver = r
}
if dm, ok := b.sys.DNSManager.GetOK(); ok {
ps.resolver = dm.Resolver()
}
b.peerAPIServer = ps
isNetstack := wgengine.IsNetstack(b.e)
isNetstack := b.sys.IsNetstack()
for i, a := range b.netMap.Addresses {
var ln net.Listener
var err error
@@ -3630,6 +3642,19 @@ func (b *LocalBackend) hasNodeKey() bool {
return p.Valid() && p.Persist().Valid() && !p.Persist().PrivateNodeKey().IsZero()
}
// NodeKey returns the public node key.
func (b *LocalBackend) NodeKey() key.NodePublic {
b.mu.Lock()
defer b.mu.Unlock()
p := b.pm.CurrentPrefs()
if !p.Valid() || !p.Persist().Valid() || p.Persist().PrivateNodeKey().IsZero() {
return key.NodePublic{}
}
return p.Persist().PublicNodeKey()
}
// nextState returns the state the backend seems to be in, based on
// its internal state.
func (b *LocalBackend) nextState() ipn.State {
@@ -4040,7 +4065,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
b.setServeProxyHandlersLocked()
// don't listen on netmap addresses if we're in userspace mode
if !wgengine.IsNetstack(b.e) {
if !b.sys.IsNetstack() {
b.updateServeTCPPortNetMapAddrListenersLocked(servePorts)
}
}
@@ -4391,7 +4416,7 @@ func nodeIP(n *tailcfg.Node, pred func(netip.Addr) bool) netip.Addr {
}
func (b *LocalBackend) CheckIPForwarding() error {
if wgengine.IsNetstackRouter(b.e) {
if b.sys.IsNetstackRouter() {
return nil
}
@@ -4537,13 +4562,9 @@ func (b *LocalBackend) DebugReSTUN() error {
}
func (b *LocalBackend) magicConn() (*magicsock.Conn, error) {
ig, ok := b.e.(wgengine.InternalsGetter)
mc, ok := b.sys.MagicSock.GetOK()
if !ok {
return nil, errors.New("engine isn't InternalsGetter")
}
_, mc, _, ok := ig.GetInternals()
if !ok {
return nil, errors.New("failed to get internals")
return nil, errors.New("failed to get magicsock from sys")
}
return mc, nil
}

View File

@@ -20,6 +20,7 @@ import (
"tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -501,13 +502,16 @@ func TestLazyMachineKeyGeneration(t *testing.T) {
tstest.Replace(t, &panicOnMachineKeyGeneration, func() bool { return true })
var logf logger.Logf = logger.Discard
sys := new(tsd.System)
store := new(mem.Store)
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
sys.Set(store)
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
t.Cleanup(eng.Close)
lb, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, eng, 0)
sys.Set(eng)
lb, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}
@@ -765,13 +769,16 @@ func TestPacketFilterPermitsUnlockedNodes(t *testing.T) {
func TestStatusWithoutPeers(t *testing.T) {
logf := tstest.WhileTestRunningLogger(t)
store := new(testStateStorage)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
sys := new(tsd.System)
sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
sys.Set(e)
t.Cleanup(e.Close)
b, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, e, 0)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}

View File

@@ -12,6 +12,7 @@ import (
"tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/store/mem"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -47,14 +48,17 @@ func TestLocalLogLines(t *testing.T) {
idA := logid(0xaa)
// set up a LocalBackend, super bare bones. No functional data.
sys := new(tsd.System)
store := new(mem.Store)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil {
t.Fatal(err)
}
t.Cleanup(e.Close)
sys.Set(e)
lb, err := NewLocalBackend(logf, idA, store, nil, e, 0)
lb, err := NewLocalBackend(logf, idA, sys, 0)
if err != nil {
t.Fatal(err)
}

View File

@@ -449,6 +449,8 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
filtered[i] = b.tka.filtered[i].Clone()
}
stateID1, _ := b.tka.authority.StateIDs()
return &ipnstate.NetworkLockStatus{
Enabled: true,
Head: &head,
@@ -457,6 +459,7 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
NodeKeySigned: selfAuthorized,
TrustedKeys: outKeys,
FilteredPeers: filtered,
StateID: stateID1,
}
}

View File

@@ -49,7 +49,7 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/util/clientmetric"
"tailscale.com/util/multierr"
"tailscale.com/wgengine"
"tailscale.com/version/distro"
"tailscale.com/wgengine/filter"
)
@@ -468,7 +468,7 @@ func (s *peerAPIServer) listen(ip netip.Addr, ifState *interfaces.State) (ln net
}
}
if wgengine.IsNetstack(s.b.e) {
if s.b.sys.IsNetstack() {
ipStr = ""
}
@@ -1090,6 +1090,10 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
http.Error(w, errNoTaildrop.Error(), http.StatusInternalServerError)
return
}
if distro.Get() == distro.Unraid && !h.ps.directFileMode {
http.Error(w, "Taildrop folder not configured or accessible", http.StatusInternalServerError)
return
}
rawPath := r.URL.EscapedPath()
suffix, ok := strings.CutPrefix(rawPath, "/v0/put/")
if !ok {
@@ -1234,12 +1238,9 @@ func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Req
http.Error(w, "denied; no debug access", http.StatusForbidden)
return
}
eng := h.ps.b.e
if ig, ok := eng.(wgengine.InternalsGetter); ok {
if _, mc, _, ok := ig.GetInternals(); ok {
mc.ServeHTTPDebug(w, r)
return
}
if mc, ok := h.ps.b.sys.MagicSock.GetOK(); ok {
mc.ServeHTTPDebug(w, r)
return
}
http.Error(w, "miswired", 500)
}

View File

@@ -22,6 +22,10 @@ import (
"tailscale.com/util/winutil"
)
var errAlreadyMigrated = errors.New("profile migration already completed")
var debug = envknob.RegisterBool("TS_DEBUG_PROFILES")
// profileManager is a wrapper around a StateStore that manages
// multiple profiles and the current profile.
type profileManager struct {
@@ -40,6 +44,13 @@ type profileManager struct {
isNewProfile bool
}
func (pm *profileManager) dlogf(format string, args ...any) {
if !debug() {
return
}
pm.logf(format, args...)
}
// CurrentUserID returns the current user ID. It is only non-empty on
// Windows where we have a multi-user system.
func (pm *profileManager) CurrentUserID() ipn.WindowsUserID {
@@ -64,9 +75,11 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
// Read the CurrentProfileKey from the store which stores
// the selected profile for the current user.
b, err := pm.store.ReadState(ipn.CurrentProfileKey(string(uid)))
pm.dlogf("SetCurrentUserID: ReadState(%q) = %v, %v", string(uid), len(b), err)
if err == ipn.ErrStateNotExist || len(b) == 0 {
if runtime.GOOS == "windows" {
if err := pm.migrateFromLegacyPrefs(); err != nil {
pm.dlogf("SetCurrentUserID: windows: migrating from legacy preferences")
if err := pm.migrateFromLegacyPrefs(); err != nil && !errors.Is(err, errAlreadyMigrated) {
return err
}
} else {
@@ -79,6 +92,7 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
pk := ipn.StateKey(string(b))
prof := pm.findProfileByKey(pk)
if prof == nil {
pm.dlogf("SetCurrentUserID: no profile found for key: %q", pk)
pm.NewProfile()
return nil
}
@@ -544,8 +558,16 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, goos stri
if err := pm.setPrefsLocked(prefs); err != nil {
return nil, err
}
} else if len(knownProfiles) == 0 && goos != "windows" {
// Most platform behavior is controlled by the goos parameter, however
// some behavior is implied by build tag and fails when run on Windows,
// so we explicitly avoid that behavior when running on Windows.
// Specifically this reaches down into legacy preference loading that is
// specialized by profiles_windows.go and fails in tests on an invalid
// uid passed in from the unix tests. The uid's used for Windows tests
// and runtime must be valid Windows security identifier structures.
} else if len(knownProfiles) == 0 && goos != "windows" && runtime.GOOS != "windows" {
// No known profiles, try a migration.
pm.dlogf("no known profiles; trying to migrate from legacy prefs")
if err := pm.migrateFromLegacyPrefs(); err != nil {
return nil, err
}
@@ -562,13 +584,15 @@ func (pm *profileManager) migrateFromLegacyPrefs() error {
sentinel, prefs, err := pm.loadLegacyPrefs()
if err != nil {
metricMigrationError.Add(1)
return err
return fmt.Errorf("load legacy prefs: %w", err)
}
pm.dlogf("loaded legacy preferences; sentinel=%q", sentinel)
if err := pm.SetPrefs(prefs); err != nil {
metricMigrationError.Add(1)
return fmt.Errorf("migrating _daemon profile: %w", err)
}
pm.completeMigration(sentinel)
pm.dlogf("completed legacy preferences migration with sentinel=%q", sentinel)
metricMigrationSuccess.Add(1)
return nil
}

View File

@@ -5,7 +5,7 @@ package ipnlocal
import (
"fmt"
"runtime"
"os/user"
"strconv"
"testing"
@@ -18,9 +18,6 @@ import (
)
func TestProfileCurrentUserSwitch(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
}
store := new(mem.Store)
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
@@ -77,9 +74,6 @@ func TestProfileCurrentUserSwitch(t *testing.T) {
}
func TestProfileList(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
}
store := new(mem.Store)
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
@@ -158,9 +152,6 @@ func TestProfileList(t *testing.T) {
// TestProfileManagement tests creating, loading, and switching profiles.
func TestProfileManagement(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
}
store := new(mem.Store)
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
@@ -312,10 +303,11 @@ func TestProfileManagement(t *testing.T) {
// TestProfileManagementWindows tests going into and out of Unattended mode on
// Windows.
func TestProfileManagementWindows(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
u, err := user.Current()
if err != nil {
t.Fatal(err)
}
uid := ipn.WindowsUserID(u.Uid)
store := new(mem.Store)
@@ -365,8 +357,8 @@ func TestProfileManagementWindows(t *testing.T) {
{
t.Logf("Set user1 as logged in user")
if err := pm.SetCurrentUserID("user1"); err != nil {
t.Fatal(err)
if err := pm.SetCurrentUserID(uid); err != nil {
t.Fatalf("can't set user id: %s", err)
}
checkProfiles(t)
t.Logf("Save prefs for user1")
@@ -401,7 +393,7 @@ func TestProfileManagementWindows(t *testing.T) {
{
t.Logf("Set user1 as current user")
if err := pm.SetCurrentUserID("user1"); err != nil {
if err := pm.SetCurrentUserID(uid); err != nil {
t.Fatal(err)
}
wantCurProfile = "test"
@@ -411,8 +403,8 @@ func TestProfileManagementWindows(t *testing.T) {
t.Logf("set unattended mode")
wantProfiles["test"] = setPrefs(t, "test", true)
}
if pm.CurrentUserID() != "user1" {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), "user1")
if pm.CurrentUserID() != uid {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
}
// Recreate the profile manager to ensure that it starts with test profile.
@@ -421,7 +413,7 @@ func TestProfileManagementWindows(t *testing.T) {
t.Fatal(err)
}
checkProfiles(t)
if pm.CurrentUserID() != "user1" {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), "user1")
if pm.CurrentUserID() != uid {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
}
}

View File

@@ -6,6 +6,7 @@ package ipnlocal
import (
"errors"
"fmt"
"io/fs"
"os"
"os/user"
"path/filepath"
@@ -21,8 +22,6 @@ const (
legacyPrefsExt = ".conf"
)
var errAlreadyMigrated = errors.New("profile migration already completed")
func legacyPrefsDir(uid ipn.WindowsUserID) (string, error) {
// TODO(aaron): Ideally we'd have the impersonation token for the pipe's
// client and use it to call SHGetKnownFolderPath, thus yielding the correct
@@ -41,6 +40,7 @@ func legacyPrefsDir(uid ipn.WindowsUserID) (string, error) {
func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
userLegacyPrefsDir, err := legacyPrefsDir(pm.currentUserID)
if err != nil {
pm.dlogf("no legacy preferences directory for %q: %v", pm.currentUserID, err)
return "", ipn.PrefsView{}, err
}
@@ -48,14 +48,20 @@ func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
// verify that migration sentinel is not present
_, err = os.Stat(migrationSentinel)
if err == nil {
pm.dlogf("migration sentinel %q already exists", migrationSentinel)
return "", ipn.PrefsView{}, errAlreadyMigrated
}
if !os.IsNotExist(err) {
pm.dlogf("os.Stat(%q) = %v", migrationSentinel, err)
return "", ipn.PrefsView{}, err
}
prefsPath := filepath.Join(userLegacyPrefsDir, legacyPrefsFile+legacyPrefsExt)
prefs, err := ipn.LoadPrefs(prefsPath)
pm.dlogf("ipn.LoadPrefs(%q) = %v, %v", prefsPath, prefs, err)
if errors.Is(err, fs.ErrNotExist) {
return "", ipn.PrefsView{}, errAlreadyMigrated
}
if err != nil {
return "", ipn.PrefsView{}, err
}

View File

@@ -143,7 +143,7 @@ func (s *serveListener) Run() {
}
func (s *serveListener) shouldWarnAboutListenError(err error) bool {
if !s.b.e.GetNetMon().InterfaceState().HasIP(s.ap.Addr()) {
if !s.b.sys.NetMon.Get().InterfaceState().HasIP(s.ap.Addr()) {
// Machine likely doesn't have IPv6 enabled (or the IP is still being
// assigned). No need to warn. Notably, WSL2 (Issue 6303).
return false
@@ -447,6 +447,8 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.Reverse
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(u)
r.Out.Host = r.In.Host
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
r.Out.Header.Set("X-Forwarded-Proto", "https")
if c, ok := r.Out.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext); ok {
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
}

View File

@@ -17,6 +17,7 @@ import (
"tailscale.com/ipn"
"tailscale.com/ipn/store/mem"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/tstest"
"tailscale.com/types/empty"
"tailscale.com/types/key"
@@ -297,14 +298,17 @@ func TestStateMachine(t *testing.T) {
c := qt.New(t)
logf := tstest.WhileTestRunningLogger(t)
sys := new(tsd.System)
store := new(testStateStorage)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
t.Cleanup(e.Close)
sys.Set(e)
b, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, e, 0)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}
@@ -941,13 +945,16 @@ func TestStateMachine(t *testing.T) {
func TestEditPrefsHasNoKeys(t *testing.T) {
logf := tstest.WhileTestRunningLogger(t)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
sys := new(tsd.System)
sys.Set(new(mem.Store))
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
t.Cleanup(e.Close)
sys.Set(e)
b, err := NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), nil, e, 0)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}
@@ -1023,10 +1030,14 @@ func TestWGEngineStatusRace(t *testing.T) {
t.Skip("test fails")
c := qt.New(t)
logf := tstest.WhileTestRunningLogger(t)
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
sys := new(tsd.System)
sys.Set(new(mem.Store))
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
c.Assert(err, qt.IsNil)
t.Cleanup(eng.Close)
b, err := NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), nil, eng, 0)
sys.Set(eng)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
c.Assert(err, qt.IsNil)
var cc *mockControl

View File

@@ -37,7 +37,7 @@ import (
type Server struct {
lb atomic.Pointer[ipnlocal.LocalBackend]
logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
netMon *netmon.Monitor // must be non-nil
backendLogID logid.PublicID
// resetOnZero is whether to call bs.Reset on transition from
// 1->0 active HTTP requests. That is, this is whether the backend is
@@ -410,14 +410,15 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit
}
// New returns a new Server.
// The netMon parameter is optional; if non-nil it's used to do faster interface
// lookups.
//
// To start it, use the Server.Run method.
//
// At some point, either before or after Run, the Server's SetLocalBackend
// method must also be called before Server can do anything useful.
func New(logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) *Server {
if netMon == nil {
panic("nil netMon")
}
return &Server{
backendLogID: logID,
logf: logf,

View File

@@ -121,6 +121,11 @@ type NetworkLockStatus struct {
// (i.e. no connectivity) because they failed tailnet lock
// checks.
FilteredPeers []*TKAFilteredPeer
// StateID is a nonce associated with the network lock authority,
// generated upon enablement. This field is not populated if the
// network lock is disabled.
StateID uint64
}
// NetworkLockUpdate describes a change to network-lock state.
@@ -583,6 +588,8 @@ func osEmoji(os string) string {
return "🖥️"
case "iOS":
return "📱"
case "tvOS":
return "🍎📺"
case "android":
return "🤖"
case "freebsd":

View File

@@ -930,8 +930,8 @@ func InUseOtherUserIPNStream(w http.ResponseWriter, r *http.Request, err error)
}
func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "denied", http.StatusForbidden)
if !h.PermitRead {
http.Error(w, "watch ipn bus access denied", http.StatusForbidden)
return
}
f, ok := w.(http.Flusher)

View File

@@ -9,22 +9,23 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
- [eliasnaur.com/font/roboto](https://pkg.go.dev/eliasnaur.com/font/roboto) ([BSD-3-Clause](https://git.sr.ht/~eliasnaur/font/tree/832bb8fc08c3/LICENSE))
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
- [gioui.org](https://pkg.go.dev/gioui.org) ([MIT](https://git.sr.ht/~eliasnaur/gio/tree/32c6a9b10d0b/LICENSE))
- [gioui.org/cpu](https://pkg.go.dev/gioui.org/cpu) ([MIT](https://git.sr.ht/~eliasnaur/gio-cpu/tree/8d6a761490d2/LICENSE))
- [gioui.org/shader](https://pkg.go.dev/gioui.org/shader) ([MIT](https://git.sr.ht/~eliasnaur/gio-shader/tree/v1.0.6/LICENSE))
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.11.0/config/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.6.4/credentials/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.8.2/feature/ec2/imds/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.27/internal/configsources/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.21/internal/endpoints/v2/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.2/internal/ini/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/internal/sync/singleflight/LICENSE))
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.5.2/service/internal/presigned-url/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.35.0/service/ssm/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.6.2/service/sso/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.11.1/service/sts/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.22/config/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.13.21/credentials/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.13.3/feature/ec2/imds/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.33/internal/configsources/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.27/internal/endpoints/v2/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.34/internal/ini/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/internal/sync/singleflight/LICENSE))
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.27/service/internal/presigned-url/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.36.3/service/ssm/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.12.9/service/sso/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.14.9/service/ssooidc/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.18.10/service/sts/LICENSE.txt))
- [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.13.5/LICENSE))
- [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.13.5/internal/sync/singleflight/LICENSE))
- [github.com/benoitkugler/textlayout](https://pkg.go.dev/github.com/benoitkugler/textlayout) ([MIT](https://github.com/benoitkugler/textlayout/blob/v0.3.0/LICENSE))
@@ -34,50 +35,51 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
- [github.com/go-text/typesetting](https://pkg.go.dev/github.com/go-text/typesetting) ([BSD-3-Clause](https://github.com/go-text/typesetting/blob/0399769901d5/LICENSE))
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.0/LICENSE))
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.0.1/LICENSE))
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/de60144f33f8/LICENSE))
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/LICENSE))
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.4/LICENSE))
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.4/internal/snapref/LICENSE))
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.4/zstd/internal/xxhash/LICENSE.txt))
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/LICENSE.md))
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.5/LICENSE))
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.16.5/internal/snapref/LICENSE))
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.16.5/zstd/internal/xxhash/LICENSE.txt))
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.1/LICENSE.md))
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.0/LICENSE.md))
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/LICENSE.md))
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
- [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.17/LICENSE))
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/bc99ab8c2d17/LICENSE))
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/17a3db2c30d2/LICENSE))
- [github.com/tailscale/goupnp](https://pkg.go.dev/github.com/tailscale/goupnp) ([BSD-2-Clause](https://github.com/tailscale/goupnp/blob/c64d0f06ea05/LICENSE))
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
- [github.com/tailscale/tailscale-android](https://pkg.go.dev/github.com/tailscale/tailscale-android) ([BSD-3-Clause](https://github.com/tailscale/tailscale-android/blob/HEAD/LICENSE))
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/af172621b4dd/LICENSE))
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/c3537552635f/LICENSE))
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/3e8cd9d6bf63/LICENSE))
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
- [go4.org/intern](https://pkg.go.dev/go4.org/intern) ([BSD-3-Clause](https://github.com/go4org/intern/blob/ae77deb06f29/LICENSE))
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/927187094b94/LICENSE))
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
- [go4.org/unsafe/assume-no-moving-gc](https://pkg.go.dev/go4.org/unsafe/assume-no-moving-gc) ([BSD-3-Clause](https://github.com/go4org/unsafe-assume-no-moving-gc/blob/ee73d164e760/LICENSE))
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47842c84:LICENSE))
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
- [golang.org/x/exp/shiny](https://pkg.go.dev/golang.org/x/exp/shiny) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/334a2380:shiny/LICENSE))
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.5.0:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.7.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/a3b23cc7:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.5.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.7.0:LICENSE))
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/LICENSE))
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.7.0:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
- [inet.af/netaddr](https://pkg.go.dev/inet.af/netaddr) ([BSD-3-Clause](https://github.com/inetaf/netaddr/blob/097006376321/LICENSE))
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))

View File

@@ -10,62 +10,63 @@ and [iOS][]. See also the dependencies in the [Tailscale CLI][].
## Go Packages
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.17.7/config/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.12.20/credentials/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.12.17/feature/ec2/imds/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.27/internal/configsources/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.21/internal/endpoints/v2/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.24/internal/ini/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/internal/sync/singleflight/LICENSE))
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.17/service/internal/presigned-url/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.35.0/service/ssm/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.11.23/service/sso/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.13.5/service/ssooidc/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.16.19/service/sts/LICENSE.txt))
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.22/config/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.13.21/credentials/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.13.3/feature/ec2/imds/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.33/internal/configsources/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.27/internal/endpoints/v2/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.34/internal/ini/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/internal/sync/singleflight/LICENSE))
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.27/service/internal/presigned-url/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.36.3/service/ssm/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.12.9/service/sso/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.14.9/service/ssooidc/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.18.10/service/sts/LICENSE.txt))
- [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.13.5/LICENSE))
- [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.13.5/internal/sync/singleflight/LICENSE))
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.0/LICENSE))
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/de60144f33f8/LICENSE))
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/LICENSE))
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.11/LICENSE))
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.11/internal/snapref/LICENSE))
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.11/zstd/internal/xxhash/LICENSE.txt))
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/LICENSE.md))
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.5/LICENSE))
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.16.5/internal/snapref/LICENSE))
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.16.5/zstd/internal/xxhash/LICENSE.txt))
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.1/LICENSE.md))
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.0/LICENSE.md))
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/LICENSE.md))
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/bc99ab8c2d17/LICENSE))
- [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.17/LICENSE))
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/17a3db2c30d2/LICENSE))
- [github.com/tailscale/goupnp](https://pkg.go.dev/github.com/tailscale/goupnp) ([BSD-2-Clause](https://github.com/tailscale/goupnp/blob/c64d0f06ea05/LICENSE))
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/af172621b4dd/LICENSE))
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/c3537552635f/LICENSE))
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/3e8cd9d6bf63/LICENSE))
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/cafedaf6:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.8.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.6.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.8.0:LICENSE))
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/LICENSE))
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.10.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.8.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE))

View File

@@ -13,85 +13,88 @@ well as an [option for macOS][].
Some packages may only be included on certain architectures or operating systems.
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.0/LICENSE))
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.1/LICENSE))
- [github.com/akutz/memconn](https://pkg.go.dev/github.com/akutz/memconn) ([Apache-2.0](https://github.com/akutz/memconn/blob/v0.1.0/LICENSE))
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
- [github.com/anmitsu/go-shlex](https://pkg.go.dev/github.com/anmitsu/go-shlex) ([MIT](https://github.com/anmitsu/go-shlex/blob/38f4b401e2be/LICENSE))
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.11.0/config/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.6.4/credentials/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.8.2/feature/ec2/imds/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.27/internal/configsources/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.21/internal/endpoints/v2/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.2/internal/ini/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/internal/sync/singleflight/LICENSE))
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.5.2/service/internal/presigned-url/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.35.0/service/ssm/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.6.2/service/sso/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.11.1/service/sts/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.22/config/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.13.21/credentials/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.13.3/feature/ec2/imds/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.33/internal/configsources/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.27/internal/endpoints/v2/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.34/internal/ini/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/internal/sync/singleflight/LICENSE))
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.27/service/internal/presigned-url/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.36.3/service/ssm/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.12.9/service/sso/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.14.9/service/ssooidc/LICENSE.txt))
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.18.10/service/sts/LICENSE.txt))
- [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.13.5/LICENSE))
- [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.13.5/internal/sync/singleflight/LICENSE))
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
- [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.17/LICENSE))
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/6ac47ab19aa5/LICENSE))
- [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.18/LICENSE))
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/111c8c3b57c8/LICENSE))
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
- [github.com/go-ole/go-ole](https://pkg.go.dev/github.com/go-ole/go-ole) ([MIT](https://github.com/go-ole/go-ole/blob/v1.2.6/LICENSE))
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.0/LICENSE))
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.0.1/LICENSE))
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.3.0/LICENSE))
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/de60144f33f8/LICENSE))
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/LICENSE))
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/LICENSE.md))
- [github.com/kballard/go-shellquote](https://pkg.go.dev/github.com/kballard/go-shellquote) ([MIT](https://github.com/kballard/go-shellquote/blob/95032a82bc51/LICENSE))
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.4/LICENSE))
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.4/internal/snapref/LICENSE))
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.4/zstd/internal/xxhash/LICENSE.txt))
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.5/LICENSE))
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.16.5/internal/snapref/LICENSE))
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.16.5/zstd/internal/xxhash/LICENSE.txt))
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
- [github.com/kr/fs](https://pkg.go.dev/github.com/kr/fs) ([BSD-3-Clause](https://github.com/kr/fs/blob/v0.1.0/LICENSE))
- [github.com/mattn/go-colorable](https://pkg.go.dev/github.com/mattn/go-colorable) ([MIT](https://github.com/mattn/go-colorable/blob/v0.1.13/LICENSE))
- [github.com/mattn/go-isatty](https://pkg.go.dev/github.com/mattn/go-isatty) ([MIT](https://github.com/mattn/go-isatty/blob/v0.0.17/LICENSE))
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.1/LICENSE.md))
- [github.com/mattn/go-isatty](https://pkg.go.dev/github.com/mattn/go-isatty) ([MIT](https://github.com/mattn/go-isatty/blob/v0.0.18/LICENSE))
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.0/LICENSE.md))
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/LICENSE.md))
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
- [github.com/peterbourgon/ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3) ([Apache-2.0](https://github.com/peterbourgon/ff/blob/v3.1.2/LICENSE))
- [github.com/peterbourgon/ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3) ([Apache-2.0](https://github.com/peterbourgon/ff/blob/v3.3.0/LICENSE))
- [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.17/LICENSE))
- [github.com/pkg/errors](https://pkg.go.dev/github.com/pkg/errors) ([BSD-2-Clause](https://github.com/pkg/errors/blob/v0.9.1/LICENSE))
- [github.com/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) ([BSD-2-Clause](https://github.com/pkg/sftp/blob/v1.13.4/LICENSE))
- [github.com/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) ([BSD-2-Clause](https://github.com/pkg/sftp/blob/v1.13.5/LICENSE))
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
- [github.com/tailscale/certstore](https://pkg.go.dev/github.com/tailscale/certstore) ([MIT](https://github.com/tailscale/certstore/blob/78d6e1c49d8d/LICENSE.md))
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/bc99ab8c2d17/LICENSE))
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/17a3db2c30d2/LICENSE))
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/af172621b4dd/LICENSE))
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
- [github.com/toqueteos/webbrowser](https://pkg.go.dev/github.com/toqueteos/webbrowser) ([MIT](https://github.com/toqueteos/webbrowser/blob/v1.2.0/LICENSE.md))
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/948a78c969ad/LICENSE))
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/c3537552635f/LICENSE))
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/v0.11.0/LICENSE))
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/3e8cd9d6bf63/LICENSE))
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/927187094b94/LICENSE))
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47842c84:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.8.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.6.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.8.0:LICENSE))
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.10.0:LICENSE))
- [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.7.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.8.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
- [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE))
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/LICENSE))
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
- [inet.af/wf](https://pkg.go.dev/inet.af/wf) ([BSD-3-Clause](https://github.com/inetaf/wf/blob/50d96caab2f6/LICENSE))
- [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.25.0/LICENSE))
- [inet.af/wf](https://pkg.go.dev/inet.af/wf) ([BSD-3-Clause](https://github.com/inetaf/wf/blob/36129f591884/LICENSE))
- [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.27.2/LICENSE))
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
- [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([MIT](https://github.com/kubernetes-sigs/yaml/blob/v1.3.0/LICENSE))
- [software.sslmate.com/src/go-pkcs12](https://pkg.go.dev/software.sslmate.com/src/go-pkcs12) ([BSD-3-Clause](https://github.com/SSLMate/go-pkcs12/blob/v0.2.0/LICENSE))

View File

@@ -9,44 +9,44 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
## Go Packages
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.0/LICENSE))
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.1/LICENSE))
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
- [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE))
- [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE))
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/2b26ab7fb5f9/LICENSE))
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/111c8c3b57c8/LICENSE))
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.3.0/LICENSE))
- [github.com/gregjones/httpcache](https://pkg.go.dev/github.com/gregjones/httpcache) ([MIT](https://github.com/gregjones/httpcache/blob/901d90724c79/LICENSE.txt))
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.11/LICENSE))
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.11/internal/snapref/LICENSE))
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.11/zstd/internal/xxhash/LICENSE.txt))
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.1/LICENSE.md))
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.0/LICENSE.md))
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/LICENSE.md))
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.5/LICENSE))
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.16.5/internal/snapref/LICENSE))
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.16.5/zstd/internal/xxhash/LICENSE.txt))
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/LICENSE.md))
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
- [github.com/nfnt/resize](https://pkg.go.dev/github.com/nfnt/resize) ([ISC](https://github.com/nfnt/resize/blob/83c6a9932646/LICENSE))
- [github.com/peterbourgon/diskv](https://pkg.go.dev/github.com/peterbourgon/diskv) ([MIT](https://github.com/peterbourgon/diskv/blob/v2.0.1/LICENSE))
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/f6f2f17d9da1/LICENSE))
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/ad93eed16885/LICENSE))
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.1.6/LICENSE))
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/f63dace725d8/LICENSE))
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/59dfb47dfef1/LICENSE))
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.2.0/LICENSE))
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/cafedaf6:LICENSE))
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.5.0:LICENSE))
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.9.0:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.8.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.6.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.8.0:LICENSE))
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.7.0:LICENSE))
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.10.0:LICENSE))
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.10.0:LICENSE))
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.8.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
- [gopkg.in/Knetic/govaluate.v3](https://pkg.go.dev/gopkg.in/Knetic/govaluate.v3) ([MIT](https://github.com/Knetic/govaluate/blob/v3.0.0/LICENSE))

View File

@@ -13,19 +13,19 @@ import (
"fmt"
"io"
"log"
mrand "math/rand"
"net/http"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"tailscale.com/envknob"
"tailscale.com/logtail/backoff"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/net/sockstats"
"tailscale.com/tstime"
tslogger "tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/util/set"
@@ -128,9 +128,6 @@ func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
cfg.FlushDelayFn = func() time.Duration { return 0 }
}
stdLogf := func(f string, a ...any) {
fmt.Fprintf(cfg.Stderr, strings.TrimSuffix(f, "\n")+"\n", a...)
}
var urlSuffix string
if !cfg.CopyPrivateID.IsZero() {
urlSuffix = "?copyId=" + cfg.CopyPrivateID.String()
@@ -148,7 +145,6 @@ func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
sentinel: make(chan int32, 16),
flushDelayFn: cfg.FlushDelayFn,
timeNow: cfg.TimeNow,
bo: backoff.NewBackoff("logtail", stdLogf, 30*time.Second),
metricsDelta: cfg.MetricsDelta,
sockstatsLabel: sockstats.LabelLogtailLogger,
@@ -186,7 +182,6 @@ type Logger struct {
flushPending atomic.Bool
sentinel chan int32
timeNow func() time.Time
bo *backoff.Backoff
zstdEncoder Encoder
uploadCancel func()
explainedRaw bool
@@ -373,23 +368,38 @@ func (l *Logger) uploading(ctx context.Context) {
}
}
for len(body) > 0 {
select {
case <-ctx.Done():
return
default:
}
uploaded, err := l.upload(ctx, body, origlen)
var lastError string
var numFailures int
var firstFailure time.Time
for len(body) > 0 && ctx.Err() == nil {
retryAfter, err := l.upload(ctx, body, origlen)
if err != nil {
numFailures++
firstFailure = time.Now()
if !l.internetUp() {
fmt.Fprintf(l.stderr, "logtail: internet down; waiting\n")
l.awaitInternetUp(ctx)
continue
}
fmt.Fprintf(l.stderr, "logtail: upload: %v\n", err)
}
l.bo.BackOff(ctx, err)
if uploaded {
// Only print the same message once.
if currError := err.Error(); lastError != currError {
fmt.Fprintf(l.stderr, "logtail: upload: %v\n", err)
lastError = currError
}
// Sleep for the specified retryAfter period,
// otherwise default to some random value.
if retryAfter <= 0 {
retryAfter = time.Duration(30+mrand.Intn(30)) * time.Second
}
tstime.Sleep(ctx, retryAfter)
} else {
// Only print a success message after recovery.
if numFailures > 0 {
fmt.Fprintf(l.stderr, "logtail: upload succeeded after %d failures and %s\n", numFailures, time.Since(firstFailure).Round(time.Second))
}
break
}
}
@@ -433,7 +443,7 @@ func (l *Logger) awaitInternetUp(ctx context.Context) {
// upload uploads body to the log server.
// origlen indicates the pre-compression body length.
// origlen of -1 indicates that the body is not compressed.
func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded bool, err error) {
func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (retryAfter time.Duration, err error) {
const maxUploadTime = 45 * time.Second
ctx = sockstats.WithSockStats(ctx, l.sockstatsLabel, l.Logf)
ctx, cancel := context.WithTimeout(ctx, maxUploadTime)
@@ -460,17 +470,16 @@ func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded
l.httpDoCalls.Add(1)
resp, err := l.httpc.Do(req)
if err != nil {
return false, fmt.Errorf("log upload of %d bytes %s failed: %v", len(body), compressedNote, err)
return 0, fmt.Errorf("log upload of %d bytes %s failed: %v", len(body), compressedNote, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
uploaded = resp.StatusCode == 400 // the server saved the logs anyway
b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
return uploaded, fmt.Errorf("log upload of %d bytes %s failed %d: %q", len(body), compressedNote, resp.StatusCode, b)
if resp.StatusCode != http.StatusOK {
n, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<10))
return time.Duration(n) * time.Second, fmt.Errorf("log upload of %d bytes %s failed %d: %s", len(body), compressedNote, resp.StatusCode, bytes.TrimSpace(b))
}
return true, nil
return 0, nil
}
// Flush uploads all logs to the server. It blocks until complete or there is an

View File

@@ -143,10 +143,6 @@ func (r *Resolver) cloudHostResolver() (v *net.Resolver, ok bool) {
switch runtime.GOOS {
case "android", "ios", "darwin":
return nil, false
case "windows":
// TODO(bradfitz): remove this restriction once we're using Go 1.19
// which supports net.Resolver.PreferGo on Windows.
return nil, false
}
ip := cloudenv.Get().ResolverIP()
if ip == "" {

View File

@@ -1321,10 +1321,7 @@ func (c *Client) measureAllICMPLatency(ctx context.Context, rs *reportState, nee
ctx, done := context.WithTimeout(ctx, icmpProbeTimeout)
defer done()
p, err := ping.New(ctx, c.logf, c.NetMon)
if err != nil {
return err
}
p := ping.New(ctx, c.logf, netns.Listener(c.logf, c.NetMon))
defer p.Close()
c.logf("UDP is blocked, trying ICMP")

View File

@@ -11,7 +11,6 @@ import (
"net/http"
"net/netip"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
@@ -156,9 +155,6 @@ func TestHairpinWait(t *testing.T) {
}
func TestBasic(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
}
stunAddr, cleanup := stuntest.Serve(t)
defer cleanup()

View File

@@ -111,7 +111,7 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
// Verify that we didn't just choose the Tailscale interface;
// if so, we fall back to binding from the default.
_, tsif, err2 := interfaces.Tailscale()
if err2 == nil && tsif.Index == idx {
if err2 == nil && tsif != nil && tsif.Index == idx {
logf("[unexpected] netns: interfaceIndexFor returned Tailscale interface")
return defaultIdx()
}

View File

@@ -11,16 +11,25 @@ import (
"crypto/rand"
"encoding/binary"
"fmt"
"io"
"log"
"net"
"net/netip"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"tailscale.com/net/netmon"
"tailscale.com/net/netns"
"golang.org/x/net/ipv6"
"tailscale.com/types/logger"
"tailscale.com/util/mak"
"tailscale.com/util/multierr"
)
const (
v4Type = "ip4:icmp"
v6Type = "ip6:icmp"
)
type response struct {
@@ -33,12 +42,21 @@ type outstanding struct {
data []byte
}
// PacketListener defines the interface required to listen to packages
// on an address.
type ListenPacketer interface {
ListenPacket(ctx context.Context, typ string, addr string) (net.PacketConn, error)
}
// Pinger represents a set of ICMP echo requests to be sent at a single time.
//
// A new instance should be created for each concurrent set of ping requests;
// this type should not be reused.
type Pinger struct {
c net.PacketConn
lp ListenPacketer
// closed guards against send incrementing the waitgroup concurrently with close.
closed atomic.Bool
Logf logger.Logf
Verbose bool
timeNow func() time.Time
@@ -46,16 +64,37 @@ type Pinger struct {
wg sync.WaitGroup
// Following fields protected by mu
mu sync.Mutex
mu sync.Mutex
// conns is a map of "type" to net.PacketConn, type is either
// "ip4:icmp" or "ip6:icmp"
conns map[string]net.PacketConn
seq uint16 // uint16 per RFC 792
pings map[uint16]outstanding
}
// New creates a new Pinger. The Context provided will be used to create
// network listeners, and to set an absolute deadline (if any) on the net.Conn
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func New(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) {
p, err := newUnstarted(ctx, logf, netMon)
func New(ctx context.Context, logf logger.Logf, lp ListenPacketer) *Pinger {
var id [2]byte
if _, err := io.ReadFull(rand.Reader, id[:]); err != nil {
panic("net/ping: New:" + err.Error())
}
return &Pinger{
lp: lp,
Logf: logf,
timeNow: time.Now,
id: binary.LittleEndian.Uint16(id[:]),
pings: make(map[uint16]outstanding),
}
}
func (p *Pinger) mkconn(ctx context.Context, typ, addr string) (net.PacketConn, error) {
if p.closed.Load() {
return nil, net.ErrClosed
}
c, err := p.lp.ListenPacket(ctx, typ, addr)
if err != nil {
return nil, err
}
@@ -64,35 +103,36 @@ func New(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger
// applies to all future I/O, so we only need to do it once.
deadline, ok := ctx.Deadline()
if ok {
if err := p.c.SetReadDeadline(deadline); err != nil {
if err := c.SetReadDeadline(deadline); err != nil {
return nil, err
}
}
p.wg.Add(1)
go p.run(ctx)
return p, nil
go p.run(ctx, c, typ)
return c, err
}
func newUnstarted(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) {
var id [2]byte
_, err := rand.Read(id[:])
// getConn creates or returns a conn matching typ which is ip4:icmp
// or ip6:icmp.
func (p *Pinger) getConn(ctx context.Context, typ string) (net.PacketConn, error) {
p.mu.Lock()
defer p.mu.Unlock()
if c, ok := p.conns[typ]; ok {
return c, nil
}
var addr = "0.0.0.0"
if typ == v6Type {
addr = "::"
}
c, err := p.mkconn(ctx, typ, addr)
if err != nil {
return nil, err
}
conn, err := netns.Listener(logf, netMon).ListenPacket(ctx, "ip4:icmp", "0.0.0.0")
if err != nil {
return nil, err
}
return &Pinger{
c: conn,
Logf: logf,
timeNow: time.Now,
id: binary.LittleEndian.Uint16(id[:]),
pings: make(map[uint16]outstanding),
}, nil
mak.Set(&p.conns, typ, c)
return c, nil
}
func (p *Pinger) logf(format string, a ...any) {
@@ -110,13 +150,34 @@ func (p *Pinger) vlogf(format string, a ...any) {
}
func (p *Pinger) Close() error {
err := p.c.Close()
p.closed.Store(true)
p.mu.Lock()
conns := p.conns
p.conns = nil
p.mu.Unlock()
var errors []error
for _, c := range conns {
if err := c.Close(); err != nil {
errors = append(errors, err)
}
}
p.wg.Wait()
return err
p.cleanupOutstanding()
return multierr.New(errors...)
}
func (p *Pinger) run(ctx context.Context) {
func (p *Pinger) run(ctx context.Context, conn net.PacketConn, typ string) {
defer p.wg.Done()
defer func() {
conn.Close()
p.mu.Lock()
delete(p.conns, typ)
p.mu.Unlock()
}()
buf := make([]byte, 1500)
loop:
@@ -127,7 +188,7 @@ loop:
default:
}
n, addr, err := p.c.ReadFrom(buf)
n, _, err := conn.ReadFrom(buf)
if err != nil {
// Ignore temporary errors; everything else is fatal
if netErr, ok := err.(net.Error); !ok || !netErr.Temporary() {
@@ -136,10 +197,8 @@ loop:
continue
}
p.handleResponse(buf[:n], addr, p.timeNow())
p.handleResponse(buf[:n], p.timeNow(), typ)
}
p.cleanupOutstanding()
}
func (p *Pinger) cleanupOutstanding() {
@@ -151,16 +210,28 @@ func (p *Pinger) cleanupOutstanding() {
}
}
func (p *Pinger) handleResponse(buf []byte, addr net.Addr, now time.Time) {
const ProtocolICMP = 1
m, err := icmp.ParseMessage(ProtocolICMP, buf)
func (p *Pinger) handleResponse(buf []byte, now time.Time, typ string) {
// We need to handle responding to both IPv4
// and IPv6.
var icmpType icmp.Type
switch typ {
case v4Type:
icmpType = ipv4.ICMPTypeEchoReply
case v6Type:
icmpType = ipv6.ICMPTypeEchoReply
default:
p.vlogf("handleResponse: unknown icmp.Type")
return
}
m, err := icmp.ParseMessage(icmpType.Protocol(), buf)
if err != nil {
p.vlogf("handleResponse: invalid packet: %v", err)
return
}
if m.Type != ipv4.ICMPTypeEchoReply {
p.vlogf("handleResponse: wanted m.Type=%d; got %d", ipv4.ICMPTypeEchoReply, m.Type)
if m.Type != icmpType {
p.vlogf("handleResponse: wanted m.Type=%d; got %d", icmpType, m.Type)
return
}
@@ -212,9 +283,27 @@ func (p *Pinger) Send(ctx context.Context, dest net.Addr, data []byte) (time.Dur
seq := p.seq
p.mu.Unlock()
// Check whether the address is IPv4 or IPv6 to
// determine the icmp.Type and conn to use.
var conn net.PacketConn
var icmpType icmp.Type = ipv4.ICMPTypeEcho
ap, err := netip.ParseAddr(dest.String())
if err != nil {
return 0, err
}
if ap.Is6() {
icmpType = ipv6.ICMPTypeEchoRequest
conn, err = p.getConn(ctx, v6Type)
} else {
conn, err = p.getConn(ctx, v4Type)
}
if err != nil {
return 0, err
}
m := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Type: icmpType,
Code: icmpType.Protocol(),
Body: &icmp.Echo{
ID: int(p.id),
Seq: int(seq),
@@ -234,7 +323,7 @@ func (p *Pinger) Send(ctx context.Context, dest net.Addr, data []byte) (time.Dur
p.mu.Unlock()
start := p.timeNow()
n, err := p.c.WriteTo(b, dest)
n, err := conn.WriteTo(b, dest)
if err != nil {
return 0, err
} else if n != len(b) {

View File

@@ -6,18 +6,20 @@ package ping
import (
"context"
"errors"
"fmt"
"net"
"testing"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"tailscale.com/tstest"
"tailscale.com/util/mak"
)
var (
localhost = &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}
localhostUDP = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 12345}
localhost = &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}
)
func TestPinger(t *testing.T) {
@@ -35,7 +37,7 @@ func TestPinger(t *testing.T) {
// Start a ping in the background
r := make(chan time.Duration, 1)
go func() {
dur, err := p.Send(ctx, localhostUDP, bodyData)
dur, err := p.Send(ctx, localhost, bodyData)
if err != nil {
t.Errorf("p.Send: %v", err)
r <- 0
@@ -49,7 +51,7 @@ func TestPinger(t *testing.T) {
// Fake a response from ourself
fakeResponse := mustMarshal(t, &icmp.Message{
Type: ipv4.ICMPTypeEchoReply,
Code: 0,
Code: ipv4.ICMPTypeEchoReply.Protocol(),
Body: &icmp.Echo{
ID: 1234,
Seq: 1,
@@ -58,7 +60,65 @@ func TestPinger(t *testing.T) {
})
const fakeDuration = 100 * time.Millisecond
p.handleResponse(fakeResponse, localhost, clock.Now().Add(fakeDuration))
p.handleResponse(fakeResponse, clock.Now().Add(fakeDuration), v4Type)
select {
case dur := <-r:
want := fakeDuration
if dur != want {
t.Errorf("wanted ping response time = %d; got %d", want, dur)
}
case <-ctx.Done():
t.Fatal("did not get response by timeout")
}
}
func TestV6Pinger(t *testing.T) {
if c, err := net.ListenPacket("udp6", "::1"); err != nil {
// skip test if we can't use IPv6.
t.Skipf("IPv6 not supported: %s", err)
} else {
c.Close()
}
clock := &tstest.Clock{}
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
p, closeP := mockPinger(t, clock)
defer closeP()
bodyData := []byte("data goes here")
// Start a ping in the background
r := make(chan time.Duration, 1)
go func() {
dur, err := p.Send(ctx, &net.IPAddr{IP: net.ParseIP("::")}, bodyData)
if err != nil {
t.Errorf("p.Send: %v", err)
r <- 0
} else {
r <- dur
}
}()
p.waitOutstanding(t, ctx, 1)
// Fake a response from ourself
fakeResponse := mustMarshal(t, &icmp.Message{
Type: ipv6.ICMPTypeEchoReply,
Code: ipv6.ICMPTypeEchoReply.Protocol(),
Body: &icmp.Echo{
ID: 1234,
Seq: 1,
Data: bodyData,
},
})
const fakeDuration = 100 * time.Millisecond
p.handleResponse(fakeResponse, clock.Now().Add(fakeDuration), v6Type)
select {
case dur := <-r:
@@ -83,7 +143,7 @@ func TestPingerTimeout(t *testing.T) {
// Send a ping in the background
r := make(chan error, 1)
go func() {
_, err := p.Send(ctx, localhostUDP, []byte("data goes here"))
_, err := p.Send(ctx, localhost, []byte("data goes here"))
r <- err
}()
@@ -115,7 +175,7 @@ func TestPingerMismatch(t *testing.T) {
// Start a ping in the background
r := make(chan time.Duration, 1)
go func() {
dur, err := p.Send(ctx, localhostUDP, bodyData)
dur, err := p.Send(ctx, localhost, bodyData)
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("p.Send: %v", err)
r <- 0
@@ -185,11 +245,11 @@ func TestPingerMismatch(t *testing.T) {
for _, tt := range badPackets {
fakeResponse := mustMarshal(t, tt.pkt)
p.handleResponse(fakeResponse, localhost, tm)
p.handleResponse(fakeResponse, tm, v4Type)
}
// Also "receive" a packet that does not unmarshal as an ICMP packet
p.handleResponse([]byte("foo"), localhost, tm)
p.handleResponse([]byte("foo"), tm, v4Type)
select {
case <-r:
@@ -199,23 +259,59 @@ func TestPingerMismatch(t *testing.T) {
}
}
// udpingPacketConn will convert potentially ICMP destination addrs to UDP
// destination addrs in WriteTo so that a test that is intending to send ICMP
// traffic will instead send UDP traffic, without the higher level Pinger being
// aware of this difference.
type udpingPacketConn struct {
net.PacketConn
// destPort will be configured by the test to be the peer expected to respond to a ping.
destPort uint16
}
func (u *udpingPacketConn) WriteTo(body []byte, dest net.Addr) (int, error) {
switch d := dest.(type) {
case *net.IPAddr:
udpAddr := &net.UDPAddr{
IP: d.IP,
Port: int(u.destPort),
Zone: d.Zone,
}
return u.PacketConn.WriteTo(body, udpAddr)
}
return 0, fmt.Errorf("unimplemented udpingPacketConn for %T", dest)
}
func mockPinger(t *testing.T, clock *tstest.Clock) (*Pinger, func()) {
p := New(context.Background(), t.Logf, nil)
p.timeNow = clock.Now
p.Verbose = true
p.id = 1234
// In tests, we use UDP so that we can test without being root; this
// doesn't matter because we mock out the ICMP reply below to be a real
// ICMP echo reply packet.
conn, err := net.ListenPacket("udp4", "127.0.0.1:0")
conn4, err := net.ListenPacket("udp4", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.ListenPacket: %v", err)
}
p := &Pinger{
c: conn,
Logf: t.Logf,
Verbose: true,
timeNow: clock.Now,
id: 1234,
pings: make(map[uint16]outstanding),
conn6, err := net.ListenPacket("udp6", "[::]:0")
if err != nil {
t.Fatalf("net.ListenPacket: %v", err)
}
conn4 = &udpingPacketConn{
destPort: 12345,
PacketConn: conn4,
}
conn6 = &udpingPacketConn{
PacketConn: conn6,
destPort: 12345,
}
mak.Set(&p.conns, v4Type, conn4)
mak.Set(&p.conns, v6Type, conn6)
done := func() {
if err := p.Close(); err != nil {
t.Errorf("error on close: %v", err)

View File

@@ -325,6 +325,10 @@ type radioMonitor struct {
// Usage is measured once per second, so this is the number of seconds of history to track.
const radioSampleSize = 3600 // 1 hour
// initStallPeriod is the minimum amount of time in seconds to collect data before reporting.
// Otherwise, all clients will report 100% radio usage on startup.
var initStallPeriod int64 = 120 // 2 minutes
var radio = &radioMonitor{
now: time.Now,
startTime: time.Now().Unix(),
@@ -375,7 +379,7 @@ func (rm *radioMonitor) radioHighPercent() int64 {
}
})
if periodLength == 0 {
if periodLength < initStallPeriod {
return 0
}
@@ -386,7 +390,7 @@ func (rm *radioMonitor) radioHighPercent() int64 {
}
// forEachSample calls f for each sample in the past hour (or less if less time
// has passed -- the evaluated period is returned)
// has passed -- the evaluated period is returned, measured in seconds)
func (rm *radioMonitor) forEachSample(f func(c int, isActive bool)) (periodLength int64) {
now := rm.now().Unix()
periodLength = radioSampleSize

View File

@@ -33,6 +33,14 @@ func TestRadioMonitor(t *testing.T) {
func(_ *testTime, _ *radioMonitor) {},
0,
},
{
"active less than init stall period",
func(tt *testTime, rm *radioMonitor) {
rm.active()
tt.Add(1 * time.Second)
},
0, // radio on, but not long enough to report data
},
{
"active, 10 sec idle",
func(tt *testTime, rm *radioMonitor) {
@@ -42,13 +50,13 @@ func TestRadioMonitor(t *testing.T) {
50, // radio on 5 seconds of 10 seconds
},
{
"active, spanning two seconds",
"active, spanning three seconds",
func(tt *testTime, rm *radioMonitor) {
rm.active()
tt.Add(1100 * time.Millisecond)
tt.Add(2100 * time.Millisecond)
rm.active()
},
100, // radio on for 2 seconds
100, // radio on for 3 seconds
},
{
"400 iterations: 2 sec active, 1 min idle",
@@ -66,13 +74,17 @@ func TestRadioMonitor(t *testing.T) {
{
"activity at end of time window",
func(tt *testTime, rm *radioMonitor) {
tt.Add(1 * time.Second)
tt.Add(3 * time.Second)
rm.active()
},
50,
25,
},
}
oldStallPeriod := initStallPeriod
initStallPeriod = 3
t.Cleanup(func() { initStallPeriod = oldStallPeriod })
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tm := &testTime{time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)}

View File

@@ -47,8 +47,11 @@ func (t *fakeTUN) Write(b [][]byte, n int) (int, error) {
return 1, nil
}
// FakeTUNName is the name of the fake TUN device.
const FakeTUNName = "FakeTUN"
func (t *fakeTUN) Flush() error { return nil }
func (t *fakeTUN) MTU() (int, error) { return 1500, nil }
func (t *fakeTUN) Name() (string, error) { return "FakeTUN", nil }
func (t *fakeTUN) Name() (string, error) { return FakeTUNName, nil }
func (t *fakeTUN) Events() <-chan tun.Event { return t.evchan }
func (t *fakeTUN) BatchSize() int { return 1 }

View File

@@ -20,6 +20,7 @@ import (
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"go4.org/mem"
"golang.org/x/exp/slices"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"tailscale.com/disco"
"tailscale.com/net/connstats"
@@ -590,16 +591,33 @@ func natConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
dstMasqAddrs map[key.NodePublic]netip.Addr
listenAddrs map[netip.Addr]struct{}
)
// When using an exit node that requires masquerading, we need to
// fill out the routing table with all peers not just the ones that
// require masquerading.
exitNodeRequiresMasq := false // true if using an exit node and it requires masquerading
for _, p := range wcfg.Peers {
isExitNode := slices.Contains(p.AllowedIPs, tsaddr.AllIPv4()) || slices.Contains(p.AllowedIPs, tsaddr.AllIPv6())
if isExitNode && p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
exitNodeRequiresMasq = true
break
}
}
for i := range wcfg.Peers {
p := &wcfg.Peers[i]
if p.V4MasqAddr == nil || !p.V4MasqAddr.IsValid() {
var addrToUse netip.Addr
if p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
addrToUse = *p.V4MasqAddr
mak.Set(&listenAddrs, addrToUse, struct{}{})
} else if exitNodeRequiresMasq {
addrToUse = nativeAddr
} else {
continue
}
rt.InsertOrReplace(p.PublicKey, p.AllowedIPs...)
mak.Set(&dstMasqAddrs, p.PublicKey, *p.V4MasqAddr)
mak.Set(&listenAddrs, *p.V4MasqAddr, struct{}{})
mak.Set(&dstMasqAddrs, p.PublicKey, addrToUse)
}
if len(listenAddrs) == 0 || len(dstMasqAddrs) == 0 {
if len(listenAddrs) == 0 && len(dstMasqAddrs) == 0 {
return nil
}
return &natV4Config{

View File

@@ -602,13 +602,13 @@ func TestFilterDiscoLoop(t *testing.T) {
}
func TestNATCfg(t *testing.T) {
node := func(ip, eip netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
node := func(ip, masqIP netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
p := wgcfg.Peer{
PublicKey: key.NewNode().Public(),
AllowedIPs: []netip.Prefix{
netip.PrefixFrom(ip, ip.BitLen()),
},
V4MasqAddr: ptr.To(eip),
V4MasqAddr: ptr.To(masqIP),
}
p.AllowedIPs = append(p.AllowedIPs, otherAllowedIPs...)
return p
@@ -619,13 +619,16 @@ func TestNATCfg(t *testing.T) {
selfNativeIP = netip.MustParseAddr("100.64.0.1")
selfEIP1 = netip.MustParseAddr("100.64.1.1")
selfEIP2 = netip.MustParseAddr("100.64.1.2")
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
peer1IP = netip.MustParseAddr("100.64.0.2")
peer2IP = netip.MustParseAddr("100.64.0.3")
subnet = netip.MustParseAddr("192.168.0.1")
subnet = netip.MustParsePrefix("192.168.0.0/24")
subnetIP = netip.MustParseAddr("192.168.0.1")
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
exitRoute = netip.MustParsePrefix("0.0.0.0/0")
publicIP = netip.MustParseAddr("8.8.8.8")
)
tests := []struct {
@@ -638,9 +641,9 @@ func TestNATCfg(t *testing.T) {
name: "no-cfg",
wcfg: nil,
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfNativeIP,
peer2IP: selfNativeIP,
subnet: selfNativeIP,
peer1IP: selfNativeIP,
peer2IP: selfNativeIP,
subnetIP: selfNativeIP,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
@@ -658,15 +661,15 @@ func TestNATCfg(t *testing.T) {
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfNativeIP,
peer2IP: selfEIP1,
subnet: selfNativeIP,
peer1IP: selfNativeIP,
peer2IP: selfEIP1,
subnetIP: selfNativeIP,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfEIP2,
subnet: subnet,
subnetIP: subnetIP,
},
},
{
@@ -679,15 +682,15 @@ func TestNATCfg(t *testing.T) {
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfEIP1,
peer2IP: selfEIP2,
subnet: selfNativeIP,
peer1IP: selfEIP1,
peer2IP: selfEIP2,
subnetIP: selfNativeIP,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfNativeIP,
subnet: subnet,
subnetIP: subnetIP,
},
},
{
@@ -696,19 +699,19 @@ func TestNATCfg(t *testing.T) {
Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, selfEIP1),
node(peer2IP, selfEIP2, netip.MustParsePrefix("192.168.0.0/24")),
node(peer2IP, selfEIP2, subnet),
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfEIP1,
peer2IP: selfEIP2,
subnet: selfEIP2,
peer1IP: selfEIP1,
peer2IP: selfEIP2,
subnetIP: selfEIP2,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfNativeIP,
subnet: subnet,
subnetIP: subnetIP,
},
},
{
@@ -717,19 +720,19 @@ func TestNATCfg(t *testing.T) {
Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, selfEIP1),
node(peer2IP, selfEIP2, netip.MustParsePrefix("0.0.0.0/0")),
node(peer2IP, selfEIP2, exitRoute),
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfEIP1,
peer2IP: selfEIP2,
netip.MustParseAddr("8.8.8.8"): selfEIP2,
peer1IP: selfEIP1,
peer2IP: selfEIP2,
publicIP: selfEIP2,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfNativeIP,
subnet: subnet,
subnetIP: subnetIP,
},
},
{
@@ -742,15 +745,35 @@ func TestNATCfg(t *testing.T) {
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfNativeIP,
peer2IP: selfNativeIP,
subnet: selfNativeIP,
peer1IP: selfNativeIP,
peer2IP: selfNativeIP,
subnetIP: selfNativeIP,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfEIP1,
selfEIP2: selfEIP2,
subnet: subnet,
subnetIP: subnetIP,
},
},
{
name: "exit-node-require-nat-peer-doesnt",
wcfg: &wgcfg.Config{
Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, noIP),
node(peer2IP, selfEIP2, exitRoute),
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfNativeIP,
peer2IP: selfEIP2,
publicIP: selfEIP2,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP2: selfNativeIP,
subnetIP: subnetIP,
},
},
}

View File

@@ -67,7 +67,7 @@ type nothing struct{}
// Unfortunately, options to filter by proto or state are non-portable,
// so we'll filter for ourselves.
// Nowadays, though, we only use it for macOS as of 2022-11-04.
func appendParsePortsNetstat(base []Port, br *bufio.Reader) ([]Port, error) {
func appendParsePortsNetstat(base []Port, br *bufio.Reader, includeLocalhost bool) ([]Port, error) {
ret := base
var fieldBuf [10]mem.RO
for {
@@ -99,7 +99,7 @@ func appendParsePortsNetstat(base []Port, br *bufio.Reader) ([]Port, error) {
// not interested in non-listener sockets
continue
}
if isLoopbackAddr(laddr) {
if !includeLocalhost && isLoopbackAddr(laddr) {
// not interested in loopback-bound listeners
continue
}
@@ -110,7 +110,7 @@ func appendParsePortsNetstat(base []Port, br *bufio.Reader) ([]Port, error) {
proto = "udp"
laddr = cols[len(cols)-2]
raddr = cols[len(cols)-1]
if isLoopbackAddr(laddr) {
if !includeLocalhost && isLoopbackAddr(laddr) {
// not interested in loopback-bound listeners
continue
}

View File

@@ -8,6 +8,7 @@ package portlist
import (
"bufio"
"encoding/json"
"fmt"
"strings"
"testing"
@@ -52,30 +53,40 @@ udp46 0 0 *.146 *.*
`
func TestParsePortsNetstat(t *testing.T) {
want := List{
Port{"tcp", 23, ""},
Port{"tcp", 24, ""},
Port{"udp", 104, ""},
Port{"udp", 106, ""},
Port{"udp", 146, ""},
Port{"tcp", 8185, ""}, // but not 8186, 8187, 8188 on localhost
}
pl, err := appendParsePortsNetstat(nil, bufio.NewReader(strings.NewReader(netstatOutput)))
if err != nil {
t.Fatal(err)
}
pl = sortAndDedup(pl)
jgot, _ := json.MarshalIndent(pl, "", "\t")
jwant, _ := json.MarshalIndent(want, "", "\t")
if len(pl) != len(want) {
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
}
for i := range pl {
if pl[i] != want[i] {
t.Errorf("row#%d\n got: %+v\n\nwant: %+v\n",
i, pl[i], want[i])
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
}
for _, loopBack := range [...]bool{false, true} {
t.Run(fmt.Sprintf("loopback_%v", loopBack), func(t *testing.T) {
want := List{
{"tcp", 23, "", 0},
{"tcp", 24, "", 0},
{"udp", 104, "", 0},
{"udp", 106, "", 0},
{"udp", 146, "", 0},
{"tcp", 8185, "", 0}, // but not 8186, 8187, 8188 on localhost, when loopback is false
}
if loopBack {
want = append(want,
Port{"tcp", 8186, "", 0},
Port{"tcp", 8187, "", 0},
Port{"tcp", 8188, "", 0},
)
}
pl, err := appendParsePortsNetstat(nil, bufio.NewReader(strings.NewReader(netstatOutput)), loopBack)
if err != nil {
t.Fatal(err)
}
pl = sortAndDedup(pl)
jgot, _ := json.MarshalIndent(pl, "", "\t")
jwant, _ := json.MarshalIndent(want, "", "\t")
if len(pl) != len(want) {
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
}
for i := range pl {
if pl[i] != want[i] {
t.Errorf("row#%d\n got: %+v\n\nwant: %+v\n",
i, pl[i], want[i])
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
}
}
})
}
}

View File

@@ -7,8 +7,8 @@
package portlist
import (
"context"
"errors"
"fmt"
"runtime"
"sync"
"time"
@@ -17,14 +17,25 @@ import (
"tailscale.com/envknob"
)
var pollInterval = 5 * time.Second // default; changed by some OS-specific init funcs
var (
newOSImpl func(includeLocalhost bool) osImpl // if non-nil, constructs a new osImpl.
pollInterval = 5 * time.Second // default; changed by some OS-specific init funcs
debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
)
var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
// PollInterval is the recommended OS-specific interval
// to wait between *Poller.Poll method calls.
func PollInterval() time.Duration {
return pollInterval
}
// Poller scans the systems for listening ports periodically and sends
// the results to C.
type Poller struct {
c chan List // unbuffered
// IncludeLocalhost controls whether services bound to localhost are included.
//
// This field should only be changed before calling Run.
IncludeLocalhost bool
// os, if non-nil, is an OS-specific implementation of the portlist getting
// code. When non-nil, it's responsible for getting the complete list of
@@ -32,14 +43,9 @@ type Poller struct {
// addProcesses is not used.
// A nil values means we don't have code for getting the list on the current
// operating system.
os osImpl
osOnce sync.Once // guards init of os
// closeCtx is the context that's canceled on Close.
closeCtx context.Context
closeCtxCancel context.CancelFunc
runDone chan struct{} // closed when Run completes
os osImpl
initOnce sync.Once // guards init of os
initErr error
// scatch is memory for Poller.getList to reuse between calls.
scratch []Port
@@ -61,123 +67,55 @@ type osImpl interface {
AppendListeningPorts(base []Port) ([]Port, error)
}
// newOSImpl, if non-nil, constructs a new osImpl.
var newOSImpl func() osImpl
var errUnimplemented = errors.New("portlist poller not implemented on " + runtime.GOOS)
// NewPoller returns a new portlist Poller. It returns an error
// if the portlist couldn't be obtained.
func NewPoller() (*Poller, error) {
if debugDisablePortlist() {
return nil, errors.New("portlist disabled by envknob")
}
p := &Poller{
c: make(chan List),
runDone: make(chan struct{}),
}
p.closeCtx, p.closeCtxCancel = context.WithCancel(context.Background())
p.osOnce.Do(p.initOSField)
if p.os == nil {
return nil, errUnimplemented
}
// Do one initial poll synchronously so we can return an error
// early.
if pl, err := p.getList(); err != nil {
return nil, err
} else {
p.setPrev(pl)
}
return p, nil
}
func (p *Poller) setPrev(pl List) {
// Make a copy, as the pass in pl slice aliases pl.scratch and we don't want
// that to except to the caller.
p.prev = slices.Clone(pl)
}
func (p *Poller) initOSField() {
if newOSImpl != nil {
p.os = newOSImpl()
// init initializes the Poller by ensuring it has an underlying
// OS implementation and is not turned off by envknob.
func (p *Poller) init() {
switch {
case debugDisablePortlist():
p.initErr = errors.New("portlist disabled by envknob")
case newOSImpl == nil:
p.initErr = errors.New("portlist poller not implemented on " + runtime.GOOS)
default:
p.os = newOSImpl(p.IncludeLocalhost)
}
}
// Updates return the channel that receives port list updates.
//
// The channel is closed when the Poller is closed.
func (p *Poller) Updates() <-chan List { return p.c }
// Close closes the Poller.
// Run will return with a nil error.
func (p *Poller) Close() error {
p.closeCtxCancel()
<-p.runDone
if p.os != nil {
p.os.Close()
if p.initErr != nil {
return p.initErr
}
return nil
if p.os == nil {
return nil
}
return p.os.Close()
}
// send sends pl to p.c and returns whether it was successfully sent.
func (p *Poller) send(ctx context.Context, pl List) (sent bool, err error) {
select {
case p.c <- pl:
return true, nil
case <-ctx.Done():
return false, ctx.Err()
case <-p.closeCtx.Done():
return false, nil
// Poll returns the list of listening ports, if changed from
// a previous call as indicated by the changed result.
func (p *Poller) Poll() (ports []Port, changed bool, err error) {
p.initOnce.Do(p.init)
if p.initErr != nil {
return nil, false, fmt.Errorf("error initializing poller: %w", p.initErr)
}
}
// Run runs the Poller periodically until either the context
// is done, or the Close is called.
//
// Run may only be called once.
func (p *Poller) Run(ctx context.Context) error {
tick := time.NewTicker(pollInterval)
defer tick.Stop()
return p.runWithTickChan(ctx, tick.C)
}
func (p *Poller) runWithTickChan(ctx context.Context, tickChan <-chan time.Time) error {
defer close(p.runDone)
defer close(p.c)
// Send out the pre-generated initial value.
if sent, err := p.send(ctx, p.prev); !sent {
return err
pl, err := p.getList()
if err != nil {
return nil, false, err
}
for {
select {
case <-tickChan:
pl, err := p.getList()
if err != nil {
return err
}
if pl.equal(p.prev) {
continue
}
p.setPrev(pl)
if sent, err := p.send(ctx, p.prev); !sent {
return err
}
case <-ctx.Done():
return ctx.Err()
case <-p.closeCtx.Done():
return nil
}
if pl.equal(p.prev) {
return nil, false, nil
}
p.setPrev(pl)
return p.prev, true, nil
}
func (p *Poller) getList() (List, error) {
if debugDisablePortlist() {
return nil, nil
}
p.osOnce.Do(p.initOSField)
var err error
p.scratch, err = p.os.AppendListeningPorts(p.scratch[:0])
return p.scratch, err

View File

@@ -18,6 +18,7 @@ type Port struct {
Proto string // "tcp" or "udp"
Port uint16 // port number
Process string // optional process name, if found
Pid int // process id, if known
}
// List is a list of Ports.
@@ -69,12 +70,11 @@ func sortAndDedup(ps List) List {
out := ps[:0]
var last Port
for _, p := range ps {
protoPort := Port{Proto: p.Proto, Port: p.Port}
if last == protoPort {
if last.Proto == p.Proto && last.Port == p.Port {
continue
}
out = append(out, p)
last = protoPort
last = p
}
return out
}

View File

@@ -35,25 +35,28 @@ type linuxImpl struct {
procNetFiles []*os.File // seeked to start & reused between calls
readlinkPathBuf []byte
known map[string]*portMeta // inode string => metadata
br *bufio.Reader
known map[string]*portMeta // inode string => metadata
br *bufio.Reader
includeLocalhost bool
}
type portMeta struct {
port Port
pid int
keep bool
needsProcName bool
}
func newLinuxImplBase() *linuxImpl {
func newLinuxImplBase(includeLocalhost bool) *linuxImpl {
return &linuxImpl{
br: bufio.NewReader(eofReader),
known: map[string]*portMeta{},
br: bufio.NewReader(eofReader),
known: map[string]*portMeta{},
includeLocalhost: includeLocalhost,
}
}
func newLinuxImpl() osImpl {
li := newLinuxImplBase()
func newLinuxImpl(includeLocalhost bool) osImpl {
li := newLinuxImplBase(includeLocalhost)
for _, name := range []string{
"/proc/net/tcp",
"/proc/net/tcp6",
@@ -220,7 +223,7 @@ func (li *linuxImpl) parseProcNetFile(r *bufio.Reader, fileBase string) error {
// If a port is bound to localhost, ignore it.
// TODO: localhost is bigger than 1 IP, we need to ignore
// more things.
if mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost)) {
if !li.includeLocalhost && (mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost))) {
continue
}
@@ -315,6 +318,9 @@ func (li *linuxImpl) findProcessNames(need map[string]*portMeta) error {
}
argv := strings.Split(strings.TrimSuffix(string(bs), "\x00"), "\x00")
if p, err := mem.ParseInt(pid, 10, 0); err == nil {
pe.pid = int(p)
}
pe.port.Process = argvSubject(argv...)
pe.needsProcName = false
delete(need, string(targetBuf[:n]))

View File

@@ -89,7 +89,7 @@ func TestParsePorts(t *testing.T) {
if tt.file != "" {
file = tt.file
}
li := newLinuxImplBase()
li := newLinuxImplBase(false)
err := li.parseProcNetFile(r, file)
if err != nil {
t.Fatal(err)
@@ -118,7 +118,7 @@ func BenchmarkParsePorts(b *testing.B) {
contents.WriteString(" 3: 69050120005716BC64906EBE009ECD4D:D506 0047062600000000000000006E171268:01BB 01 00000000:00000000 02:0000009E 00000000 1000 0 151042856 2 0000000000000000 21 4 28 10 -1\n")
}
li := newLinuxImplBase()
li := newLinuxImplBase(false)
r := bytes.NewReader(contents.Bytes())
br := bufio.NewReader(&contents)

View File

@@ -29,8 +29,9 @@ type macOSImpl struct {
known map[protoPort]*portMeta // inode string => metadata
netstatPath string // lazily populated
br *bufio.Reader // reused
portsBuf []Port
br *bufio.Reader // reused
portsBuf []Port
includeLocalhost bool
}
type protoPort struct {
@@ -43,10 +44,11 @@ type portMeta struct {
keep bool
}
func newMacOSImpl() osImpl {
func newMacOSImpl(includeLocalhost bool) osImpl {
return &macOSImpl{
known: map[protoPort]*portMeta{},
br: bufio.NewReader(bytes.NewReader(nil)),
known: map[protoPort]*portMeta{},
br: bufio.NewReader(bytes.NewReader(nil)),
includeLocalhost: includeLocalhost,
}
}
@@ -119,7 +121,7 @@ func (im *macOSImpl) appendListeningPortsNetstat(base []Port) ([]Port, error) {
defer cmd.Process.Wait()
defer cmd.Process.Kill()
return appendParsePortsNetstat(base, im.br)
return appendParsePortsNetstat(base, im.br, im.includeLocalhost)
}
var lsofFailed atomic.Bool
@@ -170,6 +172,7 @@ func (im *macOSImpl) addProcesses() error {
im.br.Reset(outPipe)
var cmd, proto string
var pid int
for {
line, err := im.br.ReadBytes('\n')
if err != nil {
@@ -184,6 +187,10 @@ func (im *macOSImpl) addProcesses() error {
// starting a new process
cmd = ""
proto = ""
pid = 0
if p, err := mem.ParseInt(mem.B(val), 10, 0); err == nil {
pid = int(p)
}
case 'c':
cmd = string(val) // TODO(bradfitz): avoid garbage; cache process names between runs?
case 'P':
@@ -202,6 +209,7 @@ func (im *macOSImpl) addProcesses() error {
switch {
case m != nil:
m.port.Process = cmd
m.port.Pid = pid
default:
// ignore: processes and ports come and go
}

View File

@@ -4,13 +4,8 @@
package portlist
import (
"context"
"flag"
"net"
"runtime"
"sync"
"testing"
"time"
"tailscale.com/tstest"
)
@@ -19,14 +14,14 @@ func TestGetList(t *testing.T) {
tstest.ResourceCheck(t)
var p Poller
pl, err := p.getList()
pl, _, err := p.Poll()
if err != nil {
t.Fatal(err)
}
for i, p := range pl {
t.Logf("[%d] %+v", i, p)
}
t.Logf("As String: %v", pl.String())
t.Logf("As String: %s", List(pl))
}
func TestIgnoreLocallyBoundPorts(t *testing.T) {
@@ -40,7 +35,7 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
ta := ln.Addr().(*net.TCPAddr)
port := ta.Port
var p Poller
pl, err := p.getList()
pl, _, err := p.Poll()
if err != nil {
t.Fatal(err)
}
@@ -51,27 +46,20 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
}
}
var flagRunUnspecTests = flag.Bool("run-unspec-tests",
runtime.GOOS == "linux", // other OSes have annoying firewall GUI confirmation dialogs
"run tests that require listening on the the unspecified address")
func TestChangesOverTime(t *testing.T) {
if !*flagRunUnspecTests {
t.Skip("skipping test without --run-unspec-tests")
}
func TestPoller(t *testing.T) {
var p Poller
p.IncludeLocalhost = true
get := func(t *testing.T) []Port {
t.Helper()
s, err := p.getList()
s, _, err := p.Poll()
if err != nil {
t.Fatal(err)
}
return append([]Port(nil), s...)
return s
}
p1 := get(t)
ln, err := net.Listen("tcp", ":0")
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Skipf("failed to bind: %v", err)
}
@@ -184,68 +172,21 @@ func TestEqualLessThan(t *testing.T) {
}
}
func TestPoller(t *testing.T) {
p, err := NewPoller()
func TestClose(t *testing.T) {
var p Poller
err := p.Close()
if err != nil {
t.Fatal(err)
}
p = Poller{}
_, _, err = p.Poll()
if err != nil {
t.Skipf("skipping due to poll error: %v", err)
}
err = p.Close()
if err != nil {
t.Skipf("not running test: %v", err)
}
defer p.Close()
var wg sync.WaitGroup
wg.Add(2)
gotUpdate := make(chan bool, 16)
go func() {
defer wg.Done()
for pl := range p.Updates() {
// Look at all the pl slice memory to maximize
// chance of race detector seeing violations.
for _, v := range pl {
if v == (Port{}) {
// Force use
panic("empty port")
}
}
select {
case gotUpdate <- true:
default:
}
}
}()
tick := make(chan time.Time, 16)
go func() {
defer wg.Done()
if err := p.runWithTickChan(context.Background(), tick); err != nil {
t.Error("runWithTickChan:", err)
}
}()
for i := 0; i < 10; i++ {
ln, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatal(err)
}
defer ln.Close()
tick <- time.Time{}
select {
case <-gotUpdate:
case <-time.After(5 * time.Second):
t.Fatal("timed out waiting for update")
}
}
// And a bunch of ticks without waiting for updates,
// to make race tests more likely to fail, if any present.
for i := 0; i < 10; i++ {
tick <- time.Time{}
}
if err := p.Close(); err != nil {
t.Fatal(err)
}
wg.Wait()
}
func BenchmarkGetList(b *testing.B) {
@@ -259,6 +200,11 @@ func BenchmarkGetListIncremental(b *testing.B) {
func benchmarkGetList(b *testing.B, incremental bool) {
b.ReportAllocs()
var p Poller
p.init()
if p.initErr != nil {
b.Skip(p.initErr)
}
b.Cleanup(func() { p.Close() })
for i := 0; i < b.N; i++ {
pl, err := p.getList()
if err != nil {

View File

@@ -25,7 +25,8 @@ type famPort struct {
}
type windowsImpl struct {
known map[famPort]*portMeta // inode string => metadata
known map[famPort]*portMeta // inode string => metadata
includeLocalhost bool
}
type portMeta struct {
@@ -33,9 +34,10 @@ type portMeta struct {
keep bool
}
func newWindowsImpl() osImpl {
func newWindowsImpl(includeLocalhost bool) osImpl {
return &windowsImpl{
known: map[famPort]*portMeta{},
known: map[famPort]*portMeta{},
includeLocalhost: includeLocalhost,
}
}
@@ -58,7 +60,7 @@ func (im *windowsImpl) AppendListeningPorts(base []Port) ([]Port, error) {
if e.State != "LISTEN" {
continue
}
if !e.Local.Addr().IsUnspecified() {
if !im.includeLocalhost && !e.Local.Addr().IsUnspecified() {
continue
}
fp := famPort{
@@ -83,6 +85,7 @@ func (im *windowsImpl) AppendListeningPorts(base []Port) ([]Port, error) {
Proto: "tcp",
Port: e.Local.Port(),
Process: process,
Pid: e.Pid,
},
}
im.known[fp] = pm

View File

@@ -124,10 +124,11 @@ func runBuild(ctx context.Context, filters []string, targets []dist.Target) erro
if err != nil {
return fmt.Errorf("getting absolute path of manifest: %w", err)
}
fmt.Println(manifest)
fmt.Println(filepath.Join(b.Out, out[0]))
for i := range out {
rel, err := filepath.Rel(filepath.Dir(manifest), filepath.Join(b.Out, out[i]))
if !filepath.IsAbs(out[i]) {
out[i] = filepath.Join(b.Out, out[i])
}
rel, err := filepath.Rel(filepath.Dir(manifest), out[i])
if err != nil {
return fmt.Errorf("making path relative: %w", err)
}

View File

@@ -17,6 +17,7 @@ import (
"sort"
"strings"
"sync"
"time"
"tailscale.com/util/multierr"
"tailscale.com/version/mkversion"
@@ -44,6 +45,8 @@ type Build struct {
Go string
// Version is the version info of the build.
Version mkversion.VersionInfo
// Time is the timestamp of the build.
Time time.Time
// once is a cache of function invocations that should run once per process
// (for example building a helper docker container)
@@ -86,6 +89,7 @@ func NewBuild(repo, out string) (*Build, error) {
Out: out,
Go: goTool,
Version: mkversion.Info(),
Time: time.Now().UTC(),
extra: map[any]any{},
goBuildLimit: make(chan struct{}, runtime.NumCPU()),
}
@@ -114,6 +118,9 @@ func (b *Build) Build(targets []Target) (files []string, err error) {
go func(i int, t Target) {
var err error
defer func() {
if err != nil {
err = fmt.Errorf("%s: %w", t, err)
}
errs[i] = err
wg.Done()
}()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,6 @@
[Tailscale]
title="Tailscale"
desc="Tailscale VPN"
port_forward="no"
src.ports="41641/udp"
dst.ports="41641/udp"

12
release/dist/synology/files/config vendored Normal file
View File

@@ -0,0 +1,12 @@
{
".url": {
"SYNO.SDS.Tailscale": {
"type": "url",
"version": "1.8.3",
"title": "Tailscale",
"icon": "PACKAGE_ICON_256.PNG",
"url": "webman/3rdparty/Tailscale/",
"urlTarget": "_syno_tailscale"
}
}
}

2
release/dist/synology/files/index.cgi vendored Executable file
View File

@@ -0,0 +1,2 @@
#! /bin/sh
exec /var/packages/Tailscale/target/bin/tailscale web -cgi

View File

@@ -0,0 +1,8 @@
/var/packages/Tailscale/etc/tailscaled.stdout.log {
size 10M
rotate 3
missingok
copytruncate
compress
notifempty
}

View File

@@ -0,0 +1,8 @@
/var/packages/Tailscale/var/tailscaled.stdout.log {
size 10M
rotate 3
missingok
copytruncate
compress
notifempty
}

View File

@@ -0,0 +1,7 @@
{
"defaults":{
"run-as": "root"
},
"username": "tailscale",
"groupname": "tailscale"
}

View File

@@ -0,0 +1,7 @@
{
"defaults":{
"run-as": "package"
},
"username": "tailscale",
"groupname": "tailscale"
}

View File

@@ -0,0 +1,13 @@
{
"defaults":{
"run-as": "package"
},
"username": "tailscale",
"groupname": "tailscale",
"tool": [{
"relpath": "bin/tailscaled",
"user": "package",
"group": "package",
"capabilities": "cap_net_admin,cap_chown,cap_net_raw"
}]
}

Some files were not shown because too many files have changed in this diff Show More