Compare commits

...

395 Commits

Author SHA1 Message Date
Brad Fitzpatrick
ccdc41988c cmd/tailscale, ipn/ipn{local,server}: add start of CLI admin API + over Noise
Change-Id: I2936f6baf50e7eeac7190051adba493d4245b3ea
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-20 13:01:18 -07:00
Brad Fitzpatrick
bfb4a4d9e9 tsnet: fix format string/argument mismatch in log output
Change-Id: Ia7291ea47a289baec6cc6013d63d2f248ae57d9e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-19 20:24:33 -07:00
David Anderson
19f61607b6 prober: run all probes once on initial registration.
Turns out, it's annoying to have to wait the entire interval
before getting any monitorable data, especially for very long
interval probes like hourly/daily checks.

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-03-19 18:54:33 -07:00
David Anderson
e41a3b983c prober: library to build healthchecking probers.
Signed-off-by: David Anderson <danderson@tailscale.com>
2022-03-19 18:38:32 -07:00
Brad Fitzpatrick
f2041c9088 all: use strings.Cut even more
Change-Id: I943ce72c6f339589235bddbe10d07799c4e37979
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-19 13:02:38 -07:00
Brad Fitzpatrick
f30473211b ssh/tailssh: start of implementing optional session recording
To asciinema cast format.

Updates #3802

Change-Id: Ifd3ea31922cd2c99068369cb1650e21f2545b0e1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-19 12:59:51 -07:00
Josh Bleecher Snyder
32fd42430b all: use cibuild.On
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-18 15:19:26 -07:00
Maisem Ali
b775df0b57 ssh/tailssh_test: skip TestSSH/stdin in CI
Updates #4051

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-18 10:57:12 -07:00
Maisem Ali
309c0a13a5 tsweb: add FQDN to Port80Handler to allow HTTPS redirects
When the request comes in say over http://mon, the current
implementation would rewrite it https://mon which causes the cert
validation to fail. This PR keeps the existing behavior intact but also
allows passing in a FQDN to the handler to reroute to the correct
hostname.

Related to https://github.com/tailscale/tailscale/pull/4208#pullrequestreview-913832340

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-18 10:16:08 -07:00
Maisem Ali
7f3d0992aa Makefile: use ./tool/go everywhere
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-18 10:13:18 -07:00
Aaron Klotz
6e91f872af net/tshttpproxy: ensure we pass the correct flags to WinHttpOpen on Win7 and Win8.0
The best flag to use on Win7 and Win8.0 is deprecated in Win8.1, so we resolve
the flag depending on OS version info.

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

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-03-18 11:05:02 -06:00
Brad Fitzpatrick
1db46919ab cmd/tailscaled: make build fail nicely on older Go versions
Due to a bug in Go (golang/go#51778), cmd/go doesn't warn about your
Go version being older than the go.mod's declared Go version in that
case that package loading fails before the build starts, such as when
you use packages that are only in the current version of Go, like our
use of net/netip.

This change works around that Go bug by adding build tags and a
pre-Go1.18-only file that will cause Go 1.17 and earlier to fail like:

    $ ~/sdk/go1.17/bin/go install ./cmd/tailscaled
    # tailscale.com/cmd/tailscaled
    ./required_version.go:11:2: undefined: you_need_Go_1_18_to_compile_Tailscale
    note: module requires Go 1.18

Change-Id: I39f5820de646703e19dde448dd86a7022252f75c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-18 08:25:51 -07:00
David Anderson
2a412ac9ee .github/workflows: work around golang/go#51629
Incidentally, simplify the go generate CI workflow, by
marking the dnsfallback update non-hermetic (so CI will
skip it) rather than manually filter it out of `go list`.

Updates #4194

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-03-17 17:22:17 -07:00
Brad Fitzpatrick
18818763d1 derp: set Basic Constraints on metacert
See https://github.com/golang/go/issues/51759#issuecomment-1071147836

Once we deploy this, tailscaled should work again for macOS users with
Go 1.18.

Updates golang/go#51759

Change-Id: I869b6ddc556a2de885e96ccf9f335dfc8f6f6a7e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-17 15:38:21 -07:00
Simon Deziel
eaf5591953 scripts: install gnupg only when apt-key is needed
apt-key depends on gnupg but apt-key itself if not used
on modern systems (APT_KEY_TYPE=keyring).

Signed-off-by: Simon Deziel <simon@sdeziel.info>
2022-03-17 15:11:25 -07:00
Maisem Ali
bd073b8dd6 types/views: rename Generic to Unwrap
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-17 14:41:57 -07:00
Maisem Ali
1e12a29806 ssh/tailssh_test: Skip the env test in CI
Updates #4051

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-17 14:34:49 -07:00
Josh Bleecher Snyder
0868329936 all: use any instead of interface{}
My favorite part of generics.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-17 11:35:09 -07:00
Josh Bleecher Snyder
5f176f24db go.mod: upgrade to the latest wireguard-go
This pulls in a handful of fixes and an update to Go 1.18.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-17 10:59:39 -07:00
Brad Fitzpatrick
2708544018 tsnet: add some usability polish, remove WIP env var restriction
Change-Id: Id9ec1713c65cdd597d20b03e21e11cd60b54bb6a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-17 10:59:21 -07:00
Josh Bleecher Snyder
997b19545b syncs: use TryLock and TryRLock instead of unsafe
The docs say:

Note that while correct uses of TryLock do exist, they are rare,
and use of TryLock is often a sign of a deeper problem in a particular use of mutexes.

Rare code! Or bad code! Who can tell!

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-17 10:57:41 -07:00
Brad Fitzpatrick
ead16b24ec cmd/tailscaled: fail early with nice error on macOS with go1.18
Due to golang/go#51759

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-17 10:41:50 -07:00
Josh Bleecher Snyder
9d4ffd135f go.toolchain.rev: pick up crypto/x509 crash fix
68c97fb924

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-17 10:37:15 -07:00
Maisem Ali
6b9d938c1a types/views: add generic Slice[T] and remove StringSlice
Also make IPPrefixSliceOf use Slice[netaddr.IPPrefix] as it also
provides additional functions besides the standard ones provided by
Slice[T].

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-16 22:13:16 -07:00
Denton Gentry
d8953bf2ba cmd/derpprobe: don't alert for smaller failures.
There is a Cosmic Background level of DERP Unreachability,
with individual nodes or regions becoming unreachable briefly
and returning a short time later. This is due to hosting provider
outages or just the Internet sloshing about.

Returning a 500 error pages a human. Being awoken at 3am for
a transient error is annoying.

For relatively small levels of badness don't page a human,
just post to Slack. If the outage impacts a significant fraction
of the DERP fleet, then page a human.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-03-16 18:22:22 -07:00
Josh Bleecher Snyder
84a2dc3a7e go.toolchain.rev: update to slightly less forked Go 1.18
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 16:10:29 -07:00
Josh Bleecher Snyder
8c2cb4b431 go.mod: update to latest certstore
It includes a fix to allow us to use Go 1.18.
We can now remove our Tailscale-only build tags.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 16:10:29 -07:00
Brad Fitzpatrick
61ee72940c all: use Go 1.18's strings.Cut
More remain.

Change-Id: I6ec562cc1f687600758deae1c9d7dbd0d04004cb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-16 14:53:59 -07:00
Brad Fitzpatrick
1f22507c06 version: use Go 1.18's git stamping as default implementation
No more manual version bumps!

Fixes #81

Change-Id: I3a9e544a7248f0b83bcbacbaabbc4dabc435e62d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-16 14:53:51 -07:00
Josh Bleecher Snyder
c2c97f8f38 go.toolchain.rev: remove second entry
No idea how that happened.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:55:26 -07:00
Josh Bleecher Snyder
26021b07ec control/controlclient: only build certstore-related code with the Tailscale Go toolchain
The certstore code is impacted by golang/go#51726.
The Tailscale Go toolchain fork contains a temporary workaround,
so it can compile it. Once the upstream toolchain can compile certstore,
presumably in Go 1.18.1, we can revert this change.

Note that depaware runs with the upstream toolchain.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
0ef74f37a5 net/dns/resolver: remove closure allocation explanation
As of Go 1.18, the register ABI list includes arm64, amd64,
ppc64, and ppc64le. This is a large enough percentage of the
architectures that it's not worth explaining.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
9482576bb1 ipn/ipnserver: use strings.Cut
We now require Go 1.18.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
97a01b7b17 util/deephash: remove Tailscale toolchain compatibility shim
The future is now.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
1b57b0380d wgengine/magicsock: remove final alloc from ReceiveFrom
And now that we don't have to play escape analysis and inlining games,
simplify the code.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
463728a885 util/netconv: add package to convert between netip and netaddr types
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
5cb9999be3 go.toolchain.rev: upgrade to our Go 1.18 fork
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
927fc36123 go.toolchain.branch: upgrade to Go 1.18
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
71b535fc94 go.mod: require Go 1.18
Also, update depaware for Go 1.18's dependency tree.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
f695f0b178 go.mod: update golang.org/x/tools and honnef.co/go/tools
This is required for staticcheck to process code
using Go 1.18.

This puts us on a random commit on the bleeding edge
of staticcheck, which isn't great, but there don't
appear to have been any releases yet that support 1.18.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
f143ff89b7 README.md: update current Go release
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
d77b4c1344 Dockerfile: require Go 1.18
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
4b1e02057a .github/workflows: request Go 1.18
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
Josh Bleecher Snyder
08cf54f386 wgengine/magicsock: fix goMajorVersion for 1.18 ts release
The version string changed slightly. Adapt.
And always check the current Go version to prevent future
accidental regressions. I would have missed this one had
I not explicitly manually checked it.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-16 12:45:28 -07:00
David Eger
5be42c0af1 cmd/tailscale: add file get options for dealing with existing files
A new flag --conflict=(skip|overwrite|rename) lets users specify
what to do when receiving files that match a same-named file in
the target directory.

Updates #3548

Signed-off-by: David Eger <david.eger@gmail.com>
2022-03-16 12:05:41 -07:00
Maisem Ali
07f48a7bfe wgengine: handle nil netmaps when assigning isSubnetRouter.
Fixes tailscale/coral#51

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-16 10:51:12 -07:00
James Tucker
858286d97f github/windows: improve caching in -race builder (#4172)
Signed-off-by: James Tucker <james@tailscale.com>
Co-authored-by: James Tucker <james@tailscale.com>
2022-03-15 10:04:02 -07:00
Brad Fitzpatrick
5f529d1359 logtail: add Logger.PrivateID accessor
For the control plane to use.

Change-Id: I0f02321fc4fa3a41c3ece3b51eee729ea9770905
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-14 20:59:04 -07:00
James Tucker
36b148c2d2 github/windows: improve cache performance (#4171)
- Remove the expanded module files, as Go can likely expand the zips
  faster than tar can expand the extra copies.
- Add the go-build cache.
- Remove the extra restore key to avoid extra cache lookups on miss.

Signed-off-by: James Tucker <james@tailscale.com>
Co-authored-by: James Tucker <james@tailscale.com>
2022-03-14 17:10:13 -07:00
Maisem Ali
45a7f6689c tailcfg: add field to allow LocalPortForwarding in SSHAction
Updates #3802, #4129

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-14 13:39:42 -07:00
Maisem Ali
98b45ef12c ssh/tailssh: add support for agent forwarding.
Updates #3802

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-14 13:38:53 -07:00
Brad Fitzpatrick
6e86bbcb06 ssh/tailssh: add a new sshSession type to clean up existing+future code
Updates #3802

Change-Id: I7054dca387f5e5aee1185937ecf41b77a5a07f1a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Co-authored-by: Maisem Ali <maisem@tailscale.com>
2022-03-14 12:01:49 -07:00
Maisem Ali
462e75666b ssh/tailssh: start sending the server version
Updates #3802

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-12 19:40:51 -08:00
Maisem Ali
bf3559171f ssh/tailssh: set DBUS_SESSION_BUS_ADDRESS and SSH_TTY variables
Updates #3802

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-12 19:40:51 -08:00
Maisem Ali
6d61b7906e ssh/tailssh: handle terminal opcodes
Updates #3802 #4146

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-12 17:57:07 -08:00
Maisem Ali
da6ce27416 go.mod: move from github.com/gliderlabs/ssh to github.com/tailscale/ssh
Updates #4146

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-12 17:57:07 -08:00
Brad Fitzpatrick
012098ec32 ssh/tailssh: fix terminal corruption (temporary hack)
Maisem figured out the real problem but will take several commits
(e.g. tailscale/ssh#2) in different repos to get it fixed
properly. This is an interim hack.

Details of real fix:
https://github.com/tailscale/tailscale/issues/4146#issuecomment-1065952947

Updates #4146
Updates #3802

Change-Id: I7b7dc5713baa3e5de75b87b69e7179a6e7549b0b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-12 14:19:45 -08:00
Brad Fitzpatrick
ba1adf6c24 ssh/tailssh: make pty termios options match OpenSSH
Still not sure the exact rules of how/when/who's supposed to set
these, but this works for now on making them match. Baby steps.
Will research more and adjust later.

Updates #4146 (but not enough to fix it, something's still wrong)
Updates #3802

Change-Id: I496d8cd7e31d45fe9ede88fc8894f35dc096de67
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-11 12:16:10 -08:00
Brad Fitzpatrick
1dd5cf62a5 ssh/tailssh: start login shell, fix arg passing, width/height mismatch
Updates #3802

Change-Id: I137d7a79195ee86d5dd7c8999f2797fc3cb57cec
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-10 20:11:41 -08:00
Brad Fitzpatrick
efc48b0578 ssh/tailssh, ipnlocal, controlclient: fetch next SSHAction from network
Updates #3802

Change-Id: I08e98805ab86d6bbabb6c365ed4526f54742fd8e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-10 13:41:08 -08:00
Brad Fitzpatrick
6b11004a2a control/controlclient: proactively close TLS connection after /key fetch
When using Noise.

Updates #3488

Change-Id: I1049963763075a15b72fd8065dcf44a9cf37975f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-10 13:26:18 -08:00
Aaron Klotz
f8a4df66de cmd/tailscale/cli, ipn: move exit node IP parsing and validation from cli into prefs.
We need to be able to provide the ability for the GUI clients to resolve and set
the exit node IP from an untrusted string, thus enabling the ability to specify
that information via enterprise policy.

This patch moves the relevant code out of the handler for `tailscale up`,
into a method on `Prefs` that may then be called by GUI clients.

We also update tests accordingly.

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

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-03-10 10:51:05 -07:00
Maisem Ali
888e50e1f6 ipn/ipnlocal: migrate all platforms to controlplane.tailscale.com
Updates #3488

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-09 21:14:06 -08:00
Nick O'Neill
1625e87526 control/controlclient, localapi: shorten expiry time via localapi (#4112)
Signed-off-by: Nick O'Neill <nick@tailscale.com>
2022-03-09 14:42:42 -08:00
Maisem Ali
2bcc047d4f tailcfg: bump capVer for Noise
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-09 14:41:24 -08:00
Joonas Kuorilehto
c1b3500a05 cmd/tailscale: allow use of flags in gokrazy
Enable use of command line arguments with tailscale cli on gokrazy. Before
this change using arguments like "up" would cause tailscale cli to be
repeatedly restarted by gokrazy process supervisor.

We never want to have gokrazy restart tailscale cli, even if user would
manually start the process.

Expected usage is that user creates files:

flags/tailscale.com/cmd/tailscale/flags.txt:

    up

flags/tailscale.com/cmd/tailscaled/flags.txt:

    --statedir=/perm/tailscaled/
    --tun=userspace-networking

Then tailscale prints URL for user to log in with browser.

Alternatively it should be possible to use up with auth key to allow
unattended gokrazy installs.

Signed-off-by: Joonas Kuorilehto <joneskoo@derbian.fi>
2022-03-09 12:30:32 -08:00
Maisem Ali
2c89b3a601 control/controlbase: make Conn.Write return consumed bytes
Currently `Write` returns the number of ciphertext bytes written.
According to the docs for io.Writer, Write should return the amount
of bytes consumed from the input.
```
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
Write(p []byte) (n int, err error)
```

Fixes #4126

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-09 11:42:11 -08:00
Maisem Ali
e82a74553b control/controlclient: make MapRequests go over noise.
Updates #3488

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-09 11:32:11 -08:00
Maisem Ali
56bf2ce642 ssh/tailssh: handle local port forwarding
Updates #3802

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-09 11:31:04 -08:00
Maisem Ali
598c7a22e7 ssh/tailssh: use lu.Username not lu.Name.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-08 22:39:03 -08:00
Maisem Ali
06c147d848 ssh/tailssh: create login sessions for new connections
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-08 21:47:19 -08:00
Maisem Ali
ba2c0c3145 control/controlclient: call direct.Close after map requests are complete
This was causing a flake in another repo.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-08 21:17:35 -08:00
Brad Fitzpatrick
61cdcf4082 net/interfaces: add FreeBSD default route lookup (portmapping, etc)
Updates #4101 (probably fixes)

Change-Id: I2b75ee3ced276fb7b211f17c382621cf1ef882fa
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-08 13:02:02 -08:00
Maisem Ali
2fb087891b net/socks5: always close client connections after serving
Customer reported an issue where the connections were not closing, and
would instead just stay open. This commit makes it so that we close out
the connection regardless of what error we see. I've verified locally
that it fixes the issue, we should add a test for this.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-08 12:52:20 -08:00
Maisem Ali
91a8cdc84b control/controlclient: make Auto.Shutdown call Direct.Close
Updates #3488

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-08 11:57:41 -08:00
Maisem Ali
0f37317664 control/controlclient: make RegisterRequest go over Noise
Updates #3488

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-08 11:57:41 -08:00
Brad Fitzpatrick
c4f6df47e5 control/controlclient: fix Noise HTTP/2 regression from earlier commit
Fix regression from 21069124db caught by tests in another repo.

The HTTP/2 Transport that was being returned had a ConnPool that never
dialed.

Updates #3488

Change-Id: I3184d6393813448ae143d37ece14eb732334c05f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-08 09:50:36 -08:00
Brad Fitzpatrick
21069124db control/controlclient: fix the Noise HTTP/2 timeout config
We want to close the connection after a minute of inactivity,
not heartbeat once a minute to keep it alive forever.

Updates #3488

Change-Id: I4b5275e8d1f2528e13de2d54808773c70537db91
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-08 08:48:28 -08:00
Brad Fitzpatrick
740e3c006c cmd/derper: add --stun-port flag
And flesh out docs on the --http-port flag.

Change-Id: If9d42665f67409082081cb9a25ad74e98869337b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-08 07:19:33 -08:00
Maisem Ali
0588ca5d8b control/controlclient: make SetDNS attempt to go over Noise first
Updates #3488

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-07 16:27:13 -08:00
Maisem Ali
da1821197a tailcfg: add SetDNSResponse
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-07 16:27:13 -08:00
Maisem Ali
0f31a0fc76 control/controlclient: add Noise client
Updates #3488

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-07 15:43:19 -08:00
Brad Fitzpatrick
26f27a620a wgengine/router: delete legacy netfilter rule cleanup [Linux]
This was just cleanup for an ancient version of Tailscale. Any such machines
have upgraded since then.

Change-Id: Iadcde05b37c2b867f92e02ec5d2b18bf2b8f653a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-07 14:39:04 -08:00
Maisem Ali
249758df90 control/controlclient: start fetching the server noise key
Updates #3488

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-07 11:29:27 -08:00
Brad Fitzpatrick
d5f8f38ac6 tailcfg: rename map request version to "capability version"
And add a CapabilityVersion type, primarily for documentation.

This makes MapRequest.Version, RegisterRequest.Version, and
SetDNSRequest.Version all use the same version, which will avoid
confusing in the future if Register or SetDNS ever changed their
semantics on Version change. (Currently they're both always 1)

This will requre a control server change to allow a
SetDNSRequest.Version value other than 1 to be deployed first.

Change-Id: I073042a216e0d745f52ee2dbc45cf336b9f84b7c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-06 14:29:08 -08:00
Brad Fitzpatrick
105dfa1efa tailcfg: add OverTLSPublicKeyResponse for the new response from /key
Updates #3488

Change-Id: I8729cb3fb7f6dda1a874f8ae2d9570311ed158db
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-06 13:51:32 -08:00
Robert Fritzsche
0e62a7d1a2 tstime/mono: fix Before function comment
Signed-off-by: Robert Fritzsche <r.fritzsche@gridx.de>
2022-03-05 15:05:57 -08:00
Maisem Ali
c85694fac4 types/views: add ContainsExitRoutes to IPPrefixSlice
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-03-04 09:08:42 -08:00
Brad Fitzpatrick
b493ef5b71 net/tsaddr: add func ContainsExitRoutes
Change-Id: I772441a406083e2fe0f9374b2b23d89aac18928f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-04 08:56:06 -08:00
Josh Bleecher Snyder
7ddf2e2fea go.toolchain.rev: bump to Go 1.17.8
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-03-03 14:51:16 -08:00
Brad Fitzpatrick
f18bb6397b cmd/tailscale: tell gokrazy to not manage the CLI as a daemon
In the future we'll probably want to run the "tailscale web"
server instead, but for now stop the infinite restart loop.

See https://gokrazy.org/userguide/process-interface/ for details.

Updates #1866

Change-Id: I4133a5fdb859b848813972620495865727fe397a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-01 20:35:18 -08:00
Brad Fitzpatrick
db85384f9c cmd/tailscaled: default to userspace-networking mode on gokrazy, set paths
One of the current few steps to run Tailscale on gokrazy is to
specify the --tun=userspace-networking flag:

    https://gokrazy.org/userguide/install/tailscale/

Instead, make it the default for now. Later we can change the
default to kernel mode if available and fall back to userspace
mode like Synology, once #391 is done.

Likewise, set default paths for Gokrazy, as its filesystem hierarchy
is not the Linux standard one. Instead, use the conventional paths as
documented at https://gokrazy.org/userguide/install/tailscale/.

Updates #1866

RELNOTE=default to userspace-networking mode on gokrazy

Change-Id: I3766159a294738597b4b30629d2860312dbb7609
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-01 20:34:45 -08:00
Brad Fitzpatrick
c9a5dadce8 ssh/tailssh: skip flaky test on CI for now
Updates #4051

Change-Id: I94f2165dd248eba9ca3f782c907a13bd6dde4a5e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-01 19:57:07 -08:00
Brad Fitzpatrick
58a6c9b2b8 version, hostinfo: recognize gokrazy as a distro
Now:

/tmp/breakglass3929186798 # /user/tailscale debug hostinfo
{
  "IPNVersion": "1.23.0-date.20220107",
  "OS": "linux",
  "OSVersion": "Gokrazy; kernel=5.16.11",
  "DeviceModel": "Raspberry Pi 4 Model B Rev 1.2",
  "Hostname": "gokrazy",
  "GoArch": "arm64"
}

Also, cache the distro lookup. It doesn't change while the program is
running:

name   old time/op    new time/op    delta
Get-6    5.21µs ± 5%    0.00µs ± 3%   -99.91%  (p=0.008 n=5+5)

name   old alloc/op   new alloc/op   delta
Get-6      792B ± 0%        0B       -100.00%  (p=0.008 n=5+5)

name   old allocs/op  new allocs/op  delta
Get-6      8.00 ± 0%      0.00       -100.00%  (p=0.008 n=5+5)

Updates #1866

Change-Id: Ifb9a63b94287010d3f4c8bfeb6b78119e8a9b203
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-03-01 19:37:20 -08:00
Brad Fitzpatrick
6a2e94cbeb tstime/rate: deflake TestLongRunningQPS even more
Previous de-flakings:
* 8cf1af8a07 for #3733
* 30458c71c8 for #2727

Fixes #4044

Change-Id: I506cf1ff37bb224f5a9929f1998901e60b24535d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-28 20:22:16 -08:00
Brad Fitzpatrick
55095df644 net/interfaces: get Linux default route from netlink as fallback
If it's in a non-standard table, as it is on Unifi UDM Pro, apparently.

Updates #4038 (probably fixes, but don't have hardware to verify)

Change-Id: I2cb9a098d8bb07d1a97a6045b686aca31763a937
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-28 19:57:34 -08:00
Maisem Ali
518f6cee63 ipn/store: [TestNewStore] do not use an empty file
Otherwise it would log warnings about an empty file.
```
    stores.go:138: store.NewFileStore("/tmp/3777352782"): file empty; treating it like a missing file [warning]
```

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-28 18:26:24 -08:00
Maisem Ali
497324ddf6 ipn/store: add common package for instantiating ipn.StateStores
Also move KubeStore and MemStore into their own package.

RELNOTE: tsnet now supports providing a custom ipn.StateStore.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-28 13:23:33 -08:00
Dmytro Shynkevych
d9a7205be5 net/tstun: set link speed to SPEED_UNKNOWN
Fixes #3933.

Signed-off-by: Dmytro Shynkevych <dm.shynk@gmail.com>
2022-02-27 23:11:35 -08:00
Brad Fitzpatrick
5d085a6f41 controlhttp: add some docs, change Dial's path from /switch to /ts2021
When I deployed server-side changes, I put the upgrade handler at /ts2021
instead of /switch. We could move the server to /switch, but ts2021 seems
more specific and better, but I don't feel strongly.

Updates #3488

Change-Id: Ifbf8ea60a815fd2fa1bfbe1b7af1ac2a27218354
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-26 12:44:29 -08:00
Brad Fitzpatrick
4b50977422 ssh/tailssh: add more SSH tests, blend in env from ssh session
Updates #3802

Change-Id: I568c661cacbb0524afcd8be9577457ddba611f19
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 16:02:01 -08:00
Brad Fitzpatrick
4686224e5a cmd/tailscaled: add a no-op test for profiling init-time memory allocs
Turns out we're pretty good already at init-time work in tailscaled.
The regexp/syntax shows up but it's hard to get rid of that; zstd even
uses regexp. *shrug*

Change-Id: I856aca056dcb7489f5fc22ef07f55f34ddf19bd6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 14:58:12 -08:00
Brad Fitzpatrick
4cbdc84d27 cmd/tailscaled/childproc: add be-child registration mechanism
For ssh and maybe windows service babysitter later.

Updates #3802

Change-Id: I7492b98df98971b3fb72d148ba92c2276cca491f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 14:20:20 -08:00
Brad Fitzpatrick
6e4f3614cf ssh/tailssh: add start of real ssh tests
Updates #3802

Change-Id: I9aea4250062d3a06ca7a5e71a81d31c27a988615
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 14:13:12 -08:00
Brad Fitzpatrick
c9eca9451a ssh: make it build on darwin
For local dev testing initially. Product-wise, it'll probably only be
workable on the two unsandboxed builds.

Updates #3802

Change-Id: Ic352f966e7fb29aff897217d79b383131bf3f92b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 13:00:45 -08:00
Brad Fitzpatrick
c4a6d9fa5d ipn/ipnlocal: generate tailscaled-owned SSH keys as needed
Updates #3802

Change-Id: Ie1bc9ae3f3639603b88b4e19b7eb12bea528ff77
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 12:15:57 -08:00
Brad Fitzpatrick
cce6aad6c0 ssh/tailssh: fix non-interactive commands as non-root user
Updates #3802

Change-Id: I89a3f14420b8782bc407b1939dce54a1d24636da
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 12:13:16 -08:00
Brad Fitzpatrick
e2ed06c53c ssh/tailssh: break a method into half in prep for testing
And add a private context type in the process.

Updates #3802

Change-Id: I257187f4cfb0f2248d95b81c1dfe0911ef203b60
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 09:59:00 -08:00
Brad Fitzpatrick
1b5bb2e81d ssh/tailssh: rename sshContext to sshConnInfo
So it's not confused for a context.Context and we can add contexts
later and not look like we have two.

Updates #3802

Change-Id: Icf229ae2c020d173f3cbf09a13ccd03a60cbb85e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-24 09:06:21 -08:00
Denton Gentry
8175504584 VERSION.txt: This is 1.23.
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-02-23 15:51:28 -08:00
Brad Fitzpatrick
3c2cd854be ssh/tailssh: flesh out env, support non-pty commands
Updates #3802

Change-Id: I7022460117542a5424919144828bf571c7c19ec0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-23 15:00:41 -08:00
Brad Fitzpatrick
7d897229d9 net/dns: ignore permission errors on Synology DSM7 for now
Updates #4017

Change-Id: Ia7fd4df47588c010dea8e63d88f397cc8eb748e5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-23 10:13:53 -08:00
Brad Fitzpatrick
29279b34fa cmd/tailscale: make configure-host on Synology also add CAP_NET_RAW
Updates #4012

Change-Id: Ic45b5709a73b4f1cd466823e177b52d1d20ba84e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-23 07:56:38 -08:00
Maisem Ali
38c59c0ad2 tsnet: fix typo in Ephemeral
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-22 15:35:24 -08:00
Brad Fitzpatrick
bb94561c96 net/netutil: fix regression where peerapi would get closed after 1st req
I introduced a bug in 8fe503057d when unifying oneConnListener
implementations.

The NewOneConnListenerFrom API was easy to misuse (its Close method
closes the underlying Listener), and we did (via http.Serve, which
closes the listener after use, which meant we were close the peerapi's
listener, even though we only wanted its Addr)

Instead, combine those two constructors into one and pass in the Addr
explicitly, without delegating through to any Listener.

Change-Id: I061d7e5f842e0cada416e7b2dd62100d4f987125
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-22 13:52:18 -08:00
Aaron Klotz
e31d68d64e hostinfo: use the sentinel value set by the MSI installer to detect MSI package type
The MSI installer sets a special sentinel value that we can use to detect it.

I also removed the code that bails out when the installation path is not
`Program Files`, as both the NSIS and MSI installers permit the user to install
to a different path.

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-02-22 12:48:20 -07:00
Brad Fitzpatrick
4fee321004 hostinfo: move packageType out to platform-specific files
Change-Id: I3236b3d4e2376dd7e2482c2562817b1b6f44872e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-22 10:23:13 -08:00
Maisem Ali
c7a8f0992d ipn/ipnlocal: use views for Peer.PrimaryRoutes and Peer.Tags
RELNOTE=`tailscale status --json` now shows Tags and PrimaryRoutes

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-22 10:09:02 -08:00
Maisem Ali
9cbb0913be ipn/{ipnlocal,ipnstate}: add Tags and PrimaryRoutes to PeerStatus
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-21 20:06:48 -08:00
David Anderson
0fc1479633 go.mod: update github.com/mdlayher/netlink to 1.6.0
This unbreaks some downstream users of tailscale who end up
with build errors from importing a v0 indirect dependency.

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-02-21 14:31:03 -08:00
Brad Fitzpatrick
e921e1b02d cmd/tailscale: add "tailscale debug hostinfo" subcommand
Change-Id: Ifa09364d42e0516fdf80feddaf33c95880228049
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-20 08:06:37 -08:00
Brad Fitzpatrick
300d897fd7 hostinfo: detect NSIS vs MSI package type on Windows
Change-Id: I624a4cb04803e483553eb53c952060393029c435
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-20 07:58:49 -08:00
Brad Fitzpatrick
d19a63ddf6 ipn/localapi: treat ACME "invalid" state as terminal, log more
Fixes #3975

Change-Id: Idb2cc8d4730e140939898c7dcc15c2014acca142
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-19 16:22:58 -08:00
Brad Fitzpatrick
de72a1f9fc ipn/ipnserver: let TS_PERMIT_CERT_UID contain a username too, not just uid
Don't make users map their system's "caddy" (or whatever) system user
to its userid. We can do that. Support either a uid or a username.

RELNOTE=TS_PERMIT_CERT_UID can contain a uid or username

Change-Id: I7451b537a5e118b818addf1353882291d5f0d07f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-19 16:22:43 -08:00
Brad Fitzpatrick
03caa95bf2 ssh/tailssh: get login shell when running as non-root
And also reject attempts to use other users.

Updates #3802

Change-Id: Iddc85f6ea2dba17d12be66a50408d24c1f92833e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-18 19:22:11 -08:00
Brad Fitzpatrick
e1e20f6d39 ssh/tailssh: evaluate tailcfg.SSHPolicy on incoming connections
Updates #3802
Fixes #3960

Change-Id: Ieda2007d462ddce6c217b958167417ae9755774e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-18 18:07:39 -08:00
Josh Bleecher Snyder
66f5aa6814 types/logger: add more reserved top level field names
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-18 15:21:35 -08:00
Maisem Ali
f9a50779e2 cmd/tailscaled: add -state=mem: to support creation of an ephemeral node.
RELNOTE=`tailscaled --state=mem:` registers as an ephemeral node and
does not store state to disk.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-18 13:40:39 -08:00
Josh Bleecher Snyder
823d970d60 control/controlclient: use structured logging for MapResponse.ControlTime
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-18 13:05:42 -08:00
Brad Fitzpatrick
84138450a4 types/logger, logtail: add mechanism to do structured JSON logs
e.g. the change to ipnlocal in this commit ultimately logs out:

{"logtail":{"client_time":"2022-02-17T20:40:30.511381153-08:00","server_time":"2022-02-18T04:40:31.057771504Z"},"type":"Hostinfo","val":{"GoArch":"amd64","Hostname":"tsdev","IPNVersion":"1.21.0-date.20220107","OS":"linux","OSVersion":"Debian 11.2 (bullseye); kernel=5.10.0-10-amd64"},"v":1}

Change-Id: I668646b19aeae4a2fed05170d7b279456829c844
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-18 12:42:06 -08:00
Josh Bleecher Snyder
8c3c5e80b7 tailcfg: make MapResponse.ControlTime a pointer
Otherwise omitempty doesn't work.

This is wire-compatible with a non-pointer type, so switching
is safe, now and in the future.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-18 10:37:27 -08:00
Brad Fitzpatrick
bb93e29d5c tailcfg, ipn/ipnlocal: add Hostinfo.SSH_HostKeys, send when SSH enabled
(The name SSH_HostKeys is bad but SSHHostKeys is worse.)

Updates #3802

Change-Id: I2a889019c9e8b065b668dd58140db4fcab868a91
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-17 15:46:57 -08:00
Josh Bleecher Snyder
4609096271 tailcfg: fix stale docs for MapResponse.KeepAlive
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-17 15:15:43 -08:00
Brad Fitzpatrick
dd6472d4e8 api: document preauthorized auth keys
Fixes #2120

Change-Id: If6a803680b544df1f70449c26fd0f5e15940226b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-17 14:27:39 -08:00
Brad Fitzpatrick
fbff1555fc ipnlocal, tailssh: start moving host key stuff into the right spot
Make tailssh ask LocalBackend for the SSH hostkeys, as we'll need to
distribute them to peers.

For now only the hacky use-same-as-actual-host mode is implemented.

Updates #3802

Change-Id: I819dcb25c14e42e6692c441186c1dc744441592b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-17 14:01:50 -08:00
Josh Bleecher Snyder
94409db7e2 cmd/tailscale: rewrite --authkey to --auth-key
That way humans don't have to remember which is correct.

RELNOTE=--auth-key is the new --authkey, but --authkey still works

Updates tailscale/corp#3486

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-17 10:00:46 -08:00
Xe Iaso
a45f8accdb scripts/installer: add Ubuntu 22.04 LTS Jammy Jellyfish (#3955)
Signed-off-by: Xe Iaso <xe@tailscale.com>
2022-02-17 09:52:35 -05:00
Josh Bleecher Snyder
8cf6d0a17b tailcfg: add MapResponse.ControlTime field
And log it when provided in map responses.

The test uses the date on which I joined Tailscale. :)

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-16 20:18:03 -08:00
Maisem Ali
72d8672ef7 tailcfg: make Node.Hostinfo a HostinfoView
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-16 12:55:57 -08:00
Maisem Ali
53998e26a6 tailcfg: introduce HostinfoView
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-16 12:55:57 -08:00
Brad Fitzpatrick
2ff481ff10 net/dns: add health check for particular broken-ish Linux DNS config
Updates #3937 (need to write docs before closing)

Change-Id: I1df7244cfbb0303481e2621ee750d21358bd67c6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-16 10:40:04 -08:00
Brad Fitzpatrick
57115e923e tailcfg: add start of SSH policy to be sent from control plane to nodes
Updates #3802

Change-Id: Iec58f35d445aaa267d0f7e7e2f30c049c1df4c0e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-15 16:05:42 -08:00
Josh Bleecher Snyder
b486448ab9 go.toolchain.rev: bump to Go 1.17.7
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-15 13:29:17 -08:00
Brad Fitzpatrick
1b87e025e9 ssh/tailssh: move SSH code from wgengine/netstack to this new package
Still largely incomplete, but in a better home now.

Updates #3802

Change-Id: I46c5ffdeb12e306879af801b06266839157bc624
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-15 12:21:01 -08:00
Ross Zurowski
6d02a48d8d ipn: add TailnetStatus field to tailscale status --json (#3865)
We need to capture some tailnet-related information for some Docker
features we're building. This exposes the tailnet name and MagicDNS
information via `tailscale status --json`.

Fixes tailscale/corp#3670

Signed-off-by: Ross Zurowski <ross@rosszurowski.com>
2022-02-15 12:36:01 -05:00
Brad Fitzpatrick
c988bd6ed1 net/dns/resolvconffile: unify three /etc/resolv.conf parsers into new package
Change-Id: I2120893ca802d12f1bd0407d49077d3672627d33
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-14 20:55:57 -08:00
Josh Bleecher Snyder
1dc4151f8b logtail: add MustParsePublicID
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-14 16:00:17 -08:00
Brad Fitzpatrick
8d6cf14456 net/dnscache: don't do bootstrap DNS lookup after most failed dials
If we've already connected to a certain name's IP in the past, don't
assume the problem was DNS related. That just puts unnecessarily load
on our bootstrap DNS servers during regular restarts of Tailscale
infrastructure components.

Also, if we do do a bootstrap DNS lookup and it gives the same IP(s)
that we already tried, don't try them again.

Change-Id: I743e8991a7f957381b8e4c1508b8e9d0df1782fe
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-14 14:28:08 -08:00
Xe Iaso
b4947be0c8 scripts/installer: automagically run apt update (#3939)
When running this script against a totally fresh out of the box Debian
11 image, sometimes it will fail to run because it doesn't have a
package list cached. This patch adds an `apt-get update` to ensure that
the local package cache is up to date.

Signed-off-by: Xe Iaso <xe@tailscale.com>
2022-02-14 15:55:46 -05:00
Brad Fitzpatrick
01e8a152f7 ipn/ipnlocal: log most of Hostinfo once non-verbose at start-up
Our previous Hostinfo logging was all as a side effect of telling
control. And it got marked as verbose (as it was)

This adds a one-time Hostinfo logging that's not verbose, early in
start-up.

Change-Id: I1896222b207457b9bb12ffa7cf361761fa4d3b3a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-14 12:33:35 -08:00
Charlotte Brandhorst-Satzkorn
2448c000b3 words: more hamsters, less hampsters (#3938)
Spell hamster correctly, and add the name of a teeny tiny type of
hamster, the Roborovski dwarf hamster.

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2022-02-14 15:15:30 -05:00
Brad Fitzpatrick
903988b392 net/dnscache: refactor from func-y closure-y state to types & methods
No behavior changes (intended, at least).

This is in prep for future changes to this package, which would get
too complicated in the current style.

Change-Id: Ic260f8e34ae2f64f34819d4a56e38bee8d8ac5ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-14 10:47:48 -08:00
Brad Fitzpatrick
8267ea0f80 net/tstun: remove TODO that's done
This TODO was both added and fixed in 506c727e3.

As I recall, I wasn't originally going to do it because it seemed
annoying, so I wrote the TODO, but then I felt bad about it and just
did it, but forgot to remove the TODO.

Change-Id: I8f3514809ad69b447c62bfeb0a703678c1aec9a3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-13 20:59:47 -08:00
Brad Fitzpatrick
8fe503057d net/netutil: unify two oneConnListeners into a new package
I was about to add a third copy, so unify them now instead.

Change-Id: I3b93896aa1249b1250a6b1df4829d57717f2311a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-13 14:57:27 -08:00
Brad Fitzpatrick
5d9ab502f3 logtail: don't strip verbose level on upload
For analysis of log spam.

Bandwidth is ~unchanged from had we not stripped the "[vN] " from
text; it just gets restructed intot he new "v":N, field.  I guess it
adds one byte.

Updates #1548

Change-Id: Ie00a4e0d511066a33d10dc38d765d92b0b044697
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-13 11:30:37 -08:00
Brad Fitzpatrick
a19c110dd3 envknob: track, log env knobs in use
Fixes #3921

Change-Id: I8186053b5c09c43f0358b4e7fdd131361a6d8f2e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-12 21:56:10 -08:00
Brad Fitzpatrick
2db6cd1025 ipn/ipnlocal, wgengine/magicsock, logpolicy: quiet more logs
Updates #1548

Change-Id: Ied169f872e93be2857890211f2e018307d4aeadc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-12 16:42:29 -08:00
Brad Fitzpatrick
be9d564c29 envknob: remove some stutter from error messages
The strconv errors already stringified with the same.

Change-Id: I6938c5653e9aafa6d9028d45fc26e39eb9ccbaea
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-12 16:36:15 -08:00
Brad Fitzpatrick
3a94ece30c control/controlclient: remove dummy endpoint in endpoint stripping mode
The TODO is done. Magicsock doesn't require any endpoints to create an
*endpoint now.  Verified both in code and empirically: I can use the
env knob and access everything.

Change-Id: I4fe7ed5b11c5c5e94b21ef3d77be149daeab998a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-12 16:36:04 -08:00
Brad Fitzpatrick
86a902b201 all: adjust some log verbosity
Updates #1548

Change-Id: Ia55f1b5dc7dfea09a08c90324226fb92cd10fa00
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-12 08:51:16 -08:00
Adrian Dewhurst
adda2d2a51 control/controlclient: select newer certificate
If multiple certificates match when selecting a certificate, use the one
issued the most recently (as determined by the NotBefore timestamp).
This also adds some tests for the function that performs that
comparison.

Updates tailscale/coral#6

Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
2022-02-11 23:00:22 -05:00
Brad Fitzpatrick
a80cef0c13 cmd/derper: fix regression from bootstrap DNS optimization
The commit b9c92b90db earlier today
caused a regression of serving an empty map always, as it was
JSON marshalling an atomic.Value instead of the DNS entries map
it just built.

Change-Id: I9da3eeca132c6324462dedeaa7d002908557384b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-11 15:28:38 -08:00
Josh Bleecher Snyder
84046d6f7c Revert "cmd/derper: stop setting content header in handleBootstrapDNS"
Didn't help enough. We are setting another header anyway. Restore it.

This reverts commit 60abeb027b.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-11 14:15:28 -08:00
Josh Bleecher Snyder
ec62217f52 cmd/derper: close connections once bootstrap DNS has been served
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-11 14:08:43 -08:00
Brad Fitzpatrick
21358cf2f5 net/dns: slightly optimize dbusPing for non-dbus case [Linux]
Avoid some work when D-Bus isn't running.

Change-Id: I6f89bb75fdb24c13f61be9b400610772756db1ef
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-11 14:00:54 -08:00
Brad Fitzpatrick
37e7a387ff net/dns: remove some unused code for detecting systemd-resolved [Linux]
Change-Id: I19c5fd2cdacfb9e5b688ccd9b4336ae4edffc445
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-11 14:00:54 -08:00
Brad Fitzpatrick
15599323a1 net/dns: fix systemd-resolved detection race at boot
If systemd-resolved is enabled but not running (or not yet running,
such as early boot) and resolv.conf is old/dangling, we weren't
detecting systemd-resolved.

This moves its ping earlier, which will trigger it to start up and
write its file.

Updates #3362 (likely fixes)
Updates #3531 (likely fixes)

Change-Id: I6392944ac59f600571c43b8f7a677df224f2beed
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-11 14:00:54 -08:00
Josh Bleecher Snyder
60abeb027b cmd/derper: stop setting content header in handleBootstrapDNS
No one really cares. Its cost outweighs its usefulness.

name                   old time/op    new time/op    delta
HandleBootstrapDNS-10     105ns ± 4%      65ns ± 2%   -37.68%  (p=0.000 n=15+14)

name                   old alloc/op   new alloc/op   delta
HandleBootstrapDNS-10      416B ± 0%        0B       -100.00%  (p=0.000 n=15+15)

name                   old allocs/op  new allocs/op  delta
HandleBootstrapDNS-10      3.00 ± 0%      0.00       -100.00%  (p=0.000 n=15+15)

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-11 12:43:19 -08:00
Josh Bleecher Snyder
b9c92b90db cmd/derper: optimize handleBootstrapDNS
Do json formatting once, rather than on every request.

Use an atomic.Value.

name                   old time/op    new time/op    delta
HandleBootstrapDNS-10    6.35µs ± 0%    0.10µs ± 4%  -98.35%  (p=0.000 n=14+15)

name                   old alloc/op   new alloc/op   delta
HandleBootstrapDNS-10    3.20kB ± 0%    0.42kB ± 0%  -86.99%  (p=0.000 n=12+15)

name                   old allocs/op  new allocs/op  delta
HandleBootstrapDNS-10      41.0 ± 0%       3.0 ± 0%  -92.68%  (p=0.000 n=15+15)

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-11 12:43:19 -08:00
Josh Bleecher Snyder
e206a3663f cmd/derper: add BenchmarkHandleBootstrapDNS
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-02-11 12:43:19 -08:00
Joe Tsai
0173a50bf0 cmd/derper: add a rate limiter for accepting new connection (#3908)
A large influx of new connections can bring down DERP
since it spins off a new goroutine for each connection,
where each routine may do significant amount of work
(e.g., allocating memory and crunching numbers for TLS crypto).
The momentary spike can cause the process to OOM.

This commit sets the groundwork for limiting connections,
but leaves the limit at infinite by default.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-02-11 12:02:38 -08:00
Denton Gentry
dbea8217ac net/dns: add NetworkManager regression test
Use the exact /etc/resolv.conf file from a user report.
Updates https://github.com/tailscale/tailscale/issues/3531

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-02-10 15:01:49 -08:00
Aaron Klotz
82cd98609f util/winutil: migrate corp's winutil into OSS.
It makes the most sense to have all our utility functions reside in one place.
There was nothing in corp that could not reasonably live in OSS.

I also updated `StartProcessAsChild` to no longer depend on `futureexec`,
thus reducing the amount of code that needed migration. I tested this change
with `tswin` and it is working correctly.

I have a follow-up PR to remove the corresponding code from corp.

The migrated code was mostly written by @alexbrainman.
Sourced from corp revision 03e90cfcc4dd7b8bc9b25eb13a26ec3a24ae0ef9

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-02-10 15:22:55 -07:00
Jay Stapleton
39d173e5fc add -y flag for xbps to allow installation on void
Signed-off-by: Jay Stapleton <jay@tailscale.com>
2022-02-10 16:05:17 -05:00
Jay Stapleton
c8551c8a67 add -y flag for xbps to allow installation on void 2022-02-10 16:05:17 -05:00
Aaron Klotz
3a74f2d2d7 cmd/tailscaled, util/winutil: add accessor functions for Windows system policies.
This patch adds new functions to be used when accessing system policies,
and revises callers to use the new functions. They first attempt the new
registry path for policies, and if that fails, attempt to fall back to the
legacy path.

We keep non-policy variants of these functions because we should be able to
retain the ability to read settings from locations that are not exposed to
sysadmins for group policy edits.

The remaining changes will be done in corp.

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

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-02-09 14:58:51 -07:00
Brad Fitzpatrick
24c9dbd129 tsweb: fix JSONHandlerFunc regression where HTTP status was lost on gzip
Change-Id: Ia7add6cf7e8b46bb6dd45bd3c0371ea79402fb45
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-09 12:17:14 -08:00
Xe Iaso
62db629227 words: add ferret to tails.txt (#3897)
In honor of this post[1] inspiring me to remember that ferrets exist and
have tails.

[1]: https://tech.davidfield.co.uk/what-is-tailscale/

Signed-off-by: Xe <xe@tailscale.com>
2022-02-09 14:40:15 -05:00
Brad Fitzpatrick
3c481d6b18 cmd/tailscale: add "tailscale configure-host" to prep a Synology machine at boot
Updates #3761

Change-Id: Ib5ab9a4808ade074f48d3abee22c57d7670f9e21
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-08 09:26:26 -08:00
Brad Fitzpatrick
b3d268c5a1 control/controlclient: turn off Go's implicit compression
We don't use it anyway, so be explicit that we're not using it.

Change-Id: Iec953271ef0169a2e227811932f5b65b479624af
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-07 13:55:42 -08:00
Brad Fitzpatrick
df8f02db3f tsweb: add gzip support to JSONHandlerFunc
Change-Id: I337e05f92f744bfc7e9d6fb8e67c87c191ba4da8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-07 10:31:25 -08:00
Denton Gentry
16652ae52c installer.sh: accommodate linuxmint versioning.
Recent linuxmint releases now use VERSION_CODENAME for
a linuxmint release (like "uma") and set UBUNTU_CODENAME to
the Ubuntu release they branched from.

Tested in a linuxmint 20.2 VM.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-02-07 07:06:00 -08:00
Sonia Appasamy
aaba49ca10 api.md: add docs for device tags and keys endpoints
Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
2022-02-04 16:20:46 -05:00
Maisem Ali
e64cecac8e chirp: remove regex dependency
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-03 11:24:17 -08:00
Brad Fitzpatrick
2a67beaacf net/interfaces: bound Linux /proc/net/route parsing
tailscaled was using 100% CPU on a machine with ~1M lines, 100MB+
of /proc/net/route data.

Two problems: in likelyHomeRouterIPLinux, we didn't stop reading the
file once we found the default route (which is on the first non-header
line when present). Which meant it was finding the answer and then
parsing 100MB over 1M lines unnecessarily. Second was that if the
default route isn't present, it'd read to the end of the file looking
for it. If it's not in the first 1,000 lines, it ain't coming, or at
least isn't worth having. (it's only used for discovering a potential
UPnP/PMP/PCP server, which is very unlikely to be present in the
environment of a machine with a ton of routes)

Change-Id: I2c4a291ab7f26aedc13885d79237b8f05c2fd8e4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-03 09:31:25 -08:00
Brad Fitzpatrick
0626cf4183 util/winutil: fix build
It was broken on Windows:

Error: util\winutil\winutil_windows.go:15:7: regBase redeclared in this block
Error:                                       D:\a\tailscale\tailscale\util\winutil\winutil_notwindows.go:7:17: previous declaration
Error: util\winutil\winutil_windows.go:29:6: getRegString redeclared in this block
Error:                                       D:\a\tailscale\tailscale\util\winutil\winutil_notwindows.go:9:40: previous declaration
Error: util\winutil\winutil_windows.go:47:6: getRegInteger redeclared in this block
Error:                                       D:\a\tailscale\tailscale\util\winutil\winutil_notwindows.go:11:48: previous declaration
Error: util\winutil\winutil_windows.go:77:6: isSIDValidPrincipal redeclared in this block
Error:                                       D:\a\tailscale\tailscale\util\winutil\winutil_notwindows.go:13:38: previous declaration

Change-Id: Ib1ce4b647f5711547840c736b933a6c42bf09583
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-02 16:45:29 -08:00
Aaron Klotz
d7962e3bcf ipn/ipnserver, util/winutil: update workaround for os/user.LookupId failures on Windows to reject SIDs from deleted/invalid security principals.
Our current workaround made the user check too lax, thus allowing deleted
users. This patch adds a helper function to winutil that checks that the
uid's SID represents a valid Windows security principal.

Now if `lookupUserFromID` determines that the SID is invalid, we simply
propagate the error.

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

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-02-02 15:01:28 -07:00
Brad Fitzpatrick
6eed2811b2 wgengine/netstack: start supporting different SSH users
Updates #3802

Change-Id: I44de6897e36b1362cd74c9b10c9cbfeb9abc3dbc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-02-02 13:58:31 -08:00
Maisem Ali
e3dccfd7ff chirp: handle multiline responses from BIRD
Also add tests to verify the parsing logic.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-02-02 13:48:07 -08:00
Brad Fitzpatrick
fa612c28cf cmd/derper: make --stun default to on, flesh out flag docs
Change-Id: I49e80c61ab19e78e4c8b4bc9012bb70cfe3bfa75
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-31 17:42:45 -08:00
Aaron Bieber
e5cd765e00 net/dns/resolvd: properly handle not having "search" entries
This prevents adding an empty "search" line when no search domains are set.

Signed-off-by: Aaron Bieber <aaron@bolddaemon.com>
2022-01-31 15:11:28 -08:00
Brad Fitzpatrick
bd90781b34 ipn/ipnlocal, wgengine/netstack: use netstack for peerapi server
We're finding a bunch of host operating systems/firewalls interact poorly
with peerapi. We either get ICMP errors from the host or users need to run
commands to allow the peerapi port:

https://github.com/tailscale/tailscale/issues/3842#issuecomment-1025133727

... even though the peerapi should be an internal implementation detail.

Rather than fight the host OS & firewalls, this change handles the
server side of peerapi entirely in netstack (except on iOS), so it
never makes its way to the host OS where it might be messed with. Two
main downsides are:

1) netstack isn't as fast, but we don't really need speed for peerapi.
   And actually, with fewer trips to/from the kernel, we might
   actually make up for some of the netstack performance loss by
   staying in userspace.

2) tcpdump / Wireshark etc packet captures will no longer see the peerapi
   traffic. Oh well. Crawshaw's been wanting to add packet capture server
   support to tailscaled, so we'll probably do that sooner now.

A future change might also then use peerapi for the client-side
(except on iOS).

Updates #3842 (probably fixes, as well as many exit node issues I bet)

Change-Id: Ibc25edbb895dc083d1f07bd3cab614134705aa39
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-31 14:20:08 -08:00
Josh Bleecher Snyder
e45d51b060 logtail: add a few new methods to PublicID
These are for use in our internal systems.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-01-31 14:14:10 -08:00
Brad Fitzpatrick
730aa1c89c derp/derphttp, wgengine/magicsock: prefer IPv6 to DERPs when IPv6 works
Fixes #3838

Change-Id: Ie47a2a30c7e8e431512824798d2355006d72fb6a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-29 15:55:54 -08:00
David Anderson
f5ec916214 cmd/derper: disable TLS 1.0 and 1.1.
Updates tailscale/corp#3568

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-28 01:13:30 +00:00
Brad Fitzpatrick
69392411d9 .github/workflows: add some iOS CI coverage
Updates #3812

Change-Id: Ia779c6a2e9a0fd02418bf5479fdb76d4c80c55a4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-27 15:57:49 -08:00
Brad Fitzpatrick
02bdc654d5 cmd/tailscale: fix up --reset, again
Also fix a somewhat related printing bug in the process where
some paths would print "Success." inconsistently even
when there otherwise was no output (in the EditPrefs path)

Fixes #3830
Updates #3702 (which broke it once while trying to fix it)

Change-Id: Ic51e14526ad75be61ba00084670aa6a98221daa5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-27 15:54:33 -08:00
Brad Fitzpatrick
70d71ba1e7 cmd/derpprobe: check derper TLS certs too
Change-Id: If8c48e012b294570ebbb1a46bacdc58fafbfbcc5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-27 10:09:04 -08:00
Brad Fitzpatrick
1af26222b6 go.mod: bump netstack, switch to upstream netstack
Now that Go 1.17 has module graph pruning
(https://go.dev/doc/go1.17#go-command), we should be able to use
upstream netstack without breaking our private repo's build
that then depends on the tailscale.com Go module.

This is that experiment.

Updates #1518 (the original bug to break out netstack to own module)
Updates #2642 (this updates netstack, but doesn't remove workaround)

Change-Id: I27a252c74a517053462e5250db09f379de8ac8ff
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-26 11:30:03 -08:00
Brad Fitzpatrick
857cd6c0d7 ipn/ipnstate: add TailnetName to status
RELNOTE=tailscale status --json | jq .TailnetName

Fixes tailscale/corp#3665

Change-Id: I85de027ba2781eb31ee1e0c5ab913b0dfa5b4c86
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-26 10:08:39 -08:00
Xe Iaso
ae525a7394 scripts/installer: add Centos Stream 9 support (#3818)
Blocked on package server support being deployed

Signed-off-by: Xe <xe@tailscale.com>
2022-01-26 10:49:32 -05:00
David Anderson
7a18fe3dca wgengine/magicsock: make debugUseDerpRoute an opt.Bool.
Can still be constant, just needs the extra methods.

Fixes #3812

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-25 17:25:08 -08:00
David Anderson
c2059d5b8a words: fallout from mudpuppy-gate.
Salamanders also have no scales. I checked the interweb, and there
doesn't seem to be any subspecies that would let us claim that
*some* salamanders are scaley.

But they are tailey, for sure.

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-26 01:05:49 +00:00
Brad Fitzpatrick
ca774c3249 ipn/ipnserver: add TS_PERMIT_CERT_UID envknob to give webservers cert access
So you can run Caddy etc as a non-root user and let it have access to
get certs.

Updates caddyserver/caddy#4541

Change-Id: Iecc5922274530e2b00ba107d4b536580f374109b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-25 12:12:41 -08:00
David Anderson
508f332bb2 cmd/derper: export TLS vars with derper_ prefix, not stun_
Updates tailscale/corp#3568

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-25 18:57:30 +00:00
davideger
f31546809f cmd/tailscale: propagate tailscaled 403s as AccessDeniedErrors
So Linux/etc CLI users get helpful advice to run tailscale
with --operator=$USER when they try to 'tailscale file {cp,get}'
but are mysteriously forbidden.

Signed-off-by: David Eger <eger@google.com>
Signed-off-by: David Eger <david.eger@gmail.com>
2022-01-25 09:58:21 -08:00
Brad Fitzpatrick
f3c0023add wgengine/netstack: add an SSH server experiment
Disabled by default.

To use, run tailscaled with:

    TS_SSH_ALLOW_LOGIN=you@bar.com

And enable with:

    $ TAILSCALE_USE_WIP_CODE=true tailscale up --ssh=true

Then ssh [any-user]@[your-tailscale-ip] for a root bash shell.
(both the "root" and "bash" part are temporary)

Updates #3802

Change-Id: I268f8c3c95c8eed5f3231d712a5dc89615a406f0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-24 19:14:13 -08:00
Brad Fitzpatrick
41fd4eab5c envknob: add new package for all the strconv.ParseBool(os.Getenv(..))
A new package can also later record/report which knobs are checked and
set. It also makes the code cleaner & easier to grep for env knobs.

Change-Id: Id8a123ab7539f1fadbd27e0cbeac79c2e4f09751
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-24 11:51:23 -08:00
Brad Fitzpatrick
6feb8f4c51 net/dns: log why resolved does not look like it's on use [Linux]
Updates #3742
Updates #3531

Change-Id: I9fc7fa0f4bcab1cf8001ba92408c660a5b25f105
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-24 09:14:53 -08:00
David Anderson
ff3442d92d cmd/derper: record TLS versions used for requests.
Surveying the fleet prior to turning off old/unused/insecure
TLS versions.

Updates tailscale/corp#3615

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-22 02:35:55 +00:00
Maisem Ali
0ada42684b Makefile: update make spk target to use the new go spk builder
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-01-21 12:27:00 -08:00
David Anderson
7ba874d7f1 words: correct inexcusable oversight re: mudpuppies.
Mudpuppies are salamanders, and as such have tails but no scales.
The management apologizes for the error.

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-20 21:12:09 +00:00
Brad Fitzpatrick
92dfaf53bb cmd/tailscaled: include Go runtime metrics in /debug/metrics
Fixes #3772

Change-Id: I237ea23268664d99e83d27890146018b04474556
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-20 09:49:54 -08:00
Aaron Bieber
411c6c316c net/dns/resolvd: store nameservers
Currently only search domains are stored. This was an oversight
(under?) on my part.

As things are now, when MagicDNS is on and "Override local DNS" is
off, the dns forwarder has to timeout before names resolve. This
introduces a pretty annoying lang that makes everything feel
extremely slow. You will also see an error: "upstream nameservers
not set".

I tested with "Override local DNS" on and off. In both situations
things seem to function as expected (and quickly).

Signed-off-by: Aaron Bieber <aaron@bolddaemon.com>
2022-01-19 21:17:31 -08:00
Brad Fitzpatrick
c64af5e676 wgengine/netstack: clear TCP ECN bits before giving to gvisor
Updates #2642

Change-Id: Ic219442a2656dd9dc99ae1dd91e907fd3d924987
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-19 20:09:24 -08:00
Josh Bleecher Snyder
de4696da10 wgengine/magicsock: fix deadlock on shutdown
This fixes a deadlock on shutdown.
One goroutine is waiting to send on c.derpRecvCh before unlocking c.mu.
The other goroutine is waiting to lock c.mu before receiving from c.derpRecvCh.

#3736 has a more detailed explanation of the sequence of events.

Fixes #3736

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-01-19 14:39:28 -08:00
Brad Fitzpatrick
390490e7b1 net/packet: fix typo in comment
Change-Id: Ia666609fde18db44bf38d4e656f490fc372ac3b6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-19 12:59:13 -08:00
Joe Tsai
3e50a265be go.toolchain.rev: pick up new bufio.Writer API for Go1.18 (#3768)
25fe91a25c

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-01-19 12:40:56 -08:00
Brad Fitzpatrick
185825df11 wgengine/netstack: add a missing refcount decrement after packet injection
Fixes #3762
Updates #3745 (probably fixes?)

Change-Id: I1d3f0590fd5b8adfbc9110bc45ff717bb9e79aae
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-19 12:28:43 -08:00
Brad Fitzpatrick
790e41645b wgengine/netstack: add an Impl.Close method for tests
Change-Id: Idbb3fd6d749d3e4effdf96de77a1106584822fef
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-19 12:28:43 -08:00
Brad Fitzpatrick
166fe3fb12 wgengine/netstack: add missing error logging in a RST case
Updates #2642

Change-Id: I9f2f8fd28fc980208b0739eb9caf9db7b0977c09
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-18 14:15:32 -08:00
Brad Fitzpatrick
6be48dfcc6 wgengine/netstack: fix netstack ping timeout on darwin
-W is milliseconds on darwin, not seconds, and empirically it's
milliseconds after a 1 second base.

Change-Id: I2520619e6699d9c505d9645ce4dfee4973555227
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-18 08:00:30 -08:00
David Anderson
96f008cf87 control/controlhttp: package to get a controlbase.Conn over HTTP(S).
Updates #3488

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-17 23:52:27 +00:00
David Anderson
d5a7eabcd0 control/controlbase: enable asynchronous client handshaking.
With this change, the client can obtain the initial handshake message
separately from the rest of the handshake, for embedding into another
protocol. This enables things like RTT reduction by stuffing the
handshake initiation message into an HTTP header.

Similarly, the server API optionally accepts a pre-read Noise initiation
message, in addition to reading the message directly off a net.Conn.

Updates #3488

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-17 23:52:27 +00:00
David Anderson
6cd180746f control/controlbase: rename from control/noise.
Updates #3488

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-17 23:52:27 +00:00
Jay Stapleton
02461ea459 Update installer.sh 2022-01-17 11:37:05 -05:00
Josh Bleecher Snyder
8cf1af8a07 tstime/rate: deflake TestLongRunningQPS
This test set the bar too high.
Just a couple of missed timers was enough to fail.
Change the test to more of a sanity check.
While we're here, run it for just 1s instead of 5s.

Prior to this change, on a 13" M1 MPB, with

stress -p 512 ./rate.test -test.run=QPS

I saw 90%+ failures.

After this change, I'm at 30k runs with no failures yet.

Fixes #3733

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-01-13 16:48:29 -08:00
David Anderson
463b3e8f62 net/dnscache: use tls.Conn.HandshakeContext.
Go 1.17 added a HandshakeContext func to take care of timeouts during
TLS handshaking, so switch from our homegrown goroutine implementation
to the standard way.

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-13 23:01:29 +00:00
David Anderson
a9da6b73a8 net/dnscache: don't cancel the TLS context before writing to the result channel.
Cancelling the context makes the timeout goroutine race with the write that
reports a successful TLS handshake, so you can end up with a successful TLS
handshake that mysteriously reports that it timed out after ~0s in flight.

The context is always canceled and cleaned up as the function exits, which
happens mere microseconds later, so just let function exit clean up and
thereby avoid races.

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-01-13 23:01:29 +00:00
Josh Bleecher Snyder
9fe5ece833 logtail: cap the buffer size in encodeText
This started as an attempt to placate GitHub's code scanner,
but it's also probably generally a good idea.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-01-13 14:37:27 -08:00
Brad Fitzpatrick
5404a0557b wgengine/magicsock: remove a per-DERP-packet map lookup in common case
Updates #150

Change-Id: Iffb6eccbe7ca97af97d29be63b7e37d487b3ba28
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-13 14:13:45 -08:00
Brad Fitzpatrick
5a317d312d wgengine/magicsock: enable DERP Return Path Optimization (DRPO)
Turning this on at the beginning of the 1.21.x dev cycle, for 1.22.

Updates #150

Change-Id: I1de567cfe0be3df5227087de196ab88e60c9eb56
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-13 14:12:09 -08:00
Brad Fitzpatrick
c6c39930cc wgengine/magicsock: fix lock ordering deadlock with derphttp
Fixes #3726

Change-Id: I32631a44dcc1da3ae47764728ec11ace1c78190d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-13 13:47:51 -08:00
Josh Bleecher Snyder
a076aaecc6 cmd/tailscale: use html/template for synoTokenRedirect
The GitHub code scanner flagged this as a security vulnerability.
I don't believe it was, but I couldn't convince myself of it 100%.
Err on the safe side and use html/template to generate the HTML,
with all necessary escaping.

Fixes tailscale/corp#2698

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-01-13 13:32:45 -08:00
Denton Gentry
27da7fd5cb VERSION.txt: This is 1.21.
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-01-12 17:38:49 -08:00
Brad Fitzpatrick
a7da236d3d tailcfg: no-op bump of MapRequest.Version
So 1.18 and 1.20 don't have the same.

Change-Id: Ib2cac7c11eb37d9a0c2fcb66630f1cae619a97f4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-12 15:20:52 -08:00
Brad Fitzpatrick
a93937abc3 wgengine/netstack: make userspace ping work when tailscaled has CAP_NET_RAW
Updates #3710

Change-Id: Ief56c7ac20f5f09a2f940a1906b9efbf1b0d6932
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-12 14:23:39 -08:00
Maisem Ali
26d4ccb816 scripts/installer.sh: add support to use the unstable track.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-01-12 13:41:11 -08:00
Maisem Ali
9e8a432146 cmd/tailscale/cli/web: fix typo where the html template data was being
replaced instead of being appended to.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-01-12 12:41:37 -08:00
Brad Fitzpatrick
24a04d07d1 net/dns/resolver: handle tabs as whitespace when ExitDNS parses resolv.conf
On Synology, the /etc/resolv.conf has tabs in it, which this
resolv.conf parser (we have two, sigh) didn't handle.

Updates #3710

Change-Id: I86f8e09ad1867ee32fa211e85c382a27191418ea
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-12 12:38:48 -08:00
Brad Fitzpatrick
51bc9a6d9d net/netns: remove a useless probe of the "ip" command
We stopped using it in 1.18.

Change-Id: If5adf1d99275286a89e2a05f0bce5193d9f6e5e3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-11 17:47:06 -08:00
Brad Fitzpatrick
e6626366a2 cmd/tailscale: let 'tailscale up --reset' do a pref edit
The --reset shouldn't imply that a Backend.Start is necessary.  With
this, it can do a Backend.EditPrefs instead, which then doesn't do all
the heavy work that Start does. Also, Start on Windows behaves
slightly differently than Linux etc in some cases because of tailscaled
running in client mode on Windows (where the GUI supplies the prefs).

Fixes #3702

Change-Id: I75c9f08d5e0052bf623074030a3a7fcaa677abf6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-11 12:59:36 -08:00
Brad Fitzpatrick
8df3fa4638 net/dns: make WSL network configuration opt-in for now
Tailscale seems to be breaking WSL configurations lately.  Until we
understand what changed, turn off Tailscale's involvement by default
and make it opt-in.

Updates #2815

Change-Id: I9977801f8debec7d489d97761f74000a4a33f71b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-11 10:10:21 -08:00
Brad Fitzpatrick
66f6efa8cb scripts: install gnupg deb, pass --yes to apt-get
Fixes #3685
Fixes #3528
Fixes #3649

Change-Id: Ie029fca6cf3d07a77d228e9591ec1c1c828e68af
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-11 09:44:59 -08:00
Aaron Bieber
189f359609 net/dns: teach OpenBSD's manager to talk to resolvd(8). (#2789)
OpenBSD 6.9 and up has a daemon which handles nameserver configuration. This PR
teaches the OpenBSD dns manager to check if resolvd is being used. If it is, it
will use the route(8) command to tell resolvd to add the Tailscale dns entries
to resolv.conf

Signed-off-by: Aaron Bieber <aaron@bolddaemon.com>
2022-01-11 08:45:50 -08:00
Brad Fitzpatrick
b8ad90c2bf cmd/derper: in manual cert mode, don't discard error from VerifyHostname
Updates #3701

Change-Id: If8ca5104bd8221c99cc390ca49ee3401aff09b62
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-11 08:40:42 -08:00
dependabot[bot]
b1b0fd119b .github: Bump actions/setup-go from 2.1.4 to 2.1.5
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.1.4 to 2.1.5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2.1.4...v2.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 16:39:26 -08:00
Josh Bleecher Snyder
1dc1c8b709 .github/workflows: upgrade to setup-go@v2
The rest of our workflows use v2.1.4.
For reasons I do not understand, we must set GOPATH here.
Maybe the GitHub Action builds come with GOPATH already set?

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-01-10 16:19:06 -08:00
Josh Bleecher Snyder
408522ddad go.toolchain.rev: add update script
Also go.toolchain.branch.

Both migrated from the corp repo.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2022-01-10 14:55:04 -08:00
Brad Fitzpatrick
1ffc21ad71 go.toolchain.rev: update to Go 1.17.6
Co-authored-by: Josh Bleecher Snyder <josh@tailscale.com>
Change-Id: I572b2b36aa8acb53ea7fde638aa89a7510c84915
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-10 13:22:18 -08:00
dependabot[bot]
dee0833b27 .github: Bump actions/upload-artifact from 2.2.4 to 2.3.1
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2.2.4 to 2.3.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2.2.4...v2.3.1)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 12:58:41 -08:00
Brad Fitzpatrick
b03170b901 ipn/ipnserver: provide means of setting debug env vars on Windows
Fixes #3688

Change-Id: I2f88ca32bf764e2b084fe544d821c43b6a20ffa5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-10 10:48:18 -08:00
Brad Fitzpatrick
c5243562d7 version: bump date
Change-Id: Ib314eccff199fc0854553216762737ce6af19586
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-07 12:40:23 -08:00
Brad Fitzpatrick
1a4e8da084 wgengine/netstack: fake pings through netstack on Android too
Every OS ping binary is slightly different. Adjust for Android's.

Updates #1738

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-07 10:05:32 -08:00
Maisem Ali
138662e248 Dockerfile.base: update to alpine:3.15
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-01-07 07:33:25 -08:00
Brad Fitzpatrick
1b426cc232 wgengine/netstack: add env knob to turn on netstack debug logs
Except for the super verbose packet-level dumps. Keep those disabled
by default with a const.

Updates #2642

Change-Id: Ia9eae1677e8b3fe6f457a59e44896a335d95d547
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-06 16:59:35 -08:00
Brad Fitzpatrick
8d0ed1c9ba net/dns/resolver: on Android, make ExitDNS use net package for DNS
Like Windows.

Updates #1738

Change-Id: I9f26bc58ce7e760c749786fbe5d9952c99eeb91c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-06 16:17:24 -08:00
Brad Fitzpatrick
e68d87eb44 ipn: rename SetRunExitNode to SetAdvertiseExitNode
From Maisem's code review feedback where he mashed the merge
button by mistake.

Change-Id: I55abce036a6c25dc391250514983125dda10126c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-06 16:17:03 -08:00
Brad Fitzpatrick
2cfc96aa90 ipn: add methods on Prefs to get/set exit node being advertised
This code was copied in a few places (Windows, Android), so unify it
and add tests.

Change-Id: Id0510c0f5974761365a2045279d1fb498feca11e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-06 16:14:14 -08:00
Brad Fitzpatrick
addda5b96f wgengine/magicsock: fix watchdog timeout on Close when IPv6 not available
The blockForeverConn was only using its sync.Cond one side. Looks like it
was just forgotten.

Fixes #3671

Change-Id: I4ed0191982cdd0bfd451f133139428a4fa48238c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-06 13:24:59 -08:00
Brad Fitzpatrick
64c2657448 cmd/printdep: add flag to print out Go toolchain tarball URL
Updates #3669

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-06 08:44:17 -08:00
Brad Fitzpatrick
3690bfecb0 ipn/ipnlocal: fix cert fetching on macOS GUI platforms
And clarify the directory they get written to when under the sandbox.

Fixes #3667

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-06 07:59:20 -08:00
Brad Fitzpatrick
28bf53f502 wgengine/magicsock: reduce disco ping heartbeat aggressiveness a bit
Bigger changes coming later, but this should improve things a bit in
the meantime.

Rationale:

* 2 minutes -> 45 seconds: 2 minutes was overkill and never considered
  phones/battery at the time. It was totally arbitrary. 45 seconds is
  also arbitrary but is less than 2 minutes.

* heartbeat from 2 seconds to 3 seconds: in practice this meant two
  packets per second (2 pings and 2 pongs every 2 seconds) because the
  other side was also pinging us every 2 seconds on their own.
  That's just overkill. (see #540 too)

So in the worst case before: when we sent a single packet (say: a DNS
packet), we ended up sending 61 packets over 2 minutes: the 1 DNS
query and then then 60 disco pings (2 minutes / 2 seconds) & received
the same (1 DNS response + 60 pongs).  Now it's 15. In 1.22 we plan to
remove this whole timer-based heartbeat mechanism entirely.

The 5 seconds to 6.5 seconds change is just stretching out that
interval so you can still miss two heartbeats (other 3 + 3 seconds
would be greater than 5 seconds). This means that if your peer moves
without telling you, you can have a path out for 6.5 seconds
now instead of 5 seconds before disco finds a new one. That will also
improve in 1.22 when we start doing UDP+DERP at the same time
when confidence starts to go down on a UDP path.

Updates #3363

Change-Id: Ic2314bbdaf42edcdd7103014b775db9cf4facb47
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-05 14:05:16 -08:00
Brad Fitzpatrick
c8b63a409e cmd/hello: also redirect https://hello.ipn.dev to hello.ts.net
I apparently only did HTTP before, not HTTPS.

Updates tailscale/corp#1327

Change-Id: I7d5265a0a25fcab5b142c8c3f21a0920f6cae39f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-04 19:45:02 -08:00
Brad Fitzpatrick
a201b89e4a wgengine/magicsock: reconnect to DERP when its definition changes
Change-Id: I7c560feb9e4a6e155a35ec764a68354f19f694e4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-04 15:19:21 -08:00
Brad Fitzpatrick
506c727e30 ipnlocal, net/{dns,tsaddr,tstun}, wgengine: support MagicDNS on IPv6
Fixes #3660

RELNOTE=MagicDNS now works over IPv6 when CGNAT IPv4 is disabled.

Change-Id: I001e983df5feeb65289abe5012dedd177b841b45
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-04 14:37:22 -08:00
Brad Fitzpatrick
e2d9c99e5b cmd/hello: migrate to hello.ts.net as the hostname
But still support hello.ipn.dev for a bit.

Updates tailscale/corp#1327

Change-Id: Iab59cca0b260d69858af16f4e42677e54f9fe54a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-04 09:50:47 -08:00
Maisem Ali
01a9906bf8 tool/go: add wrapper to download and use go.toolchain.rev go version.
Also update build_dist.sh and build_docker.sh to use the wrapper.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-01-04 09:45:29 -08:00
Brad Fitzpatrick
2aeb93003f derp: add metrics to server got pings, sent pongs
Updates #3652

Change-Id: I1d350bcaee39ea36b0c71912028624d18fb541b4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-03 14:04:20 -08:00
Brad Fitzpatrick
2513d2d728 net/{neterror,dns/resolver}: move PacketWasTruncated to neterror from DNS code
And delete the unused code in net/dns/resolver/neterr_*.go.

Change-Id: Ibe62c486bacce2733eb9968c96a98cbbdb2758bd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-03 14:03:30 -08:00
Maisem Ali
dd45bba76b tsnet: add Start method to allow connecting to the tailnet without
requiring a call to Dial/Listen.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-01-03 11:56:54 -08:00
Brad Fitzpatrick
ebdd25920e go.toolchain.rev: add Go toolchain rev, tool to print it out
Updates tailscale/corp#3385

Change-Id: Ia0e285a0ae836744539c97ff6eff207588159688
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-03 11:56:27 -08:00
Maisem Ali
431329e47c build_docker.sh: add env overrides
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-01-03 10:59:55 -08:00
Brad Fitzpatrick
7d9b1de3aa netcheck,portmapper,magicsock: ignore some UDP write errors on Linux
Treat UDP send EPERM errors as a lost UDP packet, not something super
fatal. That's just the Linux firewall preventing it from going out.

And add a leaf package net/neterror for that (and future) policy that
all three packages can share, with tests.

Updates #3619

Change-Id: Ibdb838c43ee9efe70f4f25f7fc7fdf4607ba9c1d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-31 08:27:21 -08:00
Brad Fitzpatrick
2c94e3c4ad wgengine/magicsock: don't unconditionally close DERP connections on rebind
Only if the source address isn't on the currently active interface or
a ping of the DERP server fails.

Updates #3619

Change-Id: I6bf06503cff4d781f518b437c8744ac29577acc8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-29 13:21:05 -08:00
Brad Fitzpatrick
04c2c5bd80 net/interfaces: define DefaultRouteInterface and State.DefaultRouteInterface
It was pretty ill-defined before and mostly for logging. But I wanted
to start depending on it, so define what it is and make Windows match
the other operating systems, without losing the log output we had
before. (and add tests for that)

Change-Id: I0fbbba1cfc67a265d09dd6cb738b73f0f6005247
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-29 12:13:15 -08:00
Brad Fitzpatrick
96cab21383 cmd/tailscale: add debug restun, rebind subcommands
In the hidden debug menu.

Change-Id: I20213f1f4e2290d36f9ff561bac0cc767400d5fd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-28 21:26:45 -08:00
Brad Fitzpatrick
63d9c7b9b3 derp: add Client.LocalAddr method
So magicsock can later ask a DERP connection whether its source IP
would've changed if it reconnected.

Updates #3619

Change-Id: Ibc8810340c511d6786b60c78c1a61c09f5800e40
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-28 15:13:53 -08:00
Brad Fitzpatrick
b09000ad5d ipn/localapi: add debug handler to Rebind, ReSTUN magicsock
And more later probably.

Updates #3619

Change-Id: Ia4cba34a7c0dcce4d2eddec8aae17f32b51c207f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-28 15:13:44 -08:00
Brad Fitzpatrick
eb26c081b1 net/dns: bound time we wait on restarting systemd-resolved in another place
Fixes #3629
Updates #3537

Change-Id: I5d4b6acfcfdf0e3efcf6eb49c5e6cf4521e4baed
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-28 11:09:07 -08:00
Maisem Ali
44937b59e7 tsnet: add Dial method to allow dialing out to the tailnet.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-12-28 10:19:36 -08:00
Brad Fitzpatrick
535b925d1b derp/derphttp: add Client.Ping, SendPing methods
Continuing work in 434af15a04, to make it possible for magicsock to
probe whether a DERP server is still there.

Updates #3619

Change-Id: I366a77c27e93b876734e64f445b85ef01eb590f2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-28 09:52:10 -08:00
Brad Fitzpatrick
434af15a04 derp: support client->server ping (and server->client pong)
In prep for a future change to have client ping derp connections
when their state is questionable, rather than aggressively tearing
them down and doing a heavy reconnect when their state is unknown.

We already support ping/pong in the other direction (servers probing
clients) so we already had the two frame types, but I'd never finished
this direction.

Updates #3619

Change-Id: I024b815d9db1bc57c20f82f80f95fb55fc9e2fcc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-27 14:49:50 -08:00
Brad Fitzpatrick
bc537adb1a tailcfg: add Hostinfo.HowUnequal method
Change-Id: I80ee49c2ab581feccc4aa6ab47bc3c8392d9989d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-23 09:04:53 -08:00
Brad Fitzpatrick
0aa4c6f147 net/dns/resolver: add debug HTML handler to see what DNS traffic was forwarded
Change-Id: I6b790e92dcc608515ac8b178f2271adc9fd98f78
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-21 14:32:36 -08:00
Brad Fitzpatrick
ae319b4636 wgengine/magicsock: add HTML debug handler to see magicsock state
Change-Id: Ibc46f4e9651e1c86ec6f5d139f5e9bdc7a488415
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-21 14:26:52 -08:00
Brad Fitzpatrick
c7f5bc0f69 wgengine/magicsock: add metrics for sent disco messages
We only tracked the transport type (UDP vs DERP), not what they were.

Change-Id: Ia4430c1c53afd4634e2d9893d96751a885d77955
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-20 09:39:38 -08:00
Brad Fitzpatrick
81bc812402 Dockerfile: remove long-obsolete -tags=xversion
That build tag hasn't been used since 5088af68cf
(June 2nd, 2021, for 1.10.0)

Change-Id: Ib6093a975505339872a0dc663fff9fc09c13bbc0
2021-12-19 18:15:09 -08:00
Brad Fitzpatrick
0848b36dd2 net/dns/resolver: add metrics to PTR lookup misses
Updates tailscale/corp#3326

Change-Id: I58077d889a3b58ef0633267c92ffb265686ce152
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-19 09:50:16 -08:00
Brad Fitzpatrick
39f22a357d net/dns/resolver: send NXDOMAIN to iOS DNS-SD/Bonjour queries
Don't just ignore them. See if this makes them calm down.

Updates #3363

Change-Id: Id1d66308e26660d26719b2538b577522a1e36b63
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-18 19:24:19 -08:00
Brad Fitzpatrick
394c9de02b net/dns/resolver: add nameFromQuery benchmark
To convince me it's not as alloc-y as it looks.

Change-Id: I503a0cc267268a23d2973dfde9833c420be4e868
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-18 19:03:45 -08:00
Brad Fitzpatrick
c7052154d5 net/dns/resolver: fix the subject in a func comment
Change-Id: I519268c20dbd2c2da92da565839d3c1c84612dcc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-18 15:11:01 -08:00
Brad Fitzpatrick
3dedcd1640 logpolicy, ipn/ipnserver: connect to logtail via tailscaled when needed
This is for use by the Windows GUI client to log via when an
exit node is in use, so the logs don't go out via the exit node and
instead go directly, like tailscaled's. The dialer tried to do that
in the unprivileged GUI by binding to a specific interface, but the
"Internet Kill Switch" installed by tailscaled for exit nodes
precludes that from working and instead the GUI fails to dial out.
So, go through tailscaled (with a CONNECT request) instead.

Fixes tailscale/corp#3169

Change-Id: I17a8efdc1d4b8fed53a29d1c19995592b651b215
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-17 14:40:26 -08:00
Brad Fitzpatrick
5a9914a92f wgengine/netstack: don't remove 255.255.255.255/32 from netstack
The intent of the updateIPs code is to add & remove IP addresses
to netstack based on what we get from the netmap.

But netstack itself adds 255.255.255.255/32 apparently and we always
fight it (and it adds it back?). So stop fighting it.

Updates #2642 (maybe fixes? maybe.)

Change-Id: I37cb23f8e3f07a42a1a55a585689ca51c2be7c60
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-16 14:15:07 -08:00
Joe Tsai
66164b9307 api: document API endpoint to manipulate keys (#3544)
The new /keys endpoint allows you to list API and machine auth keys.
You can also create machine auth key.
It currently does not support creating another API key.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2021-12-16 13:50:14 -08:00
Brad Fitzpatrick
40e2b312b6 ipn/ipnserver, logpolicy: move Windows disk logging up earlier
This moves the Windows-only initialization of the filelogger into
logpolicy. Previously we only did it when babysitting the tailscaled
subprocess, but this meant that log messages from the service itself
never made it to disk. Examples that weren't logged to disk:

* logtail unable to dial out,
* DNS flush messages from the service
* svc.ChangeRequest messages (#3581)

This is basically the same fix as #3571 but staying in the Logf type,
and avoiding build-tagged file (which wasn't quite a goal, but
happened and seemed nice)

Fixes #3570

Co-authored-by: Aaron Klotz <aaron@tailscale.com>
Change-Id: Iacd80c4720b7218365ec80ae143339d030842702
2021-12-16 12:33:04 -08:00
Brad Fitzpatrick
689426d6bc cmd/tailscaled: log Windows service change requests
And add a little comment.

Change-Id: If0bedf8aefd8d528149548fba829e7a9a8b2e114
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-16 12:24:05 -08:00
Josh Bleecher Snyder
add6dc8ccc ipn/ipnlocal: make TestShrinkDefaultRoute hermetic
Make shrinkDefaultRoute a pure function.
Instead of calling interfaceRoutes, accept that information as parameters.
Hard-code those parameters in TestShrinkDefaultRoute.

Fixes #3580

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-16 11:31:20 -08:00
Jay Stapleton
894693f352 scripts/installer.sh fix APT_SYSTEMCTL_START error
fix error: 'sh: 411: APT_SYSTEMCTL_START: parameter not set' on certain debian distributions

Signed-off-by: Jay Stapleton <jay@tailscale.com>
2021-12-16 13:55:11 -05:00
Josh Bleecher Snyder
4512e213d5 cmd/tailscale: improve ping error message when logged out
Refactor out the pretty status printing code from status, use it in ping.

Fixes #3549

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-16 10:46:18 -08:00
Brad Fitzpatrick
8f43ddf1a2 ipn/ipnlocal, health: populate self node's Online bit in tailscale status
One option was to just hide "offline" in the text output, but that
doesn't fix the JSON output.

The next option was to lie and say it's online in the JSON (which then
fixes the "offline" in the text output).

But instead, this sets the self node's "Online" to whether we're in an
active map poll.

Fixes #3564

Change-Id: I9b379989bd14655198959e37eec39bb570fb814a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-16 10:14:08 -08:00
Josh Bleecher Snyder
681d4897cc tstest/integration: remove t param in testNode methods
testNodes have a reference to a testing.TB via their env.
Use it instead of making the caller pass theirs.
We did this in some methods but not others; finish the job.
This simplifies the call sites.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-15 18:06:14 -08:00
Josh Bleecher Snyder
93ae11105d ipn/ipnlocal: clear magicsock's netmap on logout
magicsock was hanging onto its netmap on logout,
which caused tailscale status to display partial
information about a bunch of zombie peers.
After logout, there should be no peers.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-15 17:00:08 -08:00
Josh Bleecher Snyder
84a1106fa7 tstest/integration: make -verbose-tailscaled pass -verbose=2 to tailscaled
If you're using -verbose-tailscaled, you're doing in-the-weeds debugging,
so you probably want the verbose output.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-15 17:00:08 -08:00
Brad Fitzpatrick
aac974a5e5 ipn/ipnlocal: deflake (mostly) TestStateMachine
I'm sick of this flaking. Even if this isn't the right fix, it
stops the alert fatigue.

Updates #3020

Change-Id: I4001c127d78f1056302f7741adec34210a72ee61
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-15 13:41:06 -08:00
Brad Fitzpatrick
6590fc3a94 wgengine/netstack: remove some logging on forwarding connections
Change-Id: Ib1165b918cd5da38583f8e7d4be8cda54af3c81d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-15 11:38:25 -08:00
Brad Fitzpatrick
486059589b all: gofmt -w -s (simplify) tests
And it updates the build tag style on a couple files.

Change-Id: I84478d822c8de3f84b56fa1176c99d2ea5083237
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-15 08:43:41 -08:00
Brad Fitzpatrick
59f4f33f60 cmd/tailscaled: fix windows logtail integration
I broke it in 1.17.x sometime while rewiring some logs stuff,
mostly in 0653efb092 (but with a handful
of logs-related changes around that time)

Fixes tailscale/corp#3265

Change-Id: Icb5c07412dc6d55f1d9244c5d0b51dceca6a7e34
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-14 15:03:37 -08:00
Maya Kaczorowski
ac8e69b713 Merge pull request #3330 from tailscale/mayakacz-patch-2
README.md: update platforms
2021-12-13 17:25:02 -08:00
Maya Kaczorowski
0f3b55c299 README.md: update platforms
Update platform support. This matches content in https://tailscale.com/kb/1062/reviewer-guide/#which-platforms-does-it-run-on

Signed-off-by: Maya Kaczorowski <15946341+mayakacz@users.noreply.github.com>
2021-12-13 17:11:07 -08:00
Josh Bleecher Snyder
4691e012a9 tstest/integration: build binaries only once
The existing code relied on the Go build cache to avoid
needless work when obtaining the tailscale binaries.

For non-obvious reasons, the binaries were getting re-linked
every time, which added 600ms or so on my machine to every test.

Instead, build the binaries exactly once, on demand.
This reduces the time to run 'go test -count=5' from 34s to 10s
on my machine.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-13 14:38:08 -08:00
Denton Gentry
e133bb570b install.sh: add linuxmint, kali, several more.
After apt install, Kali Linux had not enabled nor started
the tailscaled systemd service. Add a quirks mode to enable
and start it after apt install for debian platforms.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-12-13 11:38:29 -08:00
Brad Fitzpatrick
adc97e9c4d cmd/tailscale: make --accept-routes default true on Windows, macOS GUI
One of the most annoying parts of using the Tailscale CLI on Windows
and the macOS GUI is that Tailscale's GUIs default to running with
"Route All" (accept all non-exitnode subnet routes) but the CLI--being
originally for Linux--uses the Linux default, which is to not accept
subnets.

Which means if a Windows user does, e.g.:

    tailscale up --advertise-exit-node
Or:
    tailscale up --shields-up

... then it'd warn about reverting the --accept-routes option, which the user
never explicitly used.

Instead, make the CLI's default match the platform/GUI's default.

Change-Id: I15c804b3d9b0266e9ca8651e0c09da0f96c9ef8d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-13 10:33:20 -08:00
Maisem Ali
d24a8f7b5a wgengine/router{windows}: return the output from the firewallTweaker
on error.

While debugging a customer issue where the firewallTweaker was failing
the only message we have is `router: firewall: error adding
Tailscale-Process rule: exit status 1` which is not really helpful.
This will help diagnose firewall tweaking failures.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-12-13 10:07:32 -08:00
David Crawshaw
8dbda1a722 scripts/installer.sh: press Y on RHEL
For #3540

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2021-12-13 09:48:41 -08:00
Brad Fitzpatrick
cced414c7d net/dns/resolver: add Windows ExitDNS service support, using net package
Updates #1713
Updates #835

Change-Id: Ia71e96d0632c2d617b401695ad68301b07c1c2ec
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-10 20:47:17 -08:00
Brad Fitzpatrick
cab5c46481 net/dns: bound how long we block looking for, restarting systemd-resolved
Fixes #3537

Change-Id: Iba6a3cde75983490d4072b5341f48dbfa2f997c0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-10 09:58:14 -08:00
Josh Bleecher Snyder
63cd581c3f safesocket: add ConnectionStrategy, provide control over fallbacks
fee2d9fad added support for cmd/tailscale to connect to IPNExtension.
It came in two parts: If no socket was provided, dial IPNExtension first,
and also, if dialing the socket failed, fall back to IPNExtension.

The second half of that support caused the integration tests to fail
when run on a machine that was also running IPNExtension.
The integration tests want to wait until the tailscaled instances
that they spun up are listening. They do that by dialing the new
instance. But when that dial failed, it was falling back to IPNExtension,
so it appeared (incorrectly) that tailscaled was running.
Hilarity predictably ensued.

If a user (or a test) explicitly provides a socket to dial,
it is a reasonable assumption that they have a specific tailscaled
in mind and don't want to fall back to IPNExtension.
It is certainly true of the integration tests.

Instead of adding a bool to Connect, split out the notion of a
connection strategy. For now, the implementation remains the same,
but with the details hidden a bit. Later, we can improve that.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-09 15:46:38 -08:00
Josh Bleecher Snyder
a5235e165c tstest/integration: fix running with -verbose-tailscale
Without this fix, any run with -verbose-tailscale fails.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-09 15:46:38 -08:00
Josh Bleecher Snyder
c8829b742b all: minor code cleanup
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-09 15:46:38 -08:00
Brad Fitzpatrick
39ffa16853 net/dnscache, net/tsdial: add DNS caching to tsdial UserDial
This is enough to handle the DNS queries as generated by Go's
net package (which our HTTP/SOCKS client uses), and the responses
generated by the ExitDNS DoH server.

This isn't yet suitable for putting on 100.100.100.100 where a number
of different DNS clients would hit it, as this doesn't yet do
EDNS0. It might work, but it's untested and likely incomplete.

Likewise, this doesn't handle anything about truncation, as the
exchanges are entirely in memory between Go or DoH. That would also
need to be handled later, if/when it's hooked up to 100.100.100.100.

Updates #3507

Change-Id: I1736b0ad31eea85ea853b310c52c5e6bf65c6e2a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-09 11:34:21 -08:00
Brad Fitzpatrick
b59e7669c1 wgengine/netstack: in netstack/hybrid mode, fake ICMP using ping command
Change-Id: I42cb4b9b326337f4090d9cea532230e36944b6cb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-09 09:30:10 -08:00
Brad Fitzpatrick
21741e111b net/packet: add ICMP6Header, like ICMP4Header
So we can generate IPv6 ping replies.

Change-Id: I79a9a38d8aa242e5dfca4cd15dfaffaea6cb1aee
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-09 09:30:10 -08:00
Brad Fitzpatrick
7b9c7bc42b ipn/ipnstate: remove old deprecated TailAddr IPv4-only field
It's been a bunch of releases now since the TailscaleIPs slice
replacement was added.

Change-Id: I3bd80e1466b3d9e4a4ac5bedba8b4d3d3e430a03
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-09 09:28:23 -08:00
Brad Fitzpatrick
affc4530a2 net/packet: don't make IP6Header.marshalPseudo assume UDP
It will be used for ICMPv6 next, so pass in the proto.

Also, use the ipproto constants rather than hardcoding the mysterious
number.

Change-Id: I57b68bdd2d39fff75f82affe955aff9245de246b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-08 21:15:46 -08:00
Brad Fitzpatrick
485bcdc951 net/packet: fix doc copy/paste-o
Change-Id: I0aca490b3ccb0c124192afb362a10b19a15a3e2b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-08 21:12:43 -08:00
Denton Gentry
878a20df29 net/dns: add GetBaseConfig to CallbackRouter.
Allow users of CallbackRouter to supply a GetBaseConfig
implementation. This is expected to be used on Android,
which currently lacks both a) platform support for
Split-DNS and b) a way to retrieve the current DNS
servers.

iOS/macOS also use the CallbackRouter but have platform
support for SplitDNS, so don't need getBaseConfig.

Updates https://github.com/tailscale/tailscale/issues/2116
Updates https://github.com/tailscale/tailscale/issues/988

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-12-08 16:49:11 -08:00
Brad Fitzpatrick
a28d280b95 cmd/tailscaled: move start-up failure logging to one place
The caller of func run said:

    // No need to log; the func already did

But that wasn't true. Some return paths didn't log.

So instead, return rich errors and have func main do the logging,
so we can't miss anything in the future.

Prior to this, safesocket.Listen for instance was causing tailscaled
to os.Exit(1) on failure without any clue as to why.

Change-Id: I9d71cc4d73d0fed4aa1b1902cae199f584f25793
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-08 15:13:39 -08:00
David Anderson
9f867ad2c5 .github/dependabot.yml: disable eager updates for Go.
Given our development cycle, we'll instead do big-bang updates
after every release, to give time for all the updates to soak in
unstable.

This does _not_ disable dependabot security-critical PRs.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-12-08 10:37:03 -08:00
Brad Fitzpatrick
c0701b130d ipn/ipnstate, cmd/tailscale: add Online bool to tailscale status & --json
Fixes #3533

Change-Id: I2f6f0d712cf3f987fba1c15be74cdb5c8d565f04
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-08 09:34:15 -08:00
Arnaud Dezandee
656809e4ee cmd/derper: allow http port configuration
Signed-off-by: Arnaud Dezandee <dezandee.arnaud@gmail.com>
2021-12-08 08:58:30 -08:00
root
e34ba3223c version/distro: report TrueNAS Scale as "truenas"
TrueNAS Scale is based on Debian Linux

Signed-off-by: Todd Neal <todd@tneal.org>
2021-12-07 21:04:58 -08:00
Todd Neal
c18dc57861 ipn/{ipnserver,ipnlocal}: support incoming Taildrop on TrueNAS
Signed-off-by: Todd Neal <todd@tneal.org>
2021-12-07 21:04:58 -08:00
Denton Gentry
ffb16cdffb cmd/tailscale: add --json for up
Print JSON to stdout containing everything needed for
authentication.

{
  "AuthURL": "https://login.tailscale.com/a/0123456789",
  "QR": "data:image/png;base64,iV...QmCC",
  "BackendState": "NeedsLogin"
}
{
  "BackendState": "Running"
}

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-12-06 21:17:15 -08:00
Brad Fitzpatrick
d3d503d997 ipn/ipnlocal: add HTTP/2 h2c server support to peerapi on non-mobile platforms
To make ExitDNS cheaper.

Might not finish client-side support in December before 1.20, but at
least server support can start rolling out ahead of clients being
ready for it.

Tested with curl against peerapi.

Updates #1713

Change-Id: I676fed5fb1aef67e78c542a3bc93bddd04dd11fe
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-06 16:39:14 -08:00
Brad Fitzpatrick
abc00e9c8d ipn/{ipnserver,ipnlocal}: support incoming Taildrop on Synology
If the user has a "Taildrop" shared folder on startup and
the "tailscale" system user has read/write access to it,
then the user can "tailscale file cp" to their NAS.

Updates #2179 (would be fixes, but not super ideal/easy yet)

Change-Id: I68e59a99064b302abeb6d8cc84f7d2a09f764990
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-06 16:36:24 -08:00
David Anderson
190b7a4cca go.mod: mass update with go get -u.
Gets ahead of dependabot slightly, but the updates are minor.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-12-06 13:00:37 -08:00
dependabot[bot]
0d8ef1ff35 go.mod: bump github.com/aws/aws-sdk-go-v2/service/ssm
Bumps [github.com/aws/aws-sdk-go-v2/service/ssm](https://github.com/aws/aws-sdk-go-v2) from 1.17.0 to 1.17.1.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.17.0...service/ssm/v1.17.1)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/ssm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 11:27:21 -08:00
dependabot[bot]
329751c48e go.mod: bump golang.org/x/tools from 0.1.7 to 0.1.8
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.1.7 to 0.1.8.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.1.7...v0.1.8)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 11:20:43 -08:00
dependabot[bot]
9ddef8cdbf go.mod: bump github.com/mdlayher/netlink from 1.4.1 to 1.4.2
Bumps [github.com/mdlayher/netlink](https://github.com/mdlayher/netlink) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/mdlayher/netlink/releases)
- [Changelog](https://github.com/mdlayher/netlink/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mdlayher/netlink/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: github.com/mdlayher/netlink
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 11:05:15 -08:00
dependabot[bot]
9140f193bc go.mod: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager
Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.7.3 to 1.7.4.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/fsx/v1.7.3...feature/s3/manager/v1.7.4)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 11:02:21 -08:00
dependabot[bot]
05c1be3e47 .github: Bump actions/upload-artifact from 1 to 2.2.4
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 1 to 2.2.4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v1...v2.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 11:01:40 -08:00
David Anderson
e6e63c2305 .github/dependabot.yml: make dependabot send all the updates right now.
So we can mass-process updates once now, then turn it off.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-12-06 10:57:33 -08:00
Brad Fitzpatrick
c0984f88dc Makefile: add spk and pushspk targets for iterative Synology development
Change-Id: I97319d14917aa2b00ff72a7b73b3db79ea8392b7
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-06 09:52:06 -08:00
Todd Neal
eeccbccd08 support running in a FreeBSD jail
Since devd apparently can't be made to work in a FreeBSD jail
fall back to polling.

Fixes tailscale#2858

Signed-off-by: Todd Neal <todd@tneal.org>
2021-12-05 21:42:52 -08:00
Brad Fitzpatrick
69de3bf7bf wgengine/filter: let unknown IPProto match if IP okay & match allows all ports
RELNOTE=yes

Change-Id: I96eaf3cf550cee7bb6cdb4ad81fc761e280a1b2a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-05 10:44:18 -08:00
Josh Bleecher Snyder
1813c2a162 .: add .gitattributes entry to use Go hunk-header driver
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-03 17:56:02 -08:00
Maisem Ali
0a9932f3b2 build_docker.sh: prefix version strings with v
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-12-03 13:55:09 -08:00
Brad Fitzpatrick
9c5c9d0a50 ipn/ipnlocal, net/tsdial: make SOCKS/HTTP dials use ExitDNS
And simplify, unexport some tsdial/netstack stuff in the the process.

Fixes #3475

Change-Id: I186a5a5cbd8958e25c075b4676f7f6e70f3ff76e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-03 13:39:37 -08:00
Brad Fitzpatrick
9f6249b26d ipn/policy: treat DNS service as interesting so it's not filtered out
The control plane is currently still eating it.

Updates #1713

Change-Id: I66a0698599d6794ab1302f9585bf29e38553c884
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-03 12:55:54 -08:00
Josh Bleecher Snyder
de635ac0a8 cmd/tailscale: print more detail in connection failure error message
Before:

failed to connect to local tailscaled (which appears to be running). Got error: Get "http://local-tailscaled.sock/localapi/v0/status": EOF

After:

failed to connect to local tailscaled (which appears to be running as IPNExtension, pid 2118). Got error: Get "http://local-tailscaled.sock/localapi/v0/status": EOF

This was useful just now, as it made it clear that tailscaled I thought
I was connecting to might not in fact be running; there was
a second tailscaled running that made the error message slightly misleading.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-03 12:46:50 -08:00
Brad Fitzpatrick
003089820d cmd/tailscale: fix setting revert checker's finding an exit node
It was using the wrong prefs (intended vs current) to map the current
exit node ID to an IP.

Fixes #3480

Change-Id: I9f117d99a84edddb4cd1cb0df44a2f486abde6c2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-02 21:19:48 -08:00
Brad Fitzpatrick
03a323de4e cmd/tailscale: don't allow setting exit node to known invalid value
Fixes #3081

Change-Id: I34c378dfd51ee013c21962dbe79c49b1ba06c0c5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-02 14:34:33 -08:00
Brad Fitzpatrick
a8f60cf6e8 cmd/tailscale: clarify which prefless flags don't need revert protection
Fixes #3482

Change-Id: Icb51476b0d78d3758cb04df3b565e372b8289a46
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-02 14:07:00 -08:00
Brad Fitzpatrick
f91481075d cmd/tailscale: let --exit-node= take a machine name in addition to IP
If you're online, let tailscale up --exit-node=NAME map NAME to its IP.

We don't store the exit node name server-side in prefs, avoiding
the concern raised earlier.

Fixes #3062

Change-Id: Ieea5ceec1a30befc67e9d6b8a530b3cb047b6b40
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-02 14:02:55 -08:00
Brad Fitzpatrick
adc5997592 net/tsdial: give netstack a Dialer, start refactoring name resolution
This starts to refactor tsdial.Dialer's name resolution to have
different stages: in-memory MagicDNS vs system resolution. A future
change will plug in ExitDNS resolution.

This also plumbs a Dialer into netstack and unexports the dnsMap
internals.

And it removes some of the async AddNetworkMapCallback usage and
replaces it with synchronous updates of the Dialer's netmap
from LocalBackend, since the LocalBackend has the Dialer too.

Updates #3475

Change-Id: Idcb7b1169878c74f0522f5151031ccbc49fe4cb4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-02 11:33:13 -08:00
Josh Bleecher Snyder
768baafcb5 tstest/integration: use t.Cleanup
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-02 11:11:11 -08:00
Brad Fitzpatrick
43983a4a3b ipn/ipnlocal: run peerapi even if Taildrop storage not configured
Change-Id: I77f9ecbe4617d01d13aa1127fa59c83f2aa3e1b8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-01 16:45:09 -08:00
David Anderson
44d0c1ab06 ipn/ipnlocal: resolve exit node IP to ID at EditPrefs time.
Without this, enabling an exit node immediately blackholes all traffic,
but doesn't correctly let it flow to the exit node until the next netmap
update.

Fixes #3447

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-12-01 15:50:32 -08:00
Brad Fitzpatrick
8775c646be net/tsdial: make dialing to peerapi work in netstack mode
With this, I'm able to send a Taildrop file (using "tailscale file cp")
from a Linux machine running --tun=userspace-networking.

Updates #2179

Change-Id: I4e7a4fb0fbda393e4fb483adb06b74054a02cfd0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-01 14:16:34 -08:00
Brad Fitzpatrick
ad3d6e31f0 net/tsdial: move macOS/iOS peerapi sockopt logic from LocalBackend
Change-Id: I812cae027c40c70cdc701427b1a1850cd9bcd60c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-01 12:55:31 -08:00
Josh Bleecher Snyder
25eab78573 control/noise: clean up resources in TestNoReuse
Close the server and client.
Without this, we leak system threads.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-01 12:50:21 -08:00
Brad Fitzpatrick
c7fb26acdb net/tsdial: also plumb TUN name and monitor into tsdial.Dialer
In prep for moving stuff out of LocalBackend.

Change-Id: I9725aa9c3ebc7275f8c40e040b326483c0340127
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-01 10:36:55 -08:00
Brad Fitzpatrick
c37af58ea4 net/tsdial: move more weirdo dialing into new tsdial package, plumb
Not done yet, but this move more of the outbound dial special casing
from random packages into tsdial, which aspires to be the one unified
place for all outbound dialing shenanigans.

Then this plumbs it all around, so everybody is ultimately
holding on to the same dialer.

As of this commit, macOS/iOS using an exit node should be able to
reach to the exit node's DoH DNS proxy over peerapi, doing the sockopt
to stay within the Network Extension.

A number of steps remain, including but limited to:

* move a bunch more random dialing stuff

* make netstack-mode tailscaled be able to use exit node's DNS proxy,
  teaching tsdial's resolver to use it when an exit node is in use.

Updates #1713

Change-Id: I1e8ee378f125421c2b816f47bc2c6d913ddcd2f5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-01 10:36:55 -08:00
Brad Fitzpatrick
bf1d69f25b wgengine/monitor: fix docs on Mon.InterfaceState
The behavior was changed in March (in 7f174e84e6)
but that change forgot to update these docs.

Change-Id: I79c0301692c1d13a4a26641cc5144baf48ec1360
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-12-01 10:36:06 -08:00
Josh Bleecher Snyder
2075c39fd7 net/portmapper: deflake TestPCPIntegration
Logging in goroutines after the test completed
caused data races and panics. Prevent that.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-12-01 10:13:27 -08:00
Artyom Pervukhin
49a9e62d58 Replace AWS SDK v1 dependency with v2
This change drops AWS SDKv1 dependency, leaving only SDK v2 in use.

Closes #3461

Signed-off-by: Artyom Pervukhin <github@artyom.dev>
2021-12-01 07:51:22 -08:00
Danny Hermes
56c72d9cde godoc fix: StatusWithPeers -> StatusWithoutPeers
Signed-off-by: Danny Hermes <daniel.j.hermes@gmail.com>
2021-11-30 23:17:06 -08:00
Brad Fitzpatrick
d5405c66b7 net/tsdial: start of new package to unify all outbound dialing complexity
For now this just deletes the net/socks5/tssocks implementation (and
the DNSMap stuff from wgengine/netstack) and moves it into net/tsdial.

Then initialize a Dialer early in tailscaled, currently only use for the
outbound and SOCKS5 proxies. It will be plumbed more later. Notably, it
needs to get down into the DNS forwarder for exit node DNS forwading
in netstack mode. But it will also absorb all the peerapi setsockopt
and netns Dial and tlsdial complexity too.

Updates #1713

Change-Id: Ibc6d56ae21a22655b2fa1002d8fc3f2b2ae8b6df
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-30 17:21:49 -08:00
Brad Fitzpatrick
3ae6f898cf ipn/ipnlocal, net/dns/resolver: use exit node's DoH proxy when available
Updates #1713

Change-Id: I3695a40ec12d2b4e6dac41cf4559daca6dddd68e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-30 17:01:09 -08:00
Brad Fitzpatrick
16abd7e07c ipn/ipnlocal: fix Content-Length in DoH DNS proxy response
Updates #1713

Change-Id: I912d90383b751ad97b32bcec55e8fedbcf4d3db8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-30 17:01:09 -08:00
Brad Fitzpatrick
2a95ee4680 cmd/tailscale, ipn/ipnstate: note which nodes are exit nodes in status
Fixes #3446

Change-Id: Ib41d588e7fa434c02d134fa449f85b0e15083683
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-30 16:59:23 -08:00
Josh Bleecher Snyder
deb2f5e793 types/logger: add Context and related helpers
We often need both a log function and a context.
We can do this by adding the log function as a context value.
This commit adds helper glue to make that easy.
It is designed to allow incremental adoption.

Updates tailscale/corp#3138

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 15:18:21 -08:00
Aaron Klotz
f93cf6fa03 net/dns: fix checking for wrapped error when attempting to read wsl.conf for Windows WSL2
Fixes https://github.com/tailscale/tailscale/issues/3437

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2021-11-30 15:36:39 -07:00
Josh Bleecher Snyder
b800663779 tstest/integration: stop leaking zstd.Decoders
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 14:08:05 -08:00
David Anderson
124363e0ca net/dns: detect and decode UTF-16 from wsl.exe earlier.
Fixes #3170

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-11-30 13:10:18 -08:00
Josh Bleecher Snyder
e16cb523aa net/nettest: deflake TestPipeTimeout
The block-write and block-read tests are both flaky,
because each assumes it can get a normal read/write
completed within 10ms. This isn’t always true.

We can’t increase the timeouts, because that slows down the test.
However, we don’t need to issue a regular read/write for this test.
The immediately preceding tests already test this code,
using a far more generous timeout.

Remove the extraneous read/write.

This drops the failure rate from 1 per 20,000 to undetectable
on my machine.

While we’re here, fix a typo in a debug print statement.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 12:21:59 -08:00
Josh Bleecher Snyder
a8cc519c70 net/portmapper: improve handling of UPnP parse errors
Without the continue, we might overwrite our current meta
with a zero meta.

Log the error, so that we can check for anything unexpected.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 12:13:15 -08:00
Josh Bleecher Snyder
fddf43f3d1 net/portmapper: fill out PCP/PMP client metrics
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 12:13:15 -08:00
Josh Bleecher Snyder
9787ec6f4a net/portmapper: add UPnP client metrics
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 12:13:15 -08:00
Josh Bleecher Snyder
40f11c50a1 net/portmapper: make PCP/PMP result codes stringers
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 12:13:15 -08:00
Josh Bleecher Snyder
38d90fa330 net/portmapper: add clientmetrics for PCP/PMP responses
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 12:13:15 -08:00
Josh Bleecher Snyder
999814e9e1 net/portmapper: handle pcp ADDRESS_MISMATCH response
These show up a fair amount in our logs.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-30 12:13:15 -08:00
Brad Fitzpatrick
bb91cfeae7 net/socks5/tssocks, wgengine: permit SOCKS through subnet routers/exit nodes
Fixes #1970

Change-Id: Ibef45e8796e1d9625716d72539c96d1dbf7b1f76
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-30 11:54:14 -08:00
Brad Fitzpatrick
3181bbb8e4 cmd/tailscale: make file cp send files via tailscaled localapi
So Taildrop sends work even if the local tailscaled is running in
netstack mode, as it often is on Synology, etc.

Updates #2179 (which is primarily about receiving, but both important)

Change-Id: I9bd1afdc8d25717e0ab6802c7cf2f5e0bd89a3b2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-30 11:47:27 -08:00
David Crawshaw
46a9782322 .github/dependabot.yml: slow down the stream of unusable PRs
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2021-11-30 09:27:14 -08:00
dependabot[bot]
d89c61b812 go.mod: bump github.com/aws/aws-sdk-go-v2/service/ssm
Bumps [github.com/aws/aws-sdk-go-v2/service/ssm](https://github.com/aws/aws-sdk-go-v2) from 1.16.0 to 1.17.0.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.16.0...service/s3/v1.17.0)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/ssm
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-30 08:01:44 -08:00
dependabot[bot]
341e1af873 go.mod: bump github.com/aws/aws-sdk-go-v2/config from 1.10.2 to 1.10.3
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.10.2...config/v1.10.3)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-30 05:48:56 -08:00
Brad Fitzpatrick
b811a316bc tailcfg, ipn/ipnlocal: advertise a Service when exit node DNS proxy available
Updates #1713

Change-Id: I20c8e2ad1062d82ef17363414e372133f4c7181e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-29 21:57:06 -08:00
David Anderson
6e584ffa33 cmd/tailscaled: allow running the SOCKS5 and HTTP proxies on the same port.
Fixes #3248

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-11-29 16:49:48 -08:00
David Anderson
a54d13294f net/proxymux: add a listener mux that can run SOCKS and HTTP on a single socket.
Updates #3248

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-11-29 16:49:48 -08:00
Brad Fitzpatrick
135580a5a8 tailcfg, ipn/ipnlocal, net/dns: forward exit node DNS on Unix to system DNS
Updates #1713

Change-Id: I4c073fec0992d9e01a9a4ce97087d5af0efdc68d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-29 15:25:41 -08:00
Josh Bleecher Snyder
d9c21936c3 control/controlclient: stop logging about goal.url invariant
This isn't the ideal solution, but it's good enough for now.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-11-29 14:00:53 -08:00
David Crawshaw
1e8b4e770a update github.com/aws/aws-sdk-go-v2
Replaces #3464, #3365, #3366 with a PR that includes the depaware fix.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2021-11-29 12:10:28 -08:00
Brad Fitzpatrick
105c545366 cmd/tailscale/cli: don't complain about --accept-routes true->false on Synology
Fixes #3176

Change-Id: I844883e741dccfa5e7771c853180e9f65fb7f7a4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-11-29 11:21:38 -08:00
350 changed files with 19331 additions and 3631 deletions

1
.gitattributes vendored
View File

@@ -1 +1,2 @@
go.mod filter=go-mod
*.go diff=golang

View File

@@ -2,15 +2,20 @@
# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
commit-message:
prefix: "go.mod:"
## Disabled between releases. We reenable it briefly after every
## stable release, pull in all changes, and close it again so that
## the tree remains more stable during development and the upstream
## changes have time to soak before the next release.
# - package-ecosystem: "gomod"
# directory: "/"
# schedule:
# interval: "daily"
# commit-message:
# prefix: "go.mod:"
# open-pull-requests-limit: 100
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
commit-message:
prefix: ".github:"

View File

@@ -19,7 +19,7 @@ jobs:
dry-run: false
language: go
- name: Upload Crash
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v2.3.1
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
id: go
- name: Check out code into the Go module directory
@@ -37,6 +37,12 @@ jobs:
GOARCH: amd64
run: for d in $(go list -f '{{if .TestGoFiles}}{{.Dir}}{{end}}' ./... ); do (echo $d; cd $d && go test -c ); done
- name: iOS build most
env:
GOOS: ios
GOARCH: arm64
run: go install ./ipn/... ./wgengine/ ./types/... ./control/controlclient
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
id: go
- name: Check out code into the Go module directory

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
id: go
- name: Check out code into the Go module directory

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
id: go
- name: Check out code into the Go module directory

View File

@@ -14,9 +14,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
- name: Check out code
uses: actions/checkout@v1

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env sh
#
# This is a temporary hack to work around
# https://github.com/golang/go/issues/51629 , wherein the stringer
# generator doesn't work with generics.
#
# This script is the equivalent of `go generate ./...`, except that it
# only runs generate on packages that don't try to use stringer.
set -e
find . -name '*.go' | xargs grep -l go:generate | xargs -n1 dirname | sort -u | while read dir; do
if ! egrep "cmd/(stringer|cloner)" $dir/*.go; then
set -x
go generate -tags=hermetic $dir
set +x
fi
done

View File

@@ -15,9 +15,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
- name: Check out code
uses: actions/checkout@v2
@@ -25,14 +25,13 @@ jobs:
fetch-depth: 0
- name: check 'go generate' is clean
# The shell script invocation below is a temporary hack for
# https://github.com/tailscale/tailscale/issues/4194. When
# that issue is fixed, replace its invocation with:
# go generate --tags=hermetic ./...
run: |
if [[ "${{github.ref}}" == release-branch/* ]]
then
pkgs=$(go list ./... | grep -v dnsfallback)
else
pkgs=$(go list ./... | grep -v dnsfallback)
fi
go generate $pkgs
set -e
./.github/workflows/go-generate-without-stringer.sh
echo
echo
git diff --name-only --exit-code || (echo "The files above need updating. Please run 'go generate'."; exit 1)

View File

@@ -14,9 +14,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
- name: Check out code
uses: actions/checkout@v1

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
id: go
- name: Check out code into the Go module directory

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
id: go
- name: Check out code into the Go module directory

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
id: go
- name: Check out code into the Go module directory

View File

@@ -14,9 +14,9 @@ jobs:
steps:
- name: Set up Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
go-version: 1.18
- name: Check out code
uses: actions/checkout@v1

View File

@@ -12,11 +12,13 @@ jobs:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Set GOPATH
run: echo "GOPATH=$HOME/go" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v1
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17
id: go
go-version: 1.18
- name: Checkout Code
uses: actions/checkout@v1

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Install Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17.x
go-version: 1.18.x
- name: Checkout code
uses: actions/checkout@v2
@@ -27,10 +27,21 @@ jobs:
- name: Restore Cache
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# Note: unlike some other setups, this is only grabbing the mod download
# cache, rather than the whole mod directory, as the download cache
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/go/pkg/mod/cache
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
# The -race- here ensures that non-race builds and race builds do not
# overwrite each others cache, as while they share some files, they
# differ in most by volume (build cache).
# TODO(raggi): add a go version here.
key: ${{ runner.os }}-go-2-race-${{ hashFiles('**/go.sum') }}
- name: Test with -race flag
# Don't use -bench=. -benchtime=1x.

View File

@@ -17,9 +17,9 @@ jobs:
steps:
- name: Install Go
uses: actions/setup-go@v2.1.4
uses: actions/setup-go@v2.1.5
with:
go-version: 1.17.x
go-version: 1.18.x
- name: Checkout code
uses: actions/checkout@v2
@@ -27,10 +27,18 @@ jobs:
- name: Restore Cache
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# Note: unlike some other setups, this is only grabbing the mod download
# cache, rather than the whole mod directory, as the download cache
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/go/pkg/mod/cache
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
# TODO(raggi): add a go version here.
key: ${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
- name: Test
# Don't use -bench=. -benchtime=1x.

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@
*.dll
*.so
*.dylib
*.spk
cmd/tailscale/tailscale
cmd/tailscaled/tailscaled

View File

@@ -32,7 +32,7 @@
# $ docker exec tailscaled tailscale status
FROM golang:1.17-alpine AS build-env
FROM golang:1.18-alpine AS build-env
WORKDIR /go/src/tailscale
@@ -50,7 +50,7 @@ ARG VERSION_GIT_HASH=""
ENV VERSION_GIT_HASH=$VERSION_GIT_HASH
ARG TARGETARCH
RUN GOARCH=$TARGETARCH go install -tags=xversion -ldflags="\
RUN GOARCH=$TARGETARCH go install -ldflags="\
-X tailscale.com/version.Long=$VERSION_LONG \
-X tailscale.com/version.Short=$VERSION_SHORT \
-X tailscale.com/version.GitCommit=$VERSION_GIT_HASH" \

View File

@@ -2,5 +2,5 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
FROM alpine:3.14
FROM alpine:3.15
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables

View File

@@ -1,27 +1,29 @@
IMAGE_REPO ?= tailscale/tailscale
SYNO_ARCH ?= "amd64"
SYNO_DSM ?= "7"
usage:
echo "See Makefile"
vet:
go vet ./...
./tool/go vet ./...
updatedeps:
go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscaled
go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscale
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscaled
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscale
depaware:
go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscale
./tool/go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
./tool/go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscale
buildwindows:
GOOS=windows GOARCH=amd64 go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
GOOS=windows GOARCH=amd64 ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
build386:
GOOS=linux GOARCH=386 go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
GOOS=linux GOARCH=386 ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
buildlinuxarm:
GOOS=linux GOARCH=arm go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
GOOS=linux GOARCH=arm ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
buildmultiarchimage:
./build_docker.sh
@@ -29,4 +31,16 @@ buildmultiarchimage:
check: staticcheck vet depaware buildwindows build386 buildlinuxarm
staticcheck:
go run honnef.co/go/tools/cmd/staticcheck -- $$(go list ./... | grep -v tempfork)
./tool/go run honnef.co/go/tools/cmd/staticcheck -- $$(./tool/go list ./... | grep -v tempfork)
spk:
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o tailscale.spk --source=. --goarch=${SYNO_ARCH} --dsm-version=${SYNO_DSM}
spkall:
mkdir -p spks
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o spks --source=. --goarch=all --dsm-version=all
pushspk: spk
echo "Pushing SPK to root@${SYNO_HOST} (env var SYNO_HOST) ..."
scp tailscale.spk root@${SYNO_HOST}:
ssh root@${SYNO_HOST} /usr/syno/bin/synopkg install tailscale.spk

View File

@@ -8,11 +8,12 @@ Private WireGuard® networks made easy
This repository contains all the open source Tailscale client code and
the `tailscaled` daemon and `tailscale` CLI tool. The `tailscaled`
daemon runs primarily on Linux; it also works to varying degrees on
FreeBSD, OpenBSD, Darwin, and Windows.
daemon runs on Linux, Windows and [macOS](https://tailscale.com/kb/1065/macos-variants/), and to varying degrees on FreeBSD, OpenBSD, and Darwin. (The Tailscale iOS and Android apps use this repo's code, but this repo doesn't contain the mobile GUI code.)
The Android app is at https://github.com/tailscale/tailscale-android
The Synology package is at https://github.com/tailscale/tailscale-synology
## Using
We serve packages for a variety of distros at
@@ -43,7 +44,7 @@ If your distro has conventions that preclude the use of
distro's way, so that bug reports contain useful version information.
We only guarantee to support the latest Go release and any Go beta or
release candidate builds (currently Go 1.17) in module mode. It might
release candidate builds (currently Go 1.18) in module mode. It might
work in earlier Go versions or in GOPATH mode, but we're making no
effort to keep those working.

View File

@@ -1 +1 @@
1.19.0
1.23.0

229
api.md
View File

@@ -15,6 +15,10 @@ Currently based on {some authentication method}. Visit the [admin panel](https:/
- [POST device routes](#device-routes-post)
- Authorize machine
- [POST device authorized](#device-authorized-post)
- Tags
- [POST device tags](#device-tags-post)
- Key
- [POST device key](#device-key-post)
* **[Tailnets](#tailnet)**
- ACLs
- [GET tailnet ACL](#tailnet-acl-get)
@@ -23,6 +27,11 @@ Currently based on {some authentication method}. Visit the [admin panel](https:/
- [POST tailnet ACL validate](#tailnet-acl-validate-post): run validation tests against the tailnet's existing ACL
- [Devices](#tailnet-devices)
- [GET tailnet devices](#tailnet-devices-get)
- [Keys](#tailnet-keys)
- [GET tailnet keys](#tailnet-keys-get)
- [POST tailnet key](#tailnet-keys-post)
- [GET tailnet key](#tailnet-keys-key-get)
- [DELETE tailnet key](#tailnet-keys-key-delete)
- [DNS](#tailnet-dns)
- [GET tailnet DNS nameservers](#tailnet-dns-nameservers-get)
- [POST tailnet DNS nameservers](#tailnet-dns-nameservers-post)
@@ -263,6 +272,73 @@ curl 'https://api.tailscale.com/api/v2/device/11055/authorized' \
The response is 2xx on success. The response body is currently an empty JSON
object.
<a name=device-tags-post></a>
#### `POST /api/v2/device/:deviceID/tags` - update tags on a device
Updates the tags set on a device.
##### Parameters
###### POST Body
`tags` - The new list of tags for the device.
```
{
"tags": ["tag:foo", "tag:bar"]
}
```
##### Example
```
curl 'https://api.tailscale.com/api/v2/device/11055/tags' \
-u "tskey-yourapikey123:" \
--data-binary '{"tags": ["tag:foo", "tag:bar"]}'
```
The response is 2xx on success. The response body is currently an empty JSON
object.
<a name=device-key-post></a>
#### `POST /api/v2/device/:deviceID/key` - update device key
Allows for updating properties on the device key.
##### Parameters
###### POST Body
`keyExpiryDisabled`
- Provide `true` to disable the device's key expiry. The original key expiry time is still maintained. Upon re-enabling, the key will expire at that original time.
- Provide `false` to enable the device's key expiry. Sets the key to expire at the original expiry time prior to disabling. The key may already have expired. In that case, the device must be re-authenticated.
- Empty value will not change the key expiry.
`preauthorized`
- If `true`, don't require machine authorization (if enabled on the tailnet)
```
{
"keyExpiryDisabled": true,
"preauthorized": true
}
```
##### Example
```
curl 'https://api.tailscale.com/api/v2/device/11055/key' \
-u "tskey-yourapikey123:" \
--data-binary '{"keyExpiryDisabled": true}'
```
The response is 2xx on success. The response body is currently an empty JSON
object.
## Tailnet
A tailnet is the name of your Tailscale network.
You can find it in the top left corner of the [Admin Panel](https://login.tailscale.com/admin) beside the Tailscale logo.
@@ -670,6 +746,159 @@ Response
}
```
<a name=tailnet-keys></a>
### Keys
<a name=tailnet-keys-get></a>
#### `GET /api/v2/tailnet/:tailnet/keys` - list the keys for a tailnet
Returns a list of active keys for a tailnet
for the user who owns the API key used to perform this query.
Supply the tailnet of interest in the path.
##### Parameters
No parameters.
##### Returns
Returns a JSON object with the IDs of all active keys.
This includes both API keys and also machine authentication keys.
In the future, this may provide more information about each key than just the ID.
##### Example
```
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/keys' \
-u "tskey-yourapikey123:"
```
Response:
```
{"keys": [
{"id": "kYKVU14CNTRL"},
{"id": "k68VdZ3CNTRL"},
{"id": "kJ9nq43CNTRL"},
{"id": "kkThgj1CNTRL"}
]}
```
<a name=tailnet-keys-post></a>
#### `POST /api/v2/tailnet/:tailnet/keys` - create a new key for a tailnet
Create a new key in a tailnet associated
with the user who owns the API key used to perform this request.
Supply the tailnet in the path.
##### Parameters
###### POST Body
`capabilities` - A mapping of resources to permissible actions.
```
{
"capabilities": {
"devices": {
"create": {
"reusable": false,
"ephemeral": false
}
}
}
}
```
##### Returns
Returns a JSON object with the provided capabilities in addition to the
generated key. The key should be recorded and kept safe and secure as it
wields the capabilities specified in the request. The identity of the key
is embedded in the key itself and can be used to perform operations on
the key (e.g., revoking it or retrieving information about it).
The full key can no longer be retrieved by the server.
##### Example
```
echo '{
"capabilities": {
"devices": {
"create": {
"reusable": false,
"ephemeral": false
}
}
}
}' | curl -X POST --data-binary @- https://api.tailscale.com/api/v2/tailnet/example.com/keys \
-u "tskey-yourapikey123:" \
-H "Content-Type: application/json" | jsonfmt
```
Response:
```
{
"id": "k123456CNTRL",
"key": "tskey-k123456CNTRL-abcdefghijklmnopqrstuvwxyz",
"created": "2021-12-09T23:22:39Z",
"expires": "2022-03-09T23:22:39Z",
"capabilities": {"devices": {"create": {"reusable": false, "ephemeral": false}}}
}
```
<a name=tailnet-keys-key-get></a>
#### `GET /api/v2/tailnet/:tailnet/keys/:keyid` - get information for a specific key
Returns a JSON object with information about specific key.
Supply the tailnet and key ID of interest in the path.
##### Parameters
No parameters.
##### Returns
Returns a JSON object with information about the key such as
when it was created and when it expires.
It also lists the capabilities associated with the key.
##### Example
```
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/keys/k123456CNTRL' \
-u "tskey-yourapikey123:"
```
Response:
```
{
"id": "k123456CNTRL",
"created": "2021-12-09T22:13:53Z",
"expires": "2022-03-09T22:13:53Z",
"capabilities": {"devices": {"create": {"reusable": false, "ephemeral": false}}}
}
```
<a name=tailnet-keys-key-delete></a>
#### `DELETE /api/v2/tailnet/:tailnet/keys/:keyid` - delete a specific key
Deletes a specific key.
Supply the tailnet and key ID of interest in the path.
##### Parameters
No parameters.
##### Returns
This reports status 200 upon success.
##### Example
```
curl -X DELETE 'https://api.tailscale.com/api/v2/tailnet/example.com/keys/k123456CNTRL' \
-u "tskey-yourapikey123:"
```
<a name=tailnet-dns></a>
### DNS

View File

@@ -45,4 +45,4 @@ EOF
exit 0
fi
exec go build -ldflags "-X tailscale.com/version.Long=${LONG} -X tailscale.com/version.Short=${SHORT} -X tailscale.com/version.GitCommit=${GIT_HASH}" "$@"
exec ./tool/go build -ldflags "-X tailscale.com/version.Long=${LONG} -X tailscale.com/version.Short=${SHORT} -X tailscale.com/version.GitCommit=${GIT_HASH}" "$@"

View File

@@ -19,10 +19,20 @@
set -eu
# Use the "go" binary from the "tool" directory (which is github.com/tailscale/go)
export PATH=$PWD/tool:$PATH
eval $(./build_dist.sh shellvars)
DEFAULT_TAGS="v${VERSION_SHORT},v${VERSION_MINOR}"
DEFAULT_REPOS="tailscale/tailscale,ghcr.io/tailscale/tailscale"
DEFAULT_BASE="ghcr.io/tailscale/alpine-base:3.14"
PUSH="${PUSH:-false}"
REPOS="${REPOS:-${DEFAULT_REPOS}}"
TAGS="${TAGS:-${DEFAULT_TAGS}}"
BASE="${BASE:-${DEFAULT_BASE}}"
go run github.com/tailscale/mkctr@latest \
--base="ghcr.io/tailscale/alpine-base:3.14" \
--gopaths="\
tailscale.com/cmd/tailscale:/usr/local/bin/tailscale, \
tailscale.com/cmd/tailscaled:/usr/local/bin/tailscaled" \
@@ -30,6 +40,7 @@ go run github.com/tailscale/mkctr@latest \
-X tailscale.com/version.Long=${VERSION_LONG} \
-X tailscale.com/version.Short=${VERSION_SHORT} \
-X tailscale.com/version.GitCommit=${VERSION_GIT_HASH}" \
--tags="${VERSION_SHORT},${VERSION_MINOR}" \
--repos="tailscale/tailscale,ghcr.io/tailscale/tailscale" \
--push
--base="${BASE}" \
--tags="${TAGS}" \
--repos="${REPOS}" \
--push="${PUSH}"

View File

@@ -19,9 +19,9 @@ func New(socket string) (*BIRDClient, error) {
if err != nil {
return nil, fmt.Errorf("failed to connect to BIRD: %w", err)
}
b := &BIRDClient{socket: socket, conn: conn, bs: bufio.NewScanner(conn)}
b := &BIRDClient{socket: socket, conn: conn, scanner: bufio.NewScanner(conn)}
// Read and discard the first line as that is the welcome message.
if _, err := b.readLine(); err != nil {
if _, err := b.readResponse(); err != nil {
return nil, err
}
return b, nil
@@ -29,9 +29,9 @@ func New(socket string) (*BIRDClient, error) {
// BIRDClient handles communication with the BIRD Internet Routing Daemon.
type BIRDClient struct {
socket string
conn net.Conn
bs *bufio.Scanner
socket string
conn net.Conn
scanner *bufio.Scanner
}
// Close closes the underlying connection to BIRD.
@@ -39,7 +39,7 @@ func (b *BIRDClient) Close() error { return b.conn.Close() }
// DisableProtocol disables the provided protocol.
func (b *BIRDClient) DisableProtocol(protocol string) error {
out, err := b.exec("disable %s\n", protocol)
out, err := b.exec("disable %s", protocol)
if err != nil {
return err
}
@@ -53,7 +53,7 @@ func (b *BIRDClient) DisableProtocol(protocol string) error {
// EnableProtocol enables the provided protocol.
func (b *BIRDClient) EnableProtocol(protocol string) error {
out, err := b.exec("enable %s\n", protocol)
out, err := b.exec("enable %s", protocol)
if err != nil {
return err
}
@@ -65,19 +65,65 @@ func (b *BIRDClient) EnableProtocol(protocol string) error {
return fmt.Errorf("failed to enable %s: %v", protocol, out)
}
func (b *BIRDClient) exec(cmd string, args ...interface{}) (string, error) {
// BIRD CLI docs from https://bird.network.cz/?get_doc&v=20&f=prog-2.html#ss2.9
// Each session of the CLI consists of a sequence of request and replies,
// slightly resembling the FTP and SMTP protocols.
// Requests are commands encoded as a single line of text,
// replies are sequences of lines starting with a four-digit code
// followed by either a space (if it's the last line of the reply) or
// a minus sign (when the reply is going to continue with the next line),
// the rest of the line contains a textual message semantics of which depends on the numeric code.
// If a reply line has the same code as the previous one and it's a continuation line,
// the whole prefix can be replaced by a single white space character.
//
// Reply codes starting with 0 stand for action successfully completed messages,
// 1 means table entry, 8 runtime error and 9 syntax error.
func (b *BIRDClient) exec(cmd string, args ...any) (string, error) {
if _, err := fmt.Fprintf(b.conn, cmd, args...); err != nil {
return "", err
}
return b.readLine()
fmt.Fprintln(b.conn)
return b.readResponse()
}
func (b *BIRDClient) readLine() (string, error) {
if !b.bs.Scan() {
return "", fmt.Errorf("reading response from bird failed")
// hasResponseCode reports whether the provided byte slice is
// prefixed with a BIRD response code.
// Equivalent regex: `^\d{4}[ -]`.
func hasResponseCode(s []byte) bool {
if len(s) < 5 {
return false
}
if err := b.bs.Err(); err != nil {
return "", err
for _, b := range s[:4] {
if '0' <= b && b <= '9' {
continue
}
return false
}
return b.bs.Text(), nil
return s[4] == ' ' || s[4] == '-'
}
func (b *BIRDClient) readResponse() (string, error) {
var resp strings.Builder
var done bool
for !done {
if !b.scanner.Scan() {
return "", fmt.Errorf("reading response from bird failed: %q", resp.String())
}
if err := b.scanner.Err(); err != nil {
return "", err
}
out := b.scanner.Bytes()
if _, err := resp.Write(out); err != nil {
return "", err
}
if hasResponseCode(out) {
done = out[4] == ' '
}
if !done {
resp.WriteRune('\n')
}
}
return resp.String(), nil
}

111
chirp/chirp_test.go Normal file
View File

@@ -0,0 +1,111 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package chirp
import (
"bufio"
"errors"
"fmt"
"net"
"path/filepath"
"strings"
"testing"
)
type fakeBIRD struct {
net.Listener
protocolsEnabled map[string]bool
sock string
}
func newFakeBIRD(t *testing.T, protocols ...string) *fakeBIRD {
sock := filepath.Join(t.TempDir(), "sock")
l, err := net.Listen("unix", sock)
if err != nil {
t.Fatal(err)
}
pe := make(map[string]bool)
for _, p := range protocols {
pe[p] = false
}
return &fakeBIRD{
Listener: l,
protocolsEnabled: pe,
sock: sock,
}
}
func (fb *fakeBIRD) listen() error {
for {
c, err := fb.Accept()
if err != nil {
if errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
go fb.handle(c)
}
}
func (fb *fakeBIRD) handle(c net.Conn) {
fmt.Fprintln(c, "0001 BIRD 2.0.8 ready.")
sc := bufio.NewScanner(c)
for sc.Scan() {
cmd := sc.Text()
args := strings.Split(cmd, " ")
switch args[0] {
case "enable":
en, ok := fb.protocolsEnabled[args[1]]
if !ok {
fmt.Fprintln(c, "9001 syntax error, unexpected CF_SYM_UNDEFINED, expecting CF_SYM_KNOWN or TEXT or ALL")
} else if en {
fmt.Fprintf(c, "0010-%s: already enabled\n", args[1])
} else {
fmt.Fprintf(c, "0011-%s: enabled\n", args[1])
}
fmt.Fprintln(c, "0000 ")
fb.protocolsEnabled[args[1]] = true
case "disable":
en, ok := fb.protocolsEnabled[args[1]]
if !ok {
fmt.Fprintln(c, "9001 syntax error, unexpected CF_SYM_UNDEFINED, expecting CF_SYM_KNOWN or TEXT or ALL")
} else if !en {
fmt.Fprintf(c, "0008-%s: already disabled\n", args[1])
} else {
fmt.Fprintf(c, "0009-%s: disabled\n", args[1])
}
fmt.Fprintln(c, "0000 ")
fb.protocolsEnabled[args[1]] = false
}
}
}
func TestChirp(t *testing.T) {
fb := newFakeBIRD(t, "tailscale")
defer fb.Close()
go fb.listen()
c, err := New(fb.sock)
if err != nil {
t.Fatal(err)
}
if err := c.EnableProtocol("tailscale"); err != nil {
t.Fatal(err)
}
if err := c.EnableProtocol("tailscale"); err != nil {
t.Fatal(err)
}
if err := c.DisableProtocol("tailscale"); err != nil {
t.Fatal(err)
}
if err := c.DisableProtocol("tailscale"); err != nil {
t.Fatal(err)
}
if err := c.EnableProtocol("rando"); err == nil {
t.Fatalf("enabling %q succeded", "rando")
}
if err := c.DisableProtocol("rando"); err == nil {
t.Fatalf("disabling %q succeded", "rando")
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.18
// +build !go1.18
package tailscale
func init() {
you_need_Go_1_18_to_compile_Tailscale()
}

View File

@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
// Package tailscale contains Tailscale client code.
package tailscale
@@ -38,6 +41,9 @@ var (
// TailscaledSocket is the tailscaled Unix socket. It's used by the TailscaledDialer.
TailscaledSocket = paths.DefaultTailscaledSocket()
// TailscaledSocketSetExplicitly reports whether the user explicitly set TailscaledSocket.
TailscaledSocketSetExplicitly bool
// TailscaledDialer is the DialContext func that connects to the local machine's
// tailscaled or equivalent.
TailscaledDialer = defaultDialer
@@ -47,7 +53,8 @@ func defaultDialer(ctx context.Context, network, addr string) (net.Conn, error)
if addr != "local-tailscaled.sock:80" {
return nil, fmt.Errorf("unexpected URL address %q", addr)
}
if TailscaledSocket == paths.DefaultTailscaledSocket() {
// TODO: make this part of a safesocket.ConnectionStrategy
if !TailscaledSocketSetExplicitly {
// On macOS, when dialing from non-sandboxed program to sandboxed GUI running
// a TCP server on a random port, find the random port. For HTTP connections,
// we don't send the token. It gets added in an HTTP Basic-Auth header.
@@ -56,7 +63,11 @@ func defaultDialer(ctx context.Context, network, addr string) (net.Conn, error)
return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port))
}
}
return safesocket.Connect(TailscaledSocket, safesocket.WindowsLocalPort)
s := safesocket.DefaultConnectionStrategy(TailscaledSocket)
// The user provided a non-default tailscaled socket address.
// Connect only to exactly what they provided.
s.UseFallback(false)
return safesocket.Connect(s)
}
var (
@@ -90,6 +101,28 @@ func DoLocalRequest(req *http.Request) (*http.Response, error) {
return tsClient.Do(req)
}
func doLocalRequestNiceError(req *http.Request) (*http.Response, error) {
res, err := DoLocalRequest(req)
if err == nil {
if server := res.Header.Get("Tailscale-Version"); server != "" && server != version.Long && onVersionMismatch != nil {
onVersionMismatch(version.Long, server)
}
if res.StatusCode == 403 {
all, _ := ioutil.ReadAll(res.Body)
return nil, &AccessDeniedError{errors.New(errorMessageFromBody(all))}
}
return res, nil
}
if ue, ok := err.(*url.Error); ok {
if oe, ok := ue.Err.(*net.OpError); ok && oe.Op == "dial" {
path := req.URL.Path
pathPrefix, _, _ := strings.Cut(path, "?")
return nil, fmt.Errorf("Failed to connect to local Tailscale daemon for %s; %s Error: %w", pathPrefix, tailscaledConnectHint(), oe)
}
}
return nil, err
}
type errorJSON struct {
Error string
}
@@ -140,32 +173,16 @@ func send(ctx context.Context, method, path string, wantStatus int, body io.Read
if err != nil {
return nil, err
}
res, err := DoLocalRequest(req)
res, err := doLocalRequestNiceError(req)
if err != nil {
if ue, ok := err.(*url.Error); ok {
if oe, ok := ue.Err.(*net.OpError); ok && oe.Op == "dial" {
pathPrefix := path
if i := strings.Index(path, "?"); i != -1 {
pathPrefix = path[:i]
}
return nil, fmt.Errorf("Failed to connect to local Tailscale daemon for %s; %s Error: %w", pathPrefix, tailscaledConnectHint(), oe)
}
}
return nil, err
}
defer res.Body.Close()
if server := res.Header.Get("Tailscale-Version"); server != "" && server != version.Long && onVersionMismatch != nil {
onVersionMismatch(version.Long, server)
}
slurp, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if res.StatusCode != wantStatus {
if res.StatusCode == 403 {
return nil, &AccessDeniedError{errors.New(errorMessageFromBody(slurp))}
}
err := fmt.Errorf("HTTP %s: %s (expected %v)", res.Status, slurp, wantStatus)
return nil, bestError(err, slurp)
}
return slurp, nil
@@ -223,12 +240,22 @@ func BugReport(ctx context.Context, note string) (string, error) {
return strings.TrimSpace(string(body)), nil
}
// DebugAction invokes a debug action, such as "rebind" or "restun".
// These are development tools and subject to change or removal over time.
func DebugAction(ctx context.Context, action string) error {
body, err := send(ctx, "POST", "/localapi/v0/debug?action="+url.QueryEscape(action), 200, nil)
if err != nil {
return fmt.Errorf("error %w: %s", err, body)
}
return nil
}
// Status returns the Tailscale daemon's status.
func Status(ctx context.Context) (*ipnstate.Status, error) {
return status(ctx, "")
}
// StatusWithPeers returns the Tailscale daemon's status, without the peer info.
// StatusWithoutPeers returns the Tailscale daemon's status, without the peer info.
func StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
return status(ctx, "?peers=false")
}
@@ -267,7 +294,7 @@ func GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, siz
if err != nil {
return nil, 0, err
}
res, err := DoLocalRequest(req)
res, err := doLocalRequestNiceError(req)
if err != nil {
return nil, 0, err
}
@@ -295,6 +322,30 @@ func FileTargets(ctx context.Context) ([]apitype.FileTarget, error) {
return fts, nil
}
// PushFile sends Taildrop file r to target.
//
// A size of -1 means unknown.
// The name parameter is the original filename, not escaped.
func PushFile(ctx context.Context, target tailcfg.StableNodeID, size int64, name string, r io.Reader) error {
req, err := http.NewRequestWithContext(ctx, "PUT", "http://local-tailscaled.sock/localapi/v0/file-put/"+string(target)+"/"+url.PathEscape(name), r)
if err != nil {
return err
}
if size != -1 {
req.ContentLength = size
}
res, err := doLocalRequestNiceError(req)
if err != nil {
return err
}
if res.StatusCode == 200 {
io.Copy(io.Discard, res.Body)
return nil
}
all, _ := io.ReadAll(res.Body)
return bestError(fmt.Errorf("%s: %s", res.Status, all), all)
}
func CheckIPForwarding(ctx context.Context) error {
body, err := get200(ctx, "/localapi/v0/check-ip-forwarding")
if err != nil {
@@ -466,8 +517,8 @@ func tailscaledConnectHint() string {
// SubState=dead
st := map[string]string{}
for _, line := range strings.Split(string(out), "\n") {
if i := strings.Index(line, "="); i != -1 {
st[line[:i]] = strings.TrimSpace(line[i+1:])
if k, v, ok := strings.Cut(line, "="); ok {
st[k] = strings.TrimSpace(v)
}
}
if st["LoadState"] == "loaded" &&

View File

@@ -69,14 +69,14 @@ func main() {
gen(buf, imports, typ, pkg.Types)
}
w := func(format string, args ...interface{}) {
w := func(format string, args ...any) {
fmt.Fprintf(buf, format+"\n", args...)
}
if *flagCloneFunc {
w("// Clone duplicates src into dst and reports whether it succeeded.")
w("// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,")
w("// where T is one of %s.", *flagTypes)
w("func Clone(dst, src interface{}) bool {")
w("func Clone(dst, src any) bool {")
w(" switch src := src.(type) {")
for _, typeName := range typeNames {
w(" case *%s:", typeName)
@@ -158,7 +158,7 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisP
fmt.Fprintf(buf, "// Clone makes a deep copy of %s.\n", name)
fmt.Fprintf(buf, "// The result aliases no memory with the original.\n")
fmt.Fprintf(buf, "func (src *%s) Clone() *%s {\n", name, name)
writef := func(format string, args ...interface{}) {
writef := func(format string, args ...any) {
fmt.Fprintf(buf, "\t"+format+"\n", args...)
}
writef("if src == nil {")
@@ -172,9 +172,15 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisP
if !codegen.ContainsPointers(ft) {
continue
}
if named, _ := ft.(*types.Named); named != nil && !hasBasicUnderlying(ft) {
writef("dst.%s = *src.%s.Clone()", fname, fname)
continue
if named, _ := ft.(*types.Named); named != nil {
if isViewType(ft) {
writef("dst.%s = src.%s", fname, fname)
continue
}
if !hasBasicUnderlying(ft) {
writef("dst.%s = *src.%s.Clone()", fname, fname)
continue
}
}
switch ft := ft.Underlying().(type) {
case *types.Slice:
@@ -234,6 +240,17 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisP
buf.Write(codegen.AssertStructUnchanged(t, thisPkg, name, "Clone", imports))
}
func isViewType(typ types.Type) bool {
t, ok := typ.Underlying().(*types.Struct)
if !ok {
return false
}
if t.NumFields() != 1 {
return false
}
return t.Field(0).Name() == "ж"
}
func hasBasicUnderlying(typ types.Type) bool {
switch typ.Underlying().(type) {
case *types.Slice, *types.Map:

View File

@@ -12,14 +12,11 @@ import (
"net"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
)
var (
dnsMu sync.Mutex
dnsCache = map[string][]net.IP{}
)
var dnsCache atomic.Value // of []byte
var bootstrapDNSRequests = expvar.NewInt("counter_bootstrap_dns_requests")
@@ -37,6 +34,7 @@ func refreshBootstrapDNS() {
if *bootstrapDNS == "" {
return
}
dnsEntries := make(map[string][]net.IP)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
names := strings.Split(*bootstrapDNS, ",")
@@ -47,23 +45,23 @@ func refreshBootstrapDNS() {
log.Printf("bootstrap DNS lookup %q: %v", name, err)
continue
}
dnsMu.Lock()
dnsCache[name] = addrs
dnsMu.Unlock()
dnsEntries[name] = addrs
}
j, err := json.MarshalIndent(dnsEntries, "", "\t")
if err != nil {
// leave the old values in place
return
}
dnsCache.Store(j)
}
func handleBootstrapDNS(w http.ResponseWriter, r *http.Request) {
bootstrapDNSRequests.Add(1)
dnsMu.Lock()
j, err := json.MarshalIndent(dnsCache, "", "\t")
dnsMu.Unlock()
if err != nil {
log.Printf("bootstrap DNS JSON: %v", err)
http.Error(w, "JSON marshal error", 500)
return
}
w.Header().Set("Content-Type", "application/json")
j, _ := dnsCache.Load().([]byte)
// Bootstrap DNS requests occur cross-regions,
// and are randomized per request,
// so keeping a connection open is pointlessly expensive.
w.Header().Set("Connection", "close")
w.Write(j)
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"net/http"
"testing"
)
func BenchmarkHandleBootstrapDNS(b *testing.B) {
prev := *bootstrapDNS
*bootstrapDNS = "log.tailscale.io,login.tailscale.com,controlplane.tailscale.com,login.us.tailscale.com"
defer func() {
*bootstrapDNS = prev
}()
refreshBootstrapDNS()
w := new(bitbucketResponseWriter)
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(b *testing.PB) {
for b.Next() {
handleBootstrapDNS(w, nil)
}
})
}
type bitbucketResponseWriter struct{}
func (b *bitbucketResponseWriter) Header() http.Header { return make(http.Header) }
func (b *bitbucketResponseWriter) Write(p []byte) (int, error) { return len(p), nil }
func (b *bitbucketResponseWriter) WriteHeader(statusCode int) {}

View File

@@ -67,8 +67,8 @@ func NewManualCertManager(certdir, hostname string) (certProvider, error) {
if err != nil {
return nil, fmt.Errorf("can not load cert: %w", err)
}
if x509Cert.VerifyHostname(hostname) != nil {
return nil, errors.New("refuse to load cert: hostname mismatch with key")
if err := x509Cert.VerifyHostname(hostname); err != nil {
return nil, fmt.Errorf("cert invalid for hostname %q: %w", hostname, err)
}
return &manualCertManager{cert: &cert, hostname: hostname}, nil
}

View File

@@ -12,9 +12,11 @@ import (
"errors"
"expvar"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"math"
"net"
"net/http"
"os"
@@ -23,6 +25,7 @@ import (
"strings"
"time"
"golang.org/x/time/rate"
"tailscale.com/atomicfile"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
@@ -35,23 +38,31 @@ import (
var (
dev = flag.Bool("dev", false, "run in localhost development mode")
addr = flag.String("a", ":443", "server address")
addr = flag.String("a", ":443", "server HTTPS listen address, in form \":port\", \"ip:port\", or for IPv6 \"[ip]:port\". If the IP is omitted, it defaults to all interfaces.")
httpPort = flag.Int("http-port", 80, "The port on which to serve HTTP. Set to -1 to disable. The listener is bound to the same IP (if any) as specified in the -a flag.")
stunPort = flag.Int("stun-port", 3478, "The UDP port on which to serve STUN. The listener is bound to the same IP (if any) as specified in the -a flag.")
configPath = flag.String("c", "", "config file path")
certMode = flag.String("certmode", "letsencrypt", "mode for getting a cert. possible options: manual, letsencrypt")
certDir = flag.String("certdir", tsweb.DefaultCertDir("derper-certs"), "directory to store LetsEncrypt certs, if addr's port is :443")
hostname = flag.String("hostname", "derp.tailscale.com", "LetsEncrypt host name, if addr's port is :443")
logCollection = flag.String("logcollection", "", "If non-empty, logtail collection to log to")
runSTUN = flag.Bool("stun", false, "also run a STUN server")
runSTUN = flag.Bool("stun", true, "whether to run a STUN server. It will bind to the same IP (if any) as the --addr flag value.")
meshPSKFile = flag.String("mesh-psk-file", defaultMeshPSKFile(), "if non-empty, path to file containing the mesh pre-shared key file. It should contain some hex string; whitespace is trimmed.")
meshWith = flag.String("mesh-with", "", "optional comma-separated list of hostnames to mesh with; the server's own hostname can be in the list")
bootstrapDNS = flag.String("bootstrap-dns-names", "", "optional comma-separated list of hostnames to make available at /bootstrap-dns")
verifyClients = flag.Bool("verify-clients", false, "verify clients to this DERP server through a local tailscaled instance.")
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")
)
var (
stats = new(metrics.Set)
stunDisposition = &metrics.LabelMap{Label: "disposition"}
stunAddrFamily = &metrics.LabelMap{Label: "family"}
stats = new(metrics.Set)
stunDisposition = &metrics.LabelMap{Label: "disposition"}
stunAddrFamily = &metrics.LabelMap{Label: "family"}
tlsRequestVersion = &metrics.LabelMap{Label: "version"}
tlsActiveVersion = &metrics.LabelMap{Label: "version"}
stunReadError = stunDisposition.Get("read_error")
stunNotSTUN = stunDisposition.Get("not_stun")
@@ -66,6 +77,8 @@ func init() {
stats.Set("counter_requests", stunDisposition)
stats.Set("counter_addrfamily", stunAddrFamily)
expvar.Publish("stun", stats)
expvar.Publish("derper_tls_request_version", tlsRequestVersion)
expvar.Publish("gauge_derper_tls_active_version", tlsActiveVersion)
}
type config struct {
@@ -200,7 +213,7 @@ func main() {
debug.Handle("traffic", "Traffic check", http.HandlerFunc(s.ServeDebugTraffic))
if *runSTUN {
go serveSTUN(listenHost)
go serveSTUN(listenHost, *stunPort)
}
httpsrv := &http.Server{
@@ -235,7 +248,26 @@ func main() {
cert.Certificate = append(cert.Certificate, s.MetaCert())
return cert, nil
}
// Disable TLS 1.0 and 1.1, which are obsolete and have security issues.
httpsrv.TLSConfig.MinVersion = tls.VersionTLS12
httpsrv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS != nil {
label := "unknown"
switch r.TLS.Version {
case tls.VersionTLS10:
label = "1.0"
case tls.VersionTLS11:
label = "1.1"
case tls.VersionTLS12:
label = "1.2"
case tls.VersionTLS13:
label = "1.3"
}
tlsRequestVersion.Add(label, 1)
tlsActiveVersion.Add(label, 1)
defer tlsActiveVersion.Add(label, -1)
}
// Set HTTP headers to appease automated security scanners.
//
// Security automation gets cranky when HTTPS sites don't
@@ -250,25 +282,27 @@ func main() {
w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; form-action 'none'; base-uri 'self'; block-all-mixed-content; plugin-types 'none'")
mux.ServeHTTP(w, r)
})
go func() {
port80srv := &http.Server{
Addr: net.JoinHostPort(listenHost, "80"),
Handler: certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}),
ReadTimeout: 30 * time.Second,
// Crank up WriteTimeout a bit more than usually
// necessary just so we can do long CPU profiles
// and not hit net/http/pprof's "profile
// duration exceeds server's WriteTimeout".
WriteTimeout: 5 * time.Minute,
}
err := port80srv.ListenAndServe()
if err != nil {
if err != http.ErrServerClosed {
log.Fatal(err)
if *httpPort > -1 {
go func() {
port80srv := &http.Server{
Addr: net.JoinHostPort(listenHost, fmt.Sprintf("%d", *httpPort)),
Handler: certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}),
ReadTimeout: 30 * time.Second,
// Crank up WriteTimeout a bit more than usually
// necessary just so we can do long CPU profiles
// and not hit net/http/pprof's "profile
// duration exceeds server's WriteTimeout".
WriteTimeout: 5 * time.Minute,
}
}
}()
err = httpsrv.ListenAndServeTLS("", "")
err := port80srv.ListenAndServe()
if err != nil {
if err != http.ErrServerClosed {
log.Fatal(err)
}
}
}()
}
err = rateLimitedListenAndServeTLS(httpsrv)
} else {
log.Printf("derper: serving on %s", *addr)
err = httpsrv.ListenAndServe()
@@ -289,8 +323,8 @@ func probeHandler(w http.ResponseWriter, r *http.Request) {
}
}
func serveSTUN(host string) {
pc, err := net.ListenPacket("udp", net.JoinHostPort(host, "3478"))
func serveSTUN(host string, port int) {
pc, err := net.ListenPacket("udp", net.JoinHostPort(host, fmt.Sprint(port)))
if err != nil {
log.Fatalf("failed to open STUN listener: %v", err)
}
@@ -362,3 +396,63 @@ func defaultMeshPSKFile() string {
}
return ""
}
func rateLimitedListenAndServeTLS(srv *http.Server) error {
addr := srv.Addr
if addr == "" {
addr = ":https"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
rln := newRateLimitedListener(ln, rate.Limit(*acceptConnLimit), *acceptConnBurst)
expvar.Publish("tls_listener", rln.ExpVar())
defer rln.Close()
return srv.ServeTLS(rln, "", "")
}
type rateLimitedListener struct {
// These are at the start of the struct to ensure 64-bit alignment
// on 32-bit architecture regardless of what other fields may exist
// in this package.
numAccepts expvar.Int // does not include number of rejects
numRejects expvar.Int
net.Listener
lim *rate.Limiter
}
func newRateLimitedListener(ln net.Listener, limit rate.Limit, burst int) *rateLimitedListener {
return &rateLimitedListener{Listener: ln, lim: rate.NewLimiter(limit, burst)}
}
func (l *rateLimitedListener) ExpVar() expvar.Var {
m := new(metrics.Set)
m.Set("counter_accepted_connections", &l.numAccepts)
m.Set("counter_rejected_connections", &l.numRejects)
return m
}
var errLimitedConn = errors.New("cannot accept connection; rate limited")
func (l *rateLimitedListener) Accept() (net.Conn, error) {
// Even under a rate limited situation, we accept the connection immediately
// and close it, rather than being slow at accepting new connections.
// This provides two benefits: 1) it signals to the client that something
// is going on on the server, and 2) it prevents new connections from
// piling up and occupying resources in the OS kernel.
// The client will retry as needing (with backoffs in place).
cn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
if !l.lim.Allow() {
l.numRejects.Add(1)
cn.Close()
return nil, errLimitedConn
}
l.numAccepts.Add(1)
return cn, nil
}

View File

@@ -9,7 +9,9 @@ import (
"bytes"
"context"
crand "crypto/rand"
"crypto/x509"
"encoding/json"
"errors"
"flag"
"fmt"
"html"
@@ -17,7 +19,9 @@ import (
"log"
"net"
"net/http"
"os"
"sort"
"strings"
"sync"
"time"
@@ -33,28 +37,52 @@ var (
listen = flag.String("listen", ":8030", "HTTP listen address")
)
// certReissueAfter is the time after which we expect all certs to be
// reissued, at minimum.
//
// This is currently set to the date of the LetsEncrypt ALPN revocation event of Jan 2022:
// https://community.letsencrypt.org/t/questions-about-renewing-before-tls-alpn-01-revocations/170449
//
// If there's another revocation event, bump this again.
var certReissueAfter = time.Unix(1643226768, 0)
var (
mu sync.Mutex
state = map[nodePair]pairStatus{}
lastDERPMap *tailcfg.DERPMap
lastDERPMapAt time.Time
certs = map[string]*x509.Certificate{}
)
func main() {
flag.Parse()
// proactively load the DERP map. Nothing terrible happens if this fails, so we ignore
// the error. The Slack bot will print a notification that the DERP map was empty.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, _ = getDERPMap(ctx)
go probeLoop()
go slackLoop()
log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(serve)))
}
func setCert(name string, cert *x509.Certificate) {
mu.Lock()
defer mu.Unlock()
certs[name] = cert
}
type overallStatus struct {
good, bad []string
}
func (st *overallStatus) addBadf(format string, a ...interface{}) {
func (st *overallStatus) addBadf(format string, a ...any) {
st.bad = append(st.bad, fmt.Sprintf(format, a...))
}
func (st *overallStatus) addGoodf(format string, a ...interface{}) {
func (st *overallStatus) addGoodf(format string, a ...any) {
st.good = append(st.good, fmt.Sprintf(format, a...))
}
@@ -93,16 +121,41 @@ func getOverallStatus() (o overallStatus) {
}
}
}
var subjs []string
for k := range certs {
subjs = append(subjs, k)
}
sort.Strings(subjs)
soon := time.Now().Add(14 * 24 * time.Hour) // in 2 weeks; autocert does 30 days by default
for _, s := range subjs {
cert := certs[s]
if cert.NotBefore.Before(certReissueAfter) {
o.addBadf("cert %q needs reissuing; NotBefore=%v", s, cert.NotBefore.Format(time.RFC3339))
continue
}
if cert.NotAfter.Before(soon) {
o.addBadf("cert %q expiring soon (%v); wasn't auto-refreshed", s, cert.NotAfter.Format(time.RFC3339))
continue
}
o.addGoodf("cert %q good %v - %v", s, cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339))
}
return
}
func serve(w http.ResponseWriter, r *http.Request) {
st := getOverallStatus()
summary := "All good"
if len(st.bad) > 0 {
if (float64(len(st.bad)) / float64(len(st.bad)+len(st.good))) > 0.25 {
// This will generate an alert and page a human.
// It also ends up in Slack, but as part of the alert handling pipeline not
// because we generated a Slack notification from here.
w.WriteHeader(500)
summary = fmt.Sprintf("%d problems", len(st.bad))
}
io.WriteString(w, "<html><head><style>.bad { font-weight: bold; color: #700; }</style></head>\n")
fmt.Fprintf(w, "<body><h1>derp probe</h1>\n%s:<ul>", summary)
for _, s := range st.bad {
@@ -114,6 +167,71 @@ func serve(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "</ul></body></html>\n")
}
func notifySlack(text string) error {
type SlackRequestBody struct {
Text string `json:"text"`
}
slackBody, err := json.Marshal(SlackRequestBody{Text: text})
if err != nil {
return err
}
webhookUrl := os.Getenv("SLACK_WEBHOOK")
if webhookUrl == "" {
return errors.New("No SLACK_WEBHOOK configured")
}
req, err := http.NewRequest("POST", webhookUrl, bytes.NewReader(slackBody))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New(resp.Status)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != "ok" {
return errors.New("Non-ok response returned from Slack")
}
return nil
}
// We only page a human if it looks like there is a significant outage across multiple regions.
// To Slack, we report all failures great and small.
func slackLoop() {
inBadState := false
for {
time.Sleep(time.Second * 30)
st := getOverallStatus()
if len(st.bad) > 0 && !inBadState {
err := notifySlack(strings.Join(st.bad, "\n"))
if err == nil {
inBadState = true
} else {
log.Printf("%d problems, notify Slack failed: %v", len(st.bad), err)
}
}
if len(st.bad) == 0 && inBadState {
err := notifySlack("All DERPs recovered.")
if err == nil {
inBadState = false
}
}
}
}
func sortedRegions(dm *tailcfg.DERPMap) []*tailcfg.DERPRegion {
ret := make([]*tailcfg.DERPRegion, 0, len(dm.Regions))
for _, r := range dm.Regions {
@@ -308,7 +426,7 @@ func probeNodePair(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.D
}
// Receive the random packet.
recvc := make(chan interface{}, 1) // either derp.ReceivedPacket or error
recvc := make(chan any, 1) // either derp.ReceivedPacket or error
go func() {
for {
m, err := toc.Recv()
@@ -359,6 +477,21 @@ func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (*de
if err != nil {
return nil, err
}
cs, ok := dc.TLSConnectionState()
if !ok {
dc.Close()
return nil, errors.New("no TLS state")
}
if len(cs.PeerCertificates) == 0 {
dc.Close()
return nil, errors.New("no peer certificates")
}
if cs.ServerName != n.HostName {
dc.Close()
return nil, fmt.Errorf("TLS server name %q != derp hostname %q", cs.ServerName, n.HostName)
}
setCert(cs.ServerName, cs.PeerCertificates[0])
errc := make(chan error, 1)
go func() {
m, err := dc.Recv()

View File

@@ -2,13 +2,15 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The hello binary runs hello.ipn.dev.
// The hello binary runs hello.ts.net.
package main // import "tailscale.com/cmd/hello"
import (
"context"
"crypto/tls"
_ "embed"
"encoding/json"
"errors"
"flag"
"html/template"
"io/ioutil"
@@ -16,6 +18,7 @@ import (
"net/http"
"os"
"strings"
"time"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale/apitype"
@@ -69,11 +72,31 @@ func main() {
if *httpsAddr != "" {
log.Printf("running HTTPS server on %s", *httpsAddr)
go func() {
errc <- http.ListenAndServeTLS(*httpsAddr,
"/etc/hello/hello.ipn.dev.crt",
"/etc/hello/hello.ipn.dev.key",
nil,
)
hs := &http.Server{
Addr: *httpsAddr,
TLSConfig: &tls.Config{
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
switch hi.ServerName {
case "hello.ts.net":
return tailscale.GetCertificate(hi)
case "hello.ipn.dev":
c, err := tls.LoadX509KeyPair(
"/etc/hello/hello.ipn.dev.crt",
"/etc/hello/hello.ipn.dev.key",
)
if err != nil {
return nil, err
}
return &c, nil
}
return nil, errors.New("invalid SNI name")
},
},
IdleTimeout: 30 * time.Second,
ReadHeaderTimeout: 20 * time.Second,
MaxHeaderBytes: 10 << 10,
}
errc <- hs.ListenAndServeTLS("", "")
}()
}
log.Fatal(<-errc)
@@ -127,8 +150,9 @@ func tailscaleIP(who *apitype.WhoIsResponse) string {
func root(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil && *httpsAddr != "" {
host := r.Host
if strings.Contains(r.Host, "100.101.102.103") {
host = "hello.ipn.dev"
if strings.Contains(r.Host, "100.101.102.103") ||
strings.Contains(r.Host, "hello.ipn.dev") {
host = "hello.ts.net"
}
http.Redirect(w, r, "https://"+host, http.StatusFound)
return
@@ -137,6 +161,10 @@ func root(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusFound)
return
}
if r.TLS != nil && *httpsAddr != "" && strings.Contains(r.Host, "hello.ipn.dev") {
http.Redirect(w, r, "https://hello.ts.net", http.StatusFound)
return
}
tmpl, err := getTmpl()
if err != nil {
w.Header().Set("Content-Type", "text/plain")
@@ -168,7 +196,7 @@ func root(w http.ResponseWriter, r *http.Request) {
LoginName: who.UserProfile.LoginName,
ProfilePicURL: who.UserProfile.ProfilePicURL,
MachineName: firstLabel(who.Node.ComputedName),
MachineOS: who.Node.Hostinfo.OS,
MachineOS: who.Node.Hostinfo.OS(),
IP: tailscaleIP(who),
}
}
@@ -178,8 +206,6 @@ func root(w http.ResponseWriter, r *http.Request) {
// firstLabel s up until the first period, if any.
func firstLabel(s string) string {
if i := strings.Index(s, "."); i != -1 {
return s[:i]
}
s, _, _ = strings.Cut(s, ".")
return s
}

46
cmd/printdep/printdep.go Normal file
View File

@@ -0,0 +1,46 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The printdep command is a build system tool for printing out information
// about dependencies.
package main
import (
"flag"
"fmt"
"log"
"runtime"
"strings"
ts "tailscale.com"
)
var (
goToolchain = flag.Bool("go", false, "print the supported Go toolchain git hash (a github.com/tailscale/go commit)")
goToolchainURL = flag.Bool("go-url", false, "print the URL to the tarball of the Tailscale Go toolchain")
)
func main() {
flag.Parse()
if *goToolchain {
fmt.Println(strings.TrimSpace(ts.GoToolchainRev))
}
if *goToolchainURL {
var suffix string
switch runtime.GOARCH {
case "amd64":
// None
case "arm64":
suffix = "-" + runtime.GOARCH
default:
log.Fatalf("unsupported GOARCH %q", runtime.GOARCH)
}
switch runtime.GOOS {
case "linux", "darwin":
default:
log.Fatalf("unsupported GOOS %q", runtime.GOOS)
}
fmt.Printf("https://github.com/tailscale/go/releases/download/build-%s/%s%s.tar.gz\n", strings.TrimSpace(ts.GoToolchainRev), runtime.GOOS, suffix)
}
}

231
cmd/tailscale/cli/admin.go Normal file
View File

@@ -0,0 +1,231 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Admin commands.
package cli
import (
"bytes"
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/client/tailscale"
)
var adminCmd = &ffcli.Command{
Name: "admin",
Exec: runAdmin,
LongHelp: `"tailscale admin" contains admin commands to manage a Tailscale network.`,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("admin")
fs.StringVar(&adminArgs.apiBase, "api-server", "https://api.tailscale.com", "which Tailscale server instance to use. Ignored when --token-file is empty.")
fs.StringVar(&adminArgs.tokenFile, "token-file", "", "if non-empty, filename containing API token to use. If empty, authentication is done via the active Tailscale control plane connection.")
fs.StringVar(&adminArgs.tailnet, "tailnet", "", "Tailnet to query or edit. Required if token-file is used. Must be blank if token-file is blank, in which case the tailnet used is the same as the active tailnet.")
return fs
})(),
Subcommands: []*ffcli.Command{
newTailnetACLGetCmd(),
newTailnetDeviceListCmd(),
newTailnetKeyListCmd(),
},
}
var adminArgs struct {
tokenFile string
tailnet string
apiBase string
}
func runAdmin(ctx context.Context, args []string) error {
if len(args) > 0 {
return errors.New("unknown command; see 'tailscale admin --help'")
}
return errors.New("see 'tailscale admin --help'")
}
type adminClient struct {
apiBase string // e.g. "https://api.tailscale.com"
token string // non-empty if using token-based auth
hc *http.Client
tailnet string // always non-empty
}
func getAdminHTTPClient() (*adminClient, error) {
tokenFile := adminArgs.tokenFile
tailnet := adminArgs.tailnet
apiBase := adminArgs.apiBase
if (tokenFile != "") != (tailnet != "") {
return nil, errors.New("--token-file and --tailnet must both be blank or both be specified")
}
if tailnet == "" {
st, err := tailscale.StatusWithoutPeers(context.Background())
if err != nil {
return nil, err
}
if st.BackendState != "Running" {
return nil, fmt.Errorf("Tailscale must be running; currently in state %q", st.BackendState)
}
if st.CurrentTailnet == nil {
return nil, fmt.Errorf("no CurrentTailnet in status")
}
tailnet = st.CurrentTailnet.Name
// TODO(bradfitz): put apiBase in *ipnstate.TailnetStatus? update apiBase here?
}
ac := &adminClient{
tailnet: tailnet,
apiBase: apiBase,
}
if tokenFile != "" {
v, err := os.ReadFile(tokenFile)
if err != nil {
return nil, err
}
token := strings.TrimSpace(string(v))
if token == "" || strings.Contains(token, "\n") {
return nil, fmt.Errorf("expect exactly 1 line in API token file %v", tokenFile)
}
ac.token = token
ac.hc = http.DefaultClient
} else {
// Otherwise, proxy via the local tailscaled and use its identity.
ac.hc = &http.Client{Transport: apiViaTailscaledTransport{}}
ac.apiBase = "http://local-tailscaled.sock"
}
return ac, nil
}
func newTailnetDeviceListCmd() *ffcli.Command {
var fields string
const sub = "tailnet-device-list"
fs := newFlagSet(sub)
fs.StringVar(&fields, "fields", "default", "comma-separated fields to include in response or 'default', 'all'")
return &ffcli.Command{
Name: sub,
ShortHelp: "list devices",
FlagSet: fs,
Exec: func(ctx context.Context, args []string) error {
ac, err := getAdminHTTPClient()
if err != nil {
return err
}
q := url.Values{"fields": []string{fields}}
return writeResJSON(ac.hc.Get(ac.apiBase + "/api/v2/tailnet/" + ac.tailnet + "/devices?" + q.Encode()))
},
}
}
func newTailnetKeyListCmd() *ffcli.Command {
const sub = "tailnet-key-list"
return &ffcli.Command{
Name: sub,
ShortHelp: "list keys or specific key (with keyID as argument)",
Exec: func(ctx context.Context, args []string) error {
var suf string
if len(args) == 1 {
suf = "/" + args[0]
} else if len(args) > 1 {
return errors.New("too many arguments")
}
ac, err := getAdminHTTPClient()
if err != nil {
return err
}
return writeResJSON(ac.hc.Get(ac.apiBase + "/api/v2/tailnet/" + ac.tailnet + "/keys" + suf))
},
}
}
func newTailnetACLGetCmd() *ffcli.Command {
var asJSON bool // true is JSON, false is HuJSON
const sub = "tailnet-acl-get"
fs := newFlagSet(sub)
fs.BoolVar(&asJSON, "json", false, "if true, return ACL is JSON format. The default of false means to use the original HuJSON JSON superset form that allows comments and trailing commas.")
return &ffcli.Command{
Name: sub,
ShortHelp: "list Tailnet ACL/config policy",
FlagSet: fs,
Exec: func(ctx context.Context, args []string) error {
ac, err := getAdminHTTPClient()
if err != nil {
return err
}
req, err := http.NewRequest("GET", ac.apiBase+"/api/v2/tailnet/"+ac.tailnet+"/acl", nil)
if err != nil {
return err
}
if asJSON {
req.Header.Set("Accept", "application/json")
}
res, err := ac.hc.Do(req)
if err != nil {
return err
}
if asJSON {
return writeResJSON(res, err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, _ := io.ReadAll(res.Body)
return fmt.Errorf("%v: %s", res.Status, body)
}
all, err := io.ReadAll(res.Body)
if err != nil {
return err
}
var buf bytes.Buffer
buf.Write(all)
ensureTrailingNewline(&buf)
os.Stdout.Write(buf.Bytes())
return nil
},
}
}
// apiViaTailscaledTransport is an http.RoundTripper that makes
// Tailscale API HTTP requests via the localapi to tailscaled,
// which then forwards them on over Noise.
type apiViaTailscaledTransport struct{}
func (apiViaTailscaledTransport) RoundTrip(r *http.Request) (*http.Response, error) {
return tailscale.DoLocalRequest(r)
}
func ensureTrailingNewline(buf *bytes.Buffer) {
if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] != '\n' {
buf.WriteByte('\n')
}
}
func writeResJSON(res *http.Response, err error) error {
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, _ := io.ReadAll(res.Body)
return fmt.Errorf("%v: %s", res.Status, body)
}
all, err := io.ReadAll(res.Body)
if err != nil {
return err
}
var buf bytes.Buffer
if err := json.Indent(&buf, all, "", "\t"); err != nil {
return err
}
ensureTrailingNewline(&buf)
os.Stdout.Write(buf.Bytes())
return nil
}

View File

@@ -13,7 +13,6 @@ import (
"log"
"net/http"
"os"
"runtime"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
@@ -80,7 +79,7 @@ func runCert(ctx context.Context, args []string) error {
}
domain := args[0]
printf := func(format string, a ...interface{}) {
printf := func(format string, a ...any) {
printf(format, a...)
}
if certArgs.certFile == "-" || certArgs.keyFile == "-" {
@@ -92,9 +91,6 @@ func runCert(ctx context.Context, args []string) error {
certArgs.keyFile = domain + ".key"
}
certPEM, keyPEM, err := tailscale.CertPair(ctx, domain)
if tailscale.IsAccessDeniedError(err) && os.Getuid() != 0 && runtime.GOOS != "windows" {
return fmt.Errorf("%v\n\nUse 'sudo tailscale cert' or 'tailscale up --operator=$USER' to not require root.", err)
}
if err != nil {
return err
}
@@ -108,7 +104,7 @@ func runCert(ctx context.Context, args []string) error {
if version.IsMacSysExt() {
dir = "io.tailscale.ipn.macsys"
}
printf("Warning: the macOS CLI runs in a sandbox; this binary's filesystem writes go to $HOME/Library/Containers/%s\n", dir)
printf("Warning: the macOS CLI runs in a sandbox; this binary's filesystem writes go to $HOME/Library/Containers/%s/Data\n", dir)
}
if certArgs.certFile != "" {
certChanged, err := writeIfChanged(certArgs.certFile, certPEM, 0644)

View File

@@ -29,12 +29,13 @@ import (
"tailscale.com/paths"
"tailscale.com/safesocket"
"tailscale.com/syncs"
"tailscale.com/version/distro"
)
var Stderr io.Writer = os.Stderr
var Stdout io.Writer = os.Stdout
func printf(format string, a ...interface{}) {
func printf(format string, a ...any) {
fmt.Fprintf(Stdout, format, a...)
}
@@ -43,7 +44,7 @@ func printf(format string, a ...interface{}) {
//
// It's not named println because that looks like the Go built-in
// which goes to stderr and formats slightly differently.
func outln(a ...interface{}) {
func outln(a ...any) {
fmt.Fprintln(Stdout, a...)
}
@@ -103,11 +104,43 @@ func newFlagSet(name string) *flag.FlagSet {
return fs
}
// CleanUpArgs rewrites command line arguments for simplicity and backwards compatibility.
// In particular, it rewrites --authkey to --auth-key.
func CleanUpArgs(args []string) []string {
out := make([]string, 0, len(args))
for _, arg := range args {
// Rewrite --authkey to --auth-key, and --authkey=x to --auth-key=x,
// and the same for the -authkey variant.
switch {
case arg == "--authkey", arg == "-authkey":
arg = "--auth-key"
case strings.HasPrefix(arg, "--authkey="), strings.HasPrefix(arg, "-authkey="):
arg = strings.TrimLeft(arg, "-")
arg = strings.TrimPrefix(arg, "authkey=")
arg = "--auth-key=" + arg
}
out = append(out, arg)
}
return out
}
// Run runs the CLI. The args do not include the binary name.
func Run(args []string) error {
func Run(args []string) (err error) {
if len(args) == 1 && (args[0] == "-V" || args[0] == "--version") {
args = []string{"version"}
}
if runtime.GOOS == "linux" && distro.Get() == distro.Gokrazy &&
os.Getenv("GOKRAZY_FIRST_START") == "1" {
defer func() {
// Exit with 125 otherwise the CLI binary is restarted
// forever in a loop by the Gokrazy process supervisor.
// See https://gokrazy.org/userguide/process-interface/
if err != nil {
log.Println(err)
}
os.Exit(125)
}()
}
var warnOnce sync.Once
tailscale.SetVersionMismatchHandler(func(clientVer, serverVer string) {
@@ -142,6 +175,7 @@ change in the future.
fileCmd,
bugReportCmd,
certCmd,
adminCmd,
},
FlagSet: rootfs,
Exec: func(context.Context, []string) error { return flag.ErrHelp },
@@ -155,6 +189,9 @@ change in the future.
if strSliceContains(args, "debug") {
rootCmd.Subcommands = append(rootCmd.Subcommands, debugCmd)
}
if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
rootCmd.Subcommands = append(rootCmd.Subcommands, configureHostCmd)
}
if err := rootCmd.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
@@ -164,15 +201,23 @@ change in the future.
}
tailscale.TailscaledSocket = rootArgs.socket
rootfs.Visit(func(f *flag.Flag) {
if f.Name == "socket" {
tailscale.TailscaledSocketSetExplicitly = true
}
})
err := rootCmd.Run(context.Background())
err = rootCmd.Run(context.Background())
if tailscale.IsAccessDeniedError(err) && os.Getuid() != 0 && runtime.GOOS != "windows" {
return fmt.Errorf("%v\n\nUse 'sudo tailscale %s' or 'tailscale up --operator=$USER' to not require root.", err, strings.Join(args, " "))
}
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
func fatalf(format string, a ...interface{}) {
func fatalf(format string, a ...any) {
if Fatalf != nil {
Fatalf(format, a...)
return
@@ -182,7 +227,7 @@ func fatalf(format string, a ...interface{}) {
}
// Fatalf, if non-nil, is used instead of log.Fatalf.
var Fatalf func(format string, a ...interface{})
var Fatalf func(format string, a ...any)
var rootArgs struct {
socket string
@@ -191,7 +236,8 @@ var rootArgs struct {
var gotSignal syncs.AtomicBool
func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context, context.CancelFunc) {
c, err := safesocket.Connect(rootArgs.socket, safesocket.WindowsLocalPort)
s := safesocket.DefaultConnectionStrategy(rootArgs.socket)
c, err := safesocket.Connect(s)
if err != nil {
if runtime.GOOS != "windows" && rootArgs.socket == "" {
fatalf("--socket cannot be empty")

View File

@@ -13,6 +13,7 @@ import (
"strings"
"testing"
qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp"
"inet.af/netaddr"
"tailscale.com/ipn"
@@ -20,6 +21,7 @@ import (
"tailscale.com/tstest"
"tailscale.com/types/persist"
"tailscale.com/types/preftype"
"tailscale.com/version/distro"
)
// geese is a collection of gooses. It need not be complete.
@@ -57,6 +59,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
curExitNodeIP netaddr.IP
curUser string // os.Getenv("USER") on the client side
goos string // empty means "linux"
distro distro.Distro
want string
}{
@@ -313,6 +316,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
ControlURL: ipn.DefaultControlURL,
AllowSingleHosts: true,
CorpDNS: true,
RouteAll: true,
// And assume this no-op accidental pre-1.8 value:
NoSNAT: true,
@@ -329,7 +333,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
NetfilterMode: preftype.NetfilterNoDivert, // we never had this bug, but pretend it got set non-zero on Windows somehow
},
goos: "windows",
goos: "openbsd",
want: "", // not an error
},
{
@@ -376,7 +380,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
Hostname: "foo",
},
want: accidentalUpPrefix + " --authkey=secretrand --force-reauth=false --reset --hostname=foo",
want: accidentalUpPrefix + " --auth-key=secretrand --force-reauth=false --reset --hostname=foo",
},
{
name: "error_exit_node_omit_with_ip_pref",
@@ -405,6 +409,21 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
},
want: accidentalUpPrefix + " --hostname=foo --exit-node=100.64.5.7",
},
{
name: "error_exit_node_and_allow_lan_omit_with_id_pref", // Isue 3480
flags: []string{"--hostname=foo"},
curExitNodeIP: netaddr.MustParseIP("100.2.3.4"),
curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
ExitNodeAllowLANAccess: true,
ExitNodeID: "some_stable_id",
},
want: accidentalUpPrefix + " --hostname=foo --exit-node-allow-lan-access --exit-node=100.2.3.4",
},
{
name: "ignore_login_server_synonym",
flags: []string{"--login-server=https://controlplane.tailscale.com"},
@@ -427,6 +446,38 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
},
want: accidentalUpPrefix + " --netfilter-mode=off --accept-dns=false",
},
{
// Issue 3176: on Synology, don't require --accept-routes=false because user
// migth've had old an install, and we don't support --accept-routes anyway.
name: "synology_permit_omit_accept_routes",
flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
CorpDNS: true,
AllowSingleHosts: true,
RouteAll: true,
NetfilterMode: preftype.NetfilterOn,
},
goos: "linux",
distro: distro.Synology,
want: "",
},
{
// Same test case as "synology_permit_omit_accept_routes" above, but
// on non-Synology distro.
name: "not_synology_dont_permit_omit_accept_routes",
flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
CorpDNS: true,
AllowSingleHosts: true,
RouteAll: true,
NetfilterMode: preftype.NetfilterOn,
},
goos: "linux",
distro: "", // not Synology
want: accidentalUpPrefix + " --hostname=foo --accept-routes",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -436,7 +487,8 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
}
var upArgs upArgsT
flagSet := newUpFlagSet(goos, &upArgs)
flagSet.Parse(tt.flags)
flags := CleanUpArgs(tt.flags)
flagSet.Parse(flags)
newPrefs, err := prefsFromUpArgs(upArgs, t.Logf, new(ipnstate.Status), goos)
if err != nil {
t.Fatal(err)
@@ -447,6 +499,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
goos: goos,
flagSet: flagSet,
curExitNodeIP: tt.curExitNodeIP,
distro: tt.distro,
}); err != nil {
got = err.Error()
}
@@ -495,6 +548,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
WantRunning: true,
CorpDNS: true,
AllowSingleHosts: true,
RouteAll: true,
NetfilterMode: preftype.NetfilterOn,
},
},
@@ -532,7 +586,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
args: upArgsT{
exitNodeIP: "foo",
},
wantErr: `invalid IP address "foo" for --exit-node: ParseIP("foo"): unable to parse IP`,
wantErr: `invalid value "foo" for --exit-node; must be IP or unique node name`,
},
{
name: "error_exit_node_allow_lan_without_exit_node",
@@ -577,7 +631,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
st: &ipnstate.Status{
TailscaleIPs: []netaddr.IP{netaddr.MustParseIP("100.105.106.107")},
},
wantErr: `cannot use 100.105.106.107 as the exit node as it is a local IP address to this machine, did you mean --advertise-exit-node?`,
wantErr: `cannot use 100.105.106.107 as an exit node as it is a local IP address to this machine; did you mean --advertise-exit-node?`,
},
{
name: "warn_linux_netfilter_nodivert",
@@ -733,6 +787,33 @@ func TestUpdatePrefs(t *testing.T) {
wantSimpleUp: true,
wantJustEditMP: &ipn.MaskedPrefs{WantRunningSet: true},
},
{
name: "just_edit_reset",
flags: []string{"--reset"},
curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
Persist: &persist.Persist{LoginName: "crawshaw.github"},
},
env: upCheckEnv{backendState: "Running"},
wantJustEditMP: &ipn.MaskedPrefs{
AdvertiseRoutesSet: true,
AdvertiseTagsSet: true,
AllowSingleHostsSet: true,
ControlURLSet: true,
CorpDNSSet: true,
ExitNodeAllowLANAccessSet: true,
ExitNodeIDSet: true,
ExitNodeIPSet: true,
HostnameSet: true,
NetfilterModeSet: true,
NoSNATSet: true,
OperatorUserSet: true,
RouteAllSet: true,
RunSSHSet: true,
ShieldsUpSet: true,
WantRunningSet: true,
},
},
{
name: "control_synonym",
flags: []string{},
@@ -778,7 +859,8 @@ func TestUpdatePrefs(t *testing.T) {
tt.env.goos = "linux"
}
tt.env.flagSet = newUpFlagSet(tt.env.goos, &tt.env.upArgs)
tt.env.flagSet.Parse(tt.flags)
flags := CleanUpArgs(tt.flags)
tt.env.flagSet.Parse(flags)
newPrefs, err := prefsFromUpArgs(tt.env.upArgs, t.Logf, new(ipnstate.Status), tt.env.goos)
if err != nil {
@@ -797,12 +879,45 @@ func TestUpdatePrefs(t *testing.T) {
if simpleUp != tt.wantSimpleUp {
t.Fatalf("simpleUp=%v, want %v", simpleUp, tt.wantSimpleUp)
}
var oldEditPrefs ipn.Prefs
if justEditMP != nil {
oldEditPrefs = justEditMP.Prefs
justEditMP.Prefs = ipn.Prefs{} // uninteresting
}
if !reflect.DeepEqual(justEditMP, tt.wantJustEditMP) {
t.Fatalf("justEditMP: %v", cmp.Diff(justEditMP, tt.wantJustEditMP))
t.Logf("justEditMP != wantJustEditMP; following diff omits the Prefs field, which was %+v", oldEditPrefs)
t.Fatalf("justEditMP: %v\n\n: ", cmp.Diff(justEditMP, tt.wantJustEditMP, cmpIP))
}
})
}
}
var cmpIP = cmp.Comparer(func(a, b netaddr.IP) bool {
return a == b
})
func TestCleanUpArgs(t *testing.T) {
c := qt.New(t)
tests := []struct {
in []string
want []string
}{
{in: []string{"something"}, want: []string{"something"}},
{in: []string{}, want: []string{}},
{in: []string{"--authkey=0"}, want: []string{"--auth-key=0"}},
{in: []string{"a", "--authkey=1", "b"}, want: []string{"a", "--auth-key=1", "b"}},
{in: []string{"a", "--auth-key=2", "b"}, want: []string{"a", "--auth-key=2", "b"}},
{in: []string{"a", "-authkey=3", "b"}, want: []string{"a", "--auth-key=3", "b"}},
{in: []string{"a", "-auth-key=4", "b"}, want: []string{"a", "-auth-key=4", "b"}},
{in: []string{"a", "--authkey", "5", "b"}, want: []string{"a", "--auth-key", "5", "b"}},
{in: []string{"a", "-authkey", "6", "b"}, want: []string{"a", "--auth-key", "6", "b"}},
{in: []string{"a", "authkey", "7", "b"}, want: []string{"a", "authkey", "7", "b"}},
{in: []string{"--authkeyexpiry", "8"}, want: []string{"--authkeyexpiry", "8"}},
{in: []string{"--auth-key-expiry", "9"}, want: []string{"--auth-key-expiry", "9"}},
}
for _, tt := range tests {
got := CleanUpArgs(tt.in)
c.Assert(got, qt.DeepEquals, tt.want)
}
}

View File

@@ -0,0 +1,85 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cli
import (
"context"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/hostinfo"
"tailscale.com/version/distro"
)
var configureHostCmd = &ffcli.Command{
Name: "configure-host",
Exec: runConfigureHost,
ShortHelp: "Configure Synology to enable more Tailscale features",
LongHelp: strings.TrimSpace(`
The 'configure-host' command is intended to run at boot as root
to create the /dev/net/tun device and give the tailscaled binary
permission to use it.
See: https://tailscale.com/kb/1152/synology-outbound/
`),
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("configure-host")
return fs
})(),
}
var configureHostArgs struct{}
func runConfigureHost(ctx context.Context, args []string) error {
if len(args) > 0 {
return errors.New("unknown arguments")
}
if runtime.GOOS != "linux" || distro.Get() != distro.Synology {
return errors.New("only implemented on Synology")
}
if uid := os.Getuid(); uid != 0 {
return fmt.Errorf("must be run as root, not %q (%v)", os.Getenv("USER"), uid)
}
osVer := hostinfo.GetOSVersion()
isDSM6 := strings.HasPrefix(osVer, "Synology 6")
isDSM7 := strings.HasPrefix(osVer, "Synology 7")
if !isDSM6 && !isDSM7 {
return fmt.Errorf("unsupported DSM version %q", osVer)
}
if _, err := os.Stat("/dev/net/tun"); os.IsNotExist(err) {
if err := os.MkdirAll("/dev/net", 0755); err != nil {
return fmt.Errorf("creating /dev/net: %v", err)
}
if out, err := exec.Command("/bin/mknod", "/dev/net/tun", "c", "10", "200").CombinedOutput(); err != nil {
return fmt.Errorf("creating /dev/net/tun: %v, %s", err, out)
}
}
if err := os.Chmod("/dev/net/tun", 0666); err != nil {
return err
}
if isDSM6 {
fmt.Printf("/dev/net/tun exists and has permissions 0666. Skipping setcap on DSM6.\n")
return nil
}
const daemonBin = "/var/packages/Tailscale/target/bin/tailscaled"
if _, err := os.Stat(daemonBin); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("tailscaled binary not found at %s. Is the Tailscale *.spk package installed?", daemonBin)
}
return err
}
if out, err := exec.Command("/bin/setcap", "cap_net_admin,cap_net_raw+eip", daemonBin).CombinedOutput(); err != nil {
return fmt.Errorf("setcap: %v, %s", err, out)
}
fmt.Printf("Done. To restart Tailscale to use the new permissions, run:\n\n sudo synosystemctl restart pkgctl-Tailscale.service\n\n")
return nil
}

View File

@@ -22,6 +22,7 @@ import (
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/client/tailscale"
"tailscale.com/hostinfo"
"tailscale.com/ipn"
"tailscale.com/paths"
"tailscale.com/safesocket"
@@ -65,11 +66,26 @@ var debugCmd = &ffcli.Command{
Exec: runEnv,
ShortHelp: "print cmd/tailscale environment",
},
{
Name: "hostinfo",
Exec: runHostinfo,
ShortHelp: "print hostinfo",
},
{
Name: "local-creds",
Exec: runLocalCreds,
ShortHelp: "print how to access Tailscale local API",
},
{
Name: "restun",
Exec: localAPIAction("restun"),
ShortHelp: "force a magicsock restun",
},
{
Name: "rebind",
Exec: localAPIAction("rebind"),
ShortHelp: "force a magicsock rebind",
},
{
Name: "prefs",
Exec: runPrefs,
@@ -176,7 +192,7 @@ func runDebug(ctx context.Context, args []string) error {
// to subcommands.
return nil
}
return errors.New("see 'tailscale debug --help")
return errors.New("see 'tailscale debug --help'")
}
func runLocalCreds(ctx context.Context, args []string) error {
@@ -244,6 +260,15 @@ func runDERPMap(ctx context.Context, args []string) error {
return nil
}
func localAPIAction(action string) func(context.Context, []string) error {
return func(ctx context.Context, args []string) error {
if len(args) > 0 {
return errors.New("unexpected arguments")
}
return tailscale.DebugAction(ctx, action)
}
}
func runEnv(ctx context.Context, args []string) error {
for _, e := range os.Environ() {
outln(e)
@@ -251,6 +276,13 @@ func runEnv(ctx context.Context, args []string) error {
return nil
}
func runHostinfo(ctx context.Context, args []string) error {
hi := hostinfo.New()
j, _ := json.MarshalIndent(hi, "", " ")
os.Stdout.Write(j)
return nil
}
func runDaemonGoroutines(ctx context.Context, args []string) error {
goroutines, err := tailscale.Goroutines(ctx)
if err != nil {

View File

@@ -25,23 +25,23 @@ func fixTailscaledConnectError(origErr error) error {
if err != nil {
return fmt.Errorf("failed to connect to local Tailscaled process and failed to enumerate processes while looking for it")
}
found := false
var foundProc ps.Process
for _, proc := range procs {
base := filepath.Base(proc.Executable())
if base == "tailscaled" {
found = true
foundProc = proc
break
}
if runtime.GOOS == "darwin" && base == "IPNExtension" {
found = true
foundProc = proc
break
}
if runtime.GOOS == "windows" && strings.EqualFold(base, "tailscaled.exe") {
found = true
foundProc = proc
break
}
}
if !found {
if foundProc == nil {
switch runtime.GOOS {
case "windows":
return fmt.Errorf("failed to connect to local tailscaled process; is the Tailscale service running?")
@@ -52,5 +52,5 @@ func fixTailscaledConnectError(origErr error) error {
}
return fmt.Errorf("failed to connect to local tailscaled process; it doesn't appear to be running")
}
return fmt.Errorf("failed to connect to local tailscaled (which appears to be running). Got error: %w", origErr)
return fmt.Errorf("failed to connect to local tailscaled (which appears to be running as %v, pid %v). Got error: %w", foundProc.Executable(), foundProc.Pid(), origErr)
}

View File

@@ -11,14 +11,12 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"mime"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"unicode/utf8"
@@ -28,8 +26,10 @@ import (
"inet.af/netaddr"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/version"
)
@@ -96,7 +96,7 @@ func runCp(ctx context.Context, args []string) error {
return err
}
peerAPIBase, isOffline, err := discoverPeerAPIBase(ctx, ip)
stableID, isOffline, err := getTargetStableID(ctx, ip)
if err != nil {
return fmt.Errorf("can't send to %s: %v", target, err)
}
@@ -149,37 +149,26 @@ func runCp(ctx context.Context, args []string) error {
name = filepath.Base(fileArg)
}
if slow, _ := strconv.ParseBool(os.Getenv("TS_DEBUG_SLOW_PUSH")); slow {
if envknob.Bool("TS_DEBUG_SLOW_PUSH") {
fileContents = &slowReader{r: fileContents}
}
}
dstURL := peerAPIBase + "/v0/put/" + url.PathEscape(name)
req, err := http.NewRequestWithContext(ctx, "PUT", dstURL, fileContents)
if err != nil {
return err
}
req.ContentLength = contentLength
if cpArgs.verbose {
log.Printf("sending to %v ...", dstURL)
log.Printf("sending %q to %v/%v/%v ...", name, target, ip, stableID)
}
res, err := http.DefaultClient.Do(req)
err := tailscale.PushFile(ctx, stableID, contentLength, name, fileContents)
if err != nil {
return err
}
if res.StatusCode == 200 {
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
continue
if cpArgs.verbose {
log.Printf("sent %q", name)
}
io.Copy(Stdout, res.Body)
res.Body.Close()
return errors.New(res.Status)
}
return nil
}
func discoverPeerAPIBase(ctx context.Context, ipStr string) (base string, isOffline bool, err error) {
func getTargetStableID(ctx context.Context, ipStr string) (id tailcfg.StableNodeID, isOffline bool, err error) {
ip, err := netaddr.ParseIP(ipStr)
if err != nil {
return "", false, err
@@ -195,7 +184,7 @@ func discoverPeerAPIBase(ctx context.Context, ipStr string) (base string, isOffl
continue
}
isOffline = n.Online != nil && !*n.Online
return ft.PeerAPIURL, isOffline, nil
return n.StableID, isOffline, nil
}
}
return "", false, fileTargetErrorDetail(ctx, ip)
@@ -298,22 +287,116 @@ func runCpTargets(ctx context.Context, args []string) error {
return nil
}
// onConflict is a flag.Value for the --conflict flag's three string options.
type onConflict string
const (
skipOnExist onConflict = "skip"
overwriteExisting onConflict = "overwrite" // Overwrite any existing file at the target location
createNumberedFiles onConflict = "rename" // Create an alternately named file in the style of Chrome Downloads
)
func (v *onConflict) String() string { return string(*v) }
func (v *onConflict) Set(s string) error {
if s == "" {
*v = skipOnExist
return nil
}
*v = onConflict(strings.ToLower(s))
if *v != skipOnExist && *v != overwriteExisting && *v != createNumberedFiles {
return fmt.Errorf("%q is not one of (skip|overwrite|rename)", s)
}
return nil
}
var fileGetCmd = &ffcli.Command{
Name: "get",
ShortUsage: "file get [--wait] [--verbose] <target-directory>",
ShortUsage: "file get [--wait] [--verbose] [--conflict=(skip|overwrite|rename)] <target-directory>",
ShortHelp: "Move files out of the Tailscale file inbox",
Exec: runFileGet,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("get")
fs.BoolVar(&getArgs.wait, "wait", false, "wait for a file to arrive if inbox is empty")
fs.BoolVar(&getArgs.verbose, "verbose", false, "verbose output")
fs.Var(&getArgs.conflict, "conflict", `behavior when a conflicting (same-named) file already exists in the target directory.
skip: skip conflicting files: leave them in the taildrop inbox and print an error. get any non-conflicting files
overwrite: overwrite existing file
rename: write to a new number-suffixed filename`)
return fs
})(),
}
var getArgs struct {
wait bool
verbose bool
var getArgs = struct {
wait bool
verbose bool
conflict onConflict
}{conflict: skipOnExist}
func numberedFileName(dir, name string, i int) string {
ext := path.Ext(name)
return filepath.Join(dir, fmt.Sprintf("%s (%d)%s",
strings.TrimSuffix(name, ext),
i, ext))
}
func openFileOrSubstitute(dir, base string, action onConflict) (*os.File, error) {
targetFile := filepath.Join(dir, base)
f, err := os.OpenFile(targetFile, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
if err == nil {
return f, nil
}
// Something went wrong trying to open targetFile as a new file for writing.
switch action {
default:
// This should not happen.
return nil, fmt.Errorf("file issue. how to resolve this conflict? no one knows.")
case skipOnExist:
if _, statErr := os.Stat(targetFile); statErr == nil {
// we can stat a file at that path: so it already exists.
return nil, fmt.Errorf("refusing to overwrite file: %w", err)
}
return nil, fmt.Errorf("failed to write; %w", err)
case overwriteExisting:
// remove the target file and create it anew so we don't fall for an
// attacker who symlinks a known target name to a file he wants changed.
if err = os.Remove(targetFile); err != nil {
return nil, fmt.Errorf("unable to remove target file: %w", err)
}
if f, err = os.OpenFile(targetFile, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644); err != nil {
return nil, fmt.Errorf("unable to overwrite: %w", err)
}
return f, nil
case createNumberedFiles:
// It's possible the target directory or filesystem isn't writable by us,
// not just that the target file(s) already exists. For now, give up after
// a limited number of attempts. In future, maybe distinguish this case
// and follow in the style of https://tinyurl.com/chromium100
maxAttempts := 100
for i := 1; i < maxAttempts; i++ {
if f, err = os.OpenFile(numberedFileName(dir, base, i), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644); err == nil {
return f, nil
}
}
return nil, fmt.Errorf("unable to find a name for writing %v, final attempt: %w", targetFile, err)
}
}
func receiveFile(ctx context.Context, wf apitype.WaitingFile, dir string) (targetFile string, size int64, err error) {
rc, size, err := tailscale.GetWaitingFile(ctx, wf.Name)
if err != nil {
return "", 0, fmt.Errorf("opening inbox file %q: %w", wf.Name, err)
}
f, err := openFileOrSubstitute(dir, wf.Name, getArgs.conflict)
if err != nil {
return "", 0, err
}
_, err = io.Copy(f, rc)
rc.Close()
if err != nil {
return "", 0, fmt.Errorf("failed to write %v: %v", f.Name(), err)
}
return f.Name(), size, f.Close()
}
func runFileGet(ctx context.Context, args []string) error {
@@ -336,53 +419,46 @@ func runFileGet(ctx context.Context, args []string) error {
for {
wfs, err = tailscale.WaitingFiles(ctx)
if err != nil {
return fmt.Errorf("getting WaitingFiles: %v", err)
return fmt.Errorf("getting WaitingFiles: %w", err)
}
if len(wfs) != 0 || !getArgs.wait {
break
}
if getArgs.verbose {
log.Printf("waiting for file...")
printf("waiting for file...")
}
if err := waitForFile(ctx); err != nil {
return err
}
}
var errs []error
deleted := 0
for _, wf := range wfs {
rc, size, err := tailscale.GetWaitingFile(ctx, wf.Name)
writtenFile, size, err := receiveFile(ctx, wf, dir)
if err != nil {
return fmt.Errorf("opening inbox file %q: %v", wf.Name, err)
}
targetFile := filepath.Join(dir, wf.Name)
of, err := os.OpenFile(targetFile, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
if err != nil {
if _, err := os.Stat(targetFile); err == nil {
return fmt.Errorf("refusing to overwrite %v", targetFile)
}
return err
}
_, err = io.Copy(of, rc)
rc.Close()
if err != nil {
return fmt.Errorf("failed to write %v: %v", targetFile, err)
}
if err := of.Close(); err != nil {
return err
errs = append(errs, err)
continue
}
if getArgs.verbose {
log.Printf("wrote %v (%d bytes)", wf.Name, size)
printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
}
if err := tailscale.DeleteWaitingFile(ctx, wf.Name); err != nil {
return fmt.Errorf("deleting %q from inbox: %v", wf.Name, err)
if err = tailscale.DeleteWaitingFile(ctx, wf.Name); err != nil {
errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err))
continue
}
deleted++
}
if getArgs.verbose {
log.Printf("moved %d files", deleted)
printf("moved %d/%d files\n", deleted, len(wfs))
}
return nil
if len(errs) == 0 {
return nil
}
for _, err := range errs[:len(errs)-1] {
outln(err)
}
return errs[len(errs)-1]
}
func wipeInbox(ctx context.Context) error {
@@ -391,7 +467,7 @@ func wipeInbox(ctx context.Context) error {
}
wfs, err := tailscale.WaitingFiles(ctx)
if err != nil {
return fmt.Errorf("getting WaitingFiles: %v", err)
return fmt.Errorf("getting WaitingFiles: %w", err)
}
deleted := 0
for _, wf := range wfs {

View File

@@ -13,13 +13,13 @@ import (
"io/ioutil"
"log"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/client/tailscale"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/net/netcheck"
"tailscale.com/net/portmapper"
@@ -49,7 +49,7 @@ var netcheckArgs struct {
func runNetcheck(ctx context.Context, args []string) error {
c := &netcheck.Client{
UDPBindAddr: os.Getenv("TS_DEBUG_NETCHECK_UDP_BIND"),
UDPBindAddr: envknob.String("TS_DEBUG_NETCHECK_UDP_BIND"),
PortMapper: portmapper.NewClient(logger.WithPrefix(log.Printf, "portmap: "), nil),
}
if netcheckArgs.verbose {

View File

@@ -11,6 +11,7 @@ import (
"fmt"
"log"
"net"
"os"
"strings"
"time"
@@ -64,6 +65,16 @@ var pingArgs struct {
}
func runPing(ctx context.Context, args []string) error {
st, err := tailscale.Status(ctx)
if err != nil {
return fixTailscaledConnectError(err)
}
description, ok := isRunningOrStarting(st)
if !ok {
printf("%s\n", description)
os.Exit(1)
}
c, bc, ctx, cancel := connect(ctx)
defer cancel()

View File

@@ -29,7 +29,22 @@ var statusCmd = &ffcli.Command{
Name: "status",
ShortUsage: "status [--active] [--web] [--json]",
ShortHelp: "Show state of tailscaled and its connections",
Exec: runStatus,
LongHelp: strings.TrimSpace(`
JSON FORMAT
Warning: this format has changed between releases and might change more
in the future.
For a description of the fields, see the "type Status" declaration at:
https://github.com/tailscale/tailscale/blob/main/ipn/ipnstate/ipnstate.go
(and be sure to select branch/tag that corresponds to the version
of Tailscale you're running)
`),
Exec: runStatus,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("status")
fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
@@ -106,24 +121,10 @@ func runStatus(ctx context.Context, args []string) error {
return err
}
switch st.BackendState {
default:
fmt.Fprintf(Stderr, "unexpected state: %s\n", st.BackendState)
description, ok := isRunningOrStarting(st)
if !ok {
outln(description)
os.Exit(1)
case ipn.Stopped.String():
outln("Tailscale is stopped.")
os.Exit(1)
case ipn.NeedsLogin.String():
outln("Logged out.")
if st.AuthURL != "" {
printf("\nLog in at: %s\n", st.AuthURL)
}
os.Exit(1)
case ipn.NeedsMachineAuth.String():
outln("Machine is not yet authorized by tailnet admin.")
os.Exit(1)
case ipn.Running.String(), ipn.Starting.String():
// Run below.
}
if len(st.Health) > 0 {
@@ -135,7 +136,7 @@ func runStatus(ctx context.Context, args []string) error {
}
var buf bytes.Buffer
f := func(format string, a ...interface{}) { fmt.Fprintf(&buf, format, a...) }
f := func(format string, a ...any) { fmt.Fprintf(&buf, format, a...) }
printPS := func(ps *ipnstate.PeerStatus) {
f("%-15s %-20s %-12s %-7s ",
firstIPString(ps.TailscaleIPs),
@@ -145,11 +146,19 @@ func runStatus(ctx context.Context, args []string) error {
)
relay := ps.Relay
anyTraffic := ps.TxBytes != 0 || ps.RxBytes != 0
var offline string
if !ps.Online {
offline = "; offline"
}
if !ps.Active {
if ps.ExitNode {
f("idle; exit node")
f("idle; exit node" + offline)
} else if ps.ExitNodeOption {
f("idle; offers exit node" + offline)
} else if anyTraffic {
f("idle")
f("idle" + offline)
} else if !ps.Online {
f("offline")
} else {
f("-")
}
@@ -157,12 +166,17 @@ func runStatus(ctx context.Context, args []string) error {
f("active; ")
if ps.ExitNode {
f("exit node; ")
} else if ps.ExitNodeOption {
f("offers exit node; ")
}
if relay != "" && ps.CurAddr == "" {
f("relay %q", relay)
} else if ps.CurAddr != "" {
f("direct %s", ps.CurAddr)
}
if !ps.Online {
f("; offline")
}
}
if anyTraffic {
f(", tx %d rx %d", ps.TxBytes, ps.RxBytes)
@@ -194,6 +208,27 @@ func runStatus(ctx context.Context, args []string) error {
return nil
}
// isRunningOrStarting reports whether st is in state Running or Starting.
// It also returns a description of the status suitable to display to a user.
func isRunningOrStarting(st *ipnstate.Status) (description string, ok bool) {
switch st.BackendState {
default:
return fmt.Sprintf("unexpected state: %s", st.BackendState), false
case ipn.Stopped.String():
return "Tailscale is stopped.", false
case ipn.NeedsLogin.String():
s := "Logged out."
if st.AuthURL != "" {
s += fmt.Sprintf("\nLog in at: %s", st.AuthURL)
}
return s, false
case ipn.NeedsMachineAuth.String():
return "Machine is not yet authorized by tailnet admin.", false
case ipn.Running.String(), ipn.Starting.String():
return st.BackendState, true
}
}
func dnsOrQuoteHostname(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
baseName := dnsname.TrimSuffix(ps.DNSName, st.MagicDNSSuffix)
if baseName != "" {

View File

@@ -6,6 +6,8 @@ package cli
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"flag"
"fmt"
@@ -22,12 +24,14 @@ import (
qrcode "github.com/skip2/go-qrcode"
"inet.af/netaddr"
"tailscale.com/client/tailscale"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/preftype"
"tailscale.com/version"
"tailscale.com/version/distro"
)
@@ -46,8 +50,10 @@ down").
If flags are specified, the flags must be the complete set of desired
settings. An error is returned if any setting would be changed as a
result of an unspecified flag's default value, unless the --reset
flag is also used.
result of an unspecified flag's default value, unless the --reset flag
is also used. (The flags --authkey, --force-reauth, and --qr are not
considered settings that need to be re-specified when modifying
settings.)
`),
FlagSet: upFlagSet,
Exec: runUp,
@@ -60,24 +66,43 @@ func effectiveGOOS() string {
return runtime.GOOS
}
// acceptRouteDefault returns the CLI's default value of --accept-routes as
// a function of the platform it's running on.
func acceptRouteDefault(goos string) bool {
switch goos {
case "windows":
return true
case "darwin":
return version.IsSandboxedMacOS()
default:
return false
}
}
var upFlagSet = newUpFlagSet(effectiveGOOS(), &upArgs)
func inTest() bool { return flag.Lookup("test.v") != nil }
func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
upf := newFlagSet("up")
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
upf.BoolVar(&upArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication")
upf.BoolVar(&upArgs.reset, "reset", false, "reset unspecified settings to their default values")
upf.StringVar(&upArgs.server, "login-server", ipn.DefaultControlURL, "base URL of control server")
upf.BoolVar(&upArgs.acceptRoutes, "accept-routes", false, "accept routes advertised by other Tailscale nodes")
upf.BoolVar(&upArgs.acceptRoutes, "accept-routes", acceptRouteDefault(goos), "accept routes advertised by other Tailscale nodes")
upf.BoolVar(&upArgs.acceptDNS, "accept-dns", true, "accept DNS configuration from the admin panel")
upf.BoolVar(&upArgs.singleRoutes, "host-routes", true, "install host routes to other Tailscale nodes")
upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale IP of the exit node for internet traffic, or empty string to not use an exit node")
upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale exit node (IP or base name) for internet traffic, or empty string to not use an exit node")
upf.BoolVar(&upArgs.exitNodeAllowLANAccess, "exit-node-allow-lan-access", false, "Allow direct access to the local network when routing traffic via an exit node")
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
if envknob.UseWIPCode() || inTest() {
upf.BoolVar(&upArgs.runSSH, "ssh", false, "run an SSH server, permitting access per tailnet admin's declared policy")
}
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "comma-separated ACL tags to request; each must start with \"tag:\" (e.g. \"tag:eng,tag:montreal,tag:ssh\")")
upf.StringVar(&upArgs.authKeyOrFile, "authkey", "", `node authorization key; if it begins with "file:", then it's a path to a file containing the authkey`)
upf.StringVar(&upArgs.authKeyOrFile, "auth-key", "", `node authorization key; if it begins with "file:", then it's a path to a file containing the authkey`)
upf.StringVar(&upArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes")
upf.BoolVar(&upArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
@@ -111,6 +136,7 @@ type upArgsT struct {
exitNodeIP string
exitNodeAllowLANAccess bool
shieldsUp bool
runSSH bool
forceReauth bool
forceDaemon bool
advertiseRoutes string
@@ -121,6 +147,7 @@ type upArgsT struct {
authKeyOrFile string // "secret" or "file:/path/to/secret"
hostname string
opUser string
json bool
}
func (a upArgsT) getAuthKey() (string, error) {
@@ -138,7 +165,34 @@ func (a upArgsT) getAuthKey() (string, error) {
var upArgs upArgsT
func warnf(format string, args ...interface{}) {
// Fields output when `tailscale up --json` is used. Two JSON blocks will be output.
//
// When "tailscale up" is run it first outputs a block with AuthURL and QR populated,
// providing the link for where to authenticate this client. BackendState would be
// valid but boring, as it will almost certainly be "NeedsLogin". Error would be
// populated if something goes badly wrong.
//
// When the client is authenticated by having someone visit the AuthURL, a second
// JSON block will be output. The AuthURL and QR fields will not be present, the
// BackendState and Error fields will give the result of the authentication.
// Ex:
// {
// "AuthURL": "https://login.tailscale.com/a/0123456789abcdef",
// "QR": "data:image/png;base64,0123...cdef"
// "BackendState": "NeedsLogin"
// }
// {
// "BackendState": "Running"
// }
//
type upOutputJSON struct {
AuthURL string `json:",omitempty"` // Authentication URL of the form https://login.tailscale.com/a/0123456789
QR string `json:",omitempty"` // a DataURL (base64) PNG of a QR code AuthURL
BackendState string `json:",omitempty"` // name of state like Running or NeedsMachineAuth
Error string `json:",omitempty"` // description of an error
}
func warnf(format string, args ...any) {
printf("Warning: "+format+"\n", args...)
}
@@ -202,25 +256,10 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
return nil, err
}
var exitNodeIP netaddr.IP
if upArgs.exitNodeIP != "" {
var err error
exitNodeIP, err = netaddr.ParseIP(upArgs.exitNodeIP)
if err != nil {
return nil, fmt.Errorf("invalid IP address %q for --exit-node: %v", upArgs.exitNodeIP, err)
}
} else if upArgs.exitNodeAllowLANAccess {
if upArgs.exitNodeIP == "" && upArgs.exitNodeAllowLANAccess {
return nil, fmt.Errorf("--exit-node-allow-lan-access can only be used with --exit-node")
}
if upArgs.exitNodeIP != "" {
for _, ip := range st.TailscaleIPs {
if exitNodeIP == ip {
return nil, fmt.Errorf("cannot use %s as the exit node as it is a local IP address to this machine, did you mean --advertise-exit-node?", upArgs.exitNodeIP)
}
}
}
var tags []string
if upArgs.advertiseTags != "" {
tags = strings.Split(upArgs.advertiseTags, ",")
@@ -240,11 +279,22 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
prefs.ControlURL = upArgs.server
prefs.WantRunning = true
prefs.RouteAll = upArgs.acceptRoutes
prefs.ExitNodeIP = exitNodeIP
if upArgs.exitNodeIP != "" {
if err := prefs.SetExitNodeIP(upArgs.exitNodeIP, st); err != nil {
var e ipn.ExitNodeLocalIPError
if errors.As(err, &e) {
return nil, fmt.Errorf("%w; did you mean --advertise-exit-node?", err)
}
return nil, err
}
}
prefs.ExitNodeAllowLANAccess = upArgs.exitNodeAllowLANAccess
prefs.CorpDNS = upArgs.acceptDNS
prefs.AllowSingleHosts = upArgs.singleRoutes
prefs.ShieldsUp = upArgs.shieldsUp
prefs.RunSSH = upArgs.runSSH
prefs.AdvertiseRoutes = routes
prefs.AdvertiseTags = tags
prefs.Hostname = upArgs.hostname
@@ -272,7 +322,8 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
return prefs, nil
}
// updatePrefs updates prefs based on curPrefs
// updatePrefs returns how to edit preferences based on the
// flag-provided 'prefs' and the currently active 'curPrefs'.
//
// It returns a non-nil justEditMP if we're already running and none of
// the flags require a restart, so we can just do an EditPrefs call and
@@ -306,15 +357,19 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
justEdit := env.backendState == ipn.Running.String() &&
!env.upArgs.forceReauth &&
!env.upArgs.reset &&
env.upArgs.authKeyOrFile == "" &&
!controlURLChanged &&
!tagsChanged
if justEdit {
justEditMP = new(ipn.MaskedPrefs)
justEditMP.WantRunningSet = true
justEditMP.Prefs = *prefs
env.flagSet.Visit(func(f *flag.Flag) {
visitFlags := env.flagSet.Visit
if env.upArgs.reset {
visitFlags = env.flagSet.VisitAll
}
visitFlags(func(f *flag.Flag) {
updateMaskedPrefsFromUpFlag(justEditMP, f.Name)
})
}
@@ -380,11 +435,12 @@ func runUp(ctx context.Context, args []string) error {
env := upCheckEnv{
goos: effectiveGOOS(),
distro: distro.Get(),
user: os.Getenv("USER"),
flagSet: upFlagSet,
upArgs: upArgs,
backendState: st.BackendState,
curExitNodeIP: exitNodeIP(prefs, st),
curExitNodeIP: exitNodeIP(curPrefs, st),
}
simpleUp, justEditMP, err := updatePrefs(prefs, curPrefs, env)
if err != nil {
@@ -405,7 +461,7 @@ func runUp(ctx context.Context, args []string) error {
pumpErr := make(chan error, 1)
go func() { pumpErr <- pump(pumpCtx, bc, c) }()
printed := !simpleUp
var printed bool // whether we've yet printed anything to stdout or stderr
var loginOnce sync.Once
startLoginInteractive := func() { loginOnce.Do(func() { bc.StartLoginInteractive() }) }
@@ -431,14 +487,19 @@ func runUp(ctx context.Context, args []string) error {
if s := n.State; s != nil {
switch *s {
case ipn.NeedsLogin:
printed = true
startLoginInteractive()
case ipn.NeedsMachineAuth:
printed = true
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
if env.upArgs.json {
printUpDoneJSON(ipn.NeedsMachineAuth, "")
} else {
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
}
case ipn.Running:
// Done full authentication process
if printed {
if env.upArgs.json {
printUpDoneJSON(ipn.Running, "")
} else if printed {
// Only need to print an update if we printed the "please click" message earlier.
fmt.Fprintf(Stderr, "Success.\n")
}
@@ -451,15 +512,33 @@ func runUp(ctx context.Context, args []string) error {
}
if url := n.BrowseToURL; url != nil && printAuthURL(*url) {
printed = true
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
if upArgs.qr {
if upArgs.json {
js := &upOutputJSON{AuthURL: *url, BackendState: st.BackendState}
q, err := qrcode.New(*url, qrcode.Medium)
if err != nil {
log.Printf("QR code error: %v", err)
} else {
fmt.Fprintf(Stderr, "%s\n", q.ToString(false))
if err == nil {
png, err := q.PNG(128)
if err == nil {
js.QR = "data:image/png;base64," + base64.StdEncoding.EncodeToString(png)
}
}
data, err := json.MarshalIndent(js, "", "\t")
if err != nil {
log.Printf("upOutputJSON marshalling error: %v", err)
} else {
fmt.Println(string(data))
}
} else {
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
if upArgs.qr {
q, err := qrcode.New(*url, qrcode.Medium)
if err != nil {
log.Printf("QR code error: %v", err)
} else {
fmt.Fprintf(Stderr, "%s\n", q.ToString(false))
}
}
}
}
})
@@ -546,6 +625,16 @@ func runUp(ctx context.Context, args []string) error {
}
}
func printUpDoneJSON(state ipn.State, errorString string) {
js := &upOutputJSON{BackendState: state.String(), Error: errorString}
data, err := json.MarshalIndent(js, "", " ")
if err != nil {
log.Printf("printUpDoneJSON marshalling error: %v", err)
} else {
fmt.Println(string(data))
}
}
var (
prefsOfFlag = map[string][]string{} // "exit-node" => ExitNodeIP, ExitNodeID
)
@@ -571,6 +660,7 @@ func init() {
addPrefFlagMapping("exit-node-allow-lan-access", "ExitNodeAllowLANAccess")
addPrefFlagMapping("unattended", "ForceDaemon")
addPrefFlagMapping("operator", "OperatorUser")
addPrefFlagMapping("ssh", "RunSSH")
}
func addPrefFlagMapping(flagName string, prefNames ...string) {
@@ -588,7 +678,7 @@ func addPrefFlagMapping(flagName string, prefNames ...string) {
// correspond to an ipn.Pref.
func preflessFlag(flagName string) bool {
switch flagName {
case "authkey", "force-reauth", "reset", "qr":
case "auth-key", "force-reauth", "reset", "qr", "json":
return true
}
return false
@@ -622,6 +712,7 @@ type upCheckEnv struct {
upArgs upArgsT
backendState string
curExitNodeIP netaddr.IP
distro distro.Distro
}
// checkForAccidentalSettingReverts (the "up checker") checks for
@@ -672,6 +763,10 @@ func checkForAccidentalSettingReverts(newPrefs, curPrefs *ipn.Prefs, env upCheck
if flagName == "login-server" && ipn.IsLoginServerSynonym(valCur) && ipn.IsLoginServerSynonym(valNew) {
continue
}
if flagName == "accept-routes" && valNew == false && env.goos == "linux" && env.distro == distro.Synology {
// Issue 3176. Old prefs had 'RouteAll: true' on disk, so ignore that.
continue
}
missing = append(missing, fmtFlagValueArg(flagName, valCur))
}
if len(missing) == 0 {
@@ -728,8 +823,8 @@ func flagAppliesToOS(flag, goos string) bool {
return true
}
func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]interface{}) {
ret := make(map[string]interface{})
func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]any) {
ret := make(map[string]any)
exitNodeIPStr := func() string {
if !prefs.ExitNodeIP.IsZero() {
@@ -746,7 +841,7 @@ func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]interfac
if preflessFlag(f.Name) {
return
}
set := func(v interface{}) {
set := func(v any) {
if flagAppliesToOS(f.Name, env.goos) {
ret[f.Name] = v
} else {
@@ -756,6 +851,8 @@ func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]interfac
switch f.Name {
default:
panic(fmt.Sprintf("unhandled flag %q", f.Name))
case "ssh":
set(prefs.RunSSH)
case "login-server":
set(prefs.ControlURL)
case "accept-routes":
@@ -798,7 +895,7 @@ func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]interfac
return ret
}
func fmtFlagValueArg(flagName string, val interface{}) string {
func fmtFlagValueArg(flagName string, val any) string {
if val == true {
return "--" + flagName
}

View File

@@ -270,14 +270,14 @@ func synoTokenRedirect(w http.ResponseWriter, r *http.Request) bool {
// We need a SynoToken for authenticate.cgi.
// So we tell the client to get one.
serverURL := r.URL.Scheme + "://" + r.URL.Host
fmt.Fprintf(w, synoTokenRedirectHTML, serverURL)
synoTokenRedirectHTML.Execute(w, serverURL)
return true
}
const synoTokenRedirectHTML = `<html><body>
var synoTokenRedirectHTML = template.Must(template.New("redirect").Parse(`<html><body>
Redirecting with session token...
<script>
var serverURL = %q;
var serverURL = {{ . }};
var req = new XMLHttpRequest();
req.overrideMimeType("application/json");
req.open("GET", serverURL + "/webman/login.cgi", true);
@@ -289,7 +289,7 @@ req.onload = function() {
req.send(null);
</script>
</body></html>
`
`))
func webHandler(w http.ResponseWriter, r *http.Request) {
if authRedirect(w, r) {
@@ -313,7 +313,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
AdvertiseExitNode bool
Reauthenticate bool
}
type mi map[string]interface{}
type mi map[string]any
if err := json.NewDecoder(r.Body).Decode(&postData); err != nil {
w.WriteHeader(400)
json.NewEncoder(w).Encode(mi{"error": err.Error()})
@@ -375,7 +375,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
data.AdvertiseExitNode = true
} else {
if data.AdvertiseRoutes != "" {
data.AdvertiseRoutes = ","
data.AdvertiseRoutes += ","
}
data.AdvertiseRoutes += r.String()
}

View File

@@ -3,8 +3,15 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
L github.com/klauspost/compress/flate from nhooyr.io/websocket
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
github.com/peterbourgon/ff/v3/ffcli from tailscale.com/cmd/tailscale/cli
@@ -30,22 +37,23 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn+
tailscale.com/client/tailscale from tailscale.com/cmd/tailscale/cli+
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+
tailscale.com/client/tailscale/apitype from tailscale.com/cmd/tailscale/cli+
tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale
tailscale.com/control/controlknobs from tailscale.com/net/portmapper
tailscale.com/derp from tailscale.com/derp/derphttp
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
L tailscale.com/derp/wsconn from tailscale.com/derp/derphttp
tailscale.com/disco from tailscale.com/derp
tailscale.com/hostinfo from tailscale.com/net/interfaces
tailscale.com/envknob from tailscale.com/cmd/tailscale/cli+
tailscale.com/hostinfo from tailscale.com/net/interfaces+
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
tailscale.com/kube from tailscale.com/ipn
💣 tailscale.com/metrics from tailscale.com/derp
tailscale.com/net/dnscache from tailscale.com/derp/derphttp
tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+
💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
tailscale.com/net/neterror from tailscale.com/net/netcheck+
tailscale.com/net/netknob from tailscale.com/net/netns
tailscale.com/net/netns from tailscale.com/derp/derphttp+
tailscale.com/net/packet from tailscale.com/wgengine/filter
@@ -56,7 +64,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
💣 tailscale.com/paths from tailscale.com/cmd/tailscale/cli+
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
W tailscale.com/tsconst from tailscale.com/net/interfaces
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
@@ -72,11 +80,15 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/persist from tailscale.com/ipn
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/structs from tailscale.com/ipn+
tailscale.com/util/clientmetric from tailscale.com/net/netcheck
tailscale.com/types/views from tailscale.com/tailcfg+
tailscale.com/util/clientmetric from tailscale.com/net/netcheck+
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
W tailscale.com/util/endian from tailscale.com/net/netns
tailscale.com/util/groupmember from tailscale.com/cmd/tailscale/cli
tailscale.com/util/lineread from tailscale.com/net/interfaces+
W tailscale.com/util/winutil from tailscale.com/hostinfo
W 💣 tailscale.com/util/winutil/vss from tailscale.com/util/winutil
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine/filter from tailscale.com/types/netmap
@@ -89,9 +101,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/nacl/box from tailscale.com/types/key
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/net/dns/dnsmessage from net
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http+
golang.org/x/net/http/httpproxy from net/http
golang.org/x/net/http2/hpack from net/http
@@ -174,6 +186,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
net/http/cgi from tailscale.com/cmd/tailscale/cli
net/http/httptrace from github.com/tcnksm/go-httpstat+
net/http/internal from net/http
net/netip from net
net/textproto from golang.org/x/net/http/httpguts+
net/url from crypto/x509+
os from crypto/rand+
@@ -185,7 +198,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
reflect from crypto/x509+
regexp from github.com/tailscale/goupnp/httpu+
regexp/syntax from regexp
runtime/debug from golang.org/x/sync/singleflight
runtime/debug from golang.org/x/sync/singleflight+
sort from compress/flate+
strconv from compress/flate+
strings from bufio+

View File

@@ -17,6 +17,7 @@ import (
func main() {
args := os.Args[1:]
args = cli.CleanUpArgs(args)
if name, _ := os.Executable(); strings.HasSuffix(filepath.Base(name), ".cgi") {
args = []string{"web", "-cgi"}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package childproc allows other packages to register "tailscaled be-child"
// child process hook code. This avoids duplicating build tags in the
// tailscaled package. Instead, the code that needs to fork/exec the self
// executable (when it's tailscaled) can instead register the code
// they want to run.
package childproc
var Code = map[string]func([]string) error{}
// Add registers code f to run as 'tailscaled be-child <typ> [args]'.
func Add(typ string, f func(args []string) error) {
if _, dup := Code[typ]; dup {
panic("dup hook " + typ)
}
Code[typ] = f
}

View File

@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
package main
import (
@@ -24,6 +27,7 @@ import (
"inet.af/netaddr"
"tailscale.com/derp/derphttp"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/net/interfaces"
"tailscale.com/net/portmapper"
@@ -224,7 +228,7 @@ func debugPortmap(ctx context.Context) error {
defer cancel()
portmapper.VerboseLogs = true
switch os.Getenv("TS_DEBUG_PORTMAP_TYPE") {
switch envknob.String("TS_DEBUG_PORTMAP_TYPE") {
case "":
case "pmp":
portmapper.DisablePCP = true

View File

@@ -3,9 +3,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
LD github.com/anmitsu/go-shlex from github.com/tailscale/ssh
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/aws
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/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+
@@ -15,7 +16,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4 from github.com/aws/aws-sdk-go-v2/aws/signer/v4
L github.com/aws/aws-sdk-go-v2/aws/signer/v4 from github.com/aws/aws-sdk-go-v2/service/internal/presigned-url+
L github.com/aws/aws-sdk-go-v2/aws/transport/http from github.com/aws/aws-sdk-go-v2/config+
L github.com/aws/aws-sdk-go-v2/config from tailscale.com/ipn/store/aws
L github.com/aws/aws-sdk-go-v2/config from tailscale.com/ipn/store/awsstore
L github.com/aws/aws-sdk-go-v2/credentials from github.com/aws/aws-sdk-go-v2/config
L github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds from github.com/aws/aws-sdk-go-v2/config
L github.com/aws/aws-sdk-go-v2/credentials/endpointcreds from github.com/aws/aws-sdk-go-v2/config
@@ -25,7 +26,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/aws-sdk-go-v2/credentials/stscreds from github.com/aws/aws-sdk-go-v2/config
L github.com/aws/aws-sdk-go-v2/feature/ec2/imds from github.com/aws/aws-sdk-go-v2/config+
L github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config from github.com/aws/aws-sdk-go-v2/feature/ec2/imds
L github.com/aws/aws-sdk-go-v2/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints+
L github.com/aws/aws-sdk-go-v2/internal/configsources from github.com/aws/aws-sdk-go-v2/service/ssm+
L github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 from github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints+
L github.com/aws/aws-sdk-go-v2/internal/ini from github.com/aws/aws-sdk-go-v2/config
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+
@@ -34,7 +36,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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
L github.com/aws/aws-sdk-go-v2/service/internal/presigned-url from github.com/aws/aws-sdk-go-v2/service/sts
L github.com/aws/aws-sdk-go-v2/service/ssm from tailscale.com/ipn/store/aws
L github.com/aws/aws-sdk-go-v2/service/ssm from tailscale.com/ipn/store/awsstore
L github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/aws/aws-sdk-go-v2/service/ssm/types from github.com/aws/aws-sdk-go-v2/service/ssm+
L github.com/aws/aws-sdk-go-v2/service/sso from github.com/aws/aws-sdk-go-v2/config+
@@ -59,17 +61,19 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/smithy-go/transport/http/internal/io from github.com/aws/smithy-go/transport/http
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
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
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
github.com/google/btree from inet.af/netstack/tcpip/header+
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun
L github.com/insomniacslk/dhcp/iana from github.com/insomniacslk/dhcp/dhcpv4
L github.com/insomniacslk/dhcp/interfaces from github.com/insomniacslk/dhcp/dhcpv4
L github.com/insomniacslk/dhcp/rfc1035label from github.com/insomniacslk/dhcp/dhcpv4
L github.com/jmespath/go-jmespath from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress from github.com/klauspost/compress/zstd
L github.com/klauspost/compress/flate from nhooyr.io/websocket
@@ -78,6 +82,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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
L github.com/mdlayher/genetlink from tailscale.com/net/tstun
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
@@ -92,7 +97,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
L 💣 github.com/tailscale/netlink from tailscale.com/wgengine/router
LD github.com/tailscale/ssh from tailscale.com/ssh/tailssh
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
LD github.com/u-root/u-root/pkg/termios from tailscale.com/ssh/tailssh
L github.com/u-root/uio/rand from github.com/insomniacslk/dhcp/dhcpv4
L github.com/u-root/uio/ubinary from github.com/u-root/uio/uio
L github.com/u-root/uio/uio from github.com/insomniacslk/dhcp/dhcpv4+
@@ -113,46 +120,46 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.zx2c4.com/wireguard/tai64n from golang.zx2c4.com/wireguard/device
💣 golang.zx2c4.com/wireguard/tun from golang.zx2c4.com/wireguard/device+
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/cmd/tailscaled+
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/tcpip+
💣 gvisor.dev/gvisor/pkg/buffer from gvisor.dev/gvisor/pkg/tcpip/stack
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs+
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
gvisor.dev/gvisor/pkg/rand from gvisor.dev/gvisor/pkg/tcpip/network/hash+
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/refsvfs2
gvisor.dev/gvisor/pkg/refsvfs2 from gvisor.dev/gvisor/pkg/tcpip/stack
💣 gvisor.dev/gvisor/pkg/sleep from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+
gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/linewriter+
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
💣 gvisor.dev/gvisor/pkg/tcpip/buffer from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
gvisor.dev/gvisor/pkg/tcpip/hash/jenkins from gvisor.dev/gvisor/pkg/tcpip/stack+
gvisor.dev/gvisor/pkg/tcpip/header from gvisor.dev/gvisor/pkg/tcpip/header/parse+
gvisor.dev/gvisor/pkg/tcpip/header/parse from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/internal/tcp from gvisor.dev/gvisor/pkg/tcpip/stack+
gvisor.dev/gvisor/pkg/tcpip/link/channel from tailscale.com/wgengine/netstack
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/internal/fragmentation from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/internal/ip from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/ipv4 from tailscale.com/net/tstun+
gvisor.dev/gvisor/pkg/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
gvisor.dev/gvisor/pkg/tcpip/ports from gvisor.dev/gvisor/pkg/tcpip/stack+
gvisor.dev/gvisor/pkg/tcpip/seqnum from gvisor.dev/gvisor/pkg/tcpip/header+
💣 gvisor.dev/gvisor/pkg/tcpip/stack from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
gvisor.dev/gvisor/pkg/tcpip/transport from gvisor.dev/gvisor/pkg/tcpip/transport/icmp+
gvisor.dev/gvisor/pkg/tcpip/transport/icmp from tailscale.com/wgengine/netstack
gvisor.dev/gvisor/pkg/tcpip/transport/internal/network from gvisor.dev/gvisor/pkg/tcpip/transport/icmp+
gvisor.dev/gvisor/pkg/tcpip/transport/internal/noop from gvisor.dev/gvisor/pkg/tcpip/transport/raw
gvisor.dev/gvisor/pkg/tcpip/transport/packet from gvisor.dev/gvisor/pkg/tcpip/transport/raw
gvisor.dev/gvisor/pkg/tcpip/transport/raw from gvisor.dev/gvisor/pkg/tcpip/transport/icmp+
💣 gvisor.dev/gvisor/pkg/tcpip/transport/tcp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
gvisor.dev/gvisor/pkg/tcpip/transport/tcpconntrack from gvisor.dev/gvisor/pkg/tcpip/stack
gvisor.dev/gvisor/pkg/tcpip/transport/udp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+
inet.af/netaddr from inet.af/wf+
inet.af/netstack/atomicbitops from inet.af/netstack/tcpip+
💣 inet.af/netstack/buffer from inet.af/netstack/tcpip/stack
inet.af/netstack/context from inet.af/netstack/refs+
💣 inet.af/netstack/gohacks from inet.af/netstack/state/wire+
inet.af/netstack/linewriter from inet.af/netstack/log
inet.af/netstack/log from inet.af/netstack/state+
inet.af/netstack/rand from inet.af/netstack/tcpip/network/hash+
inet.af/netstack/refs from inet.af/netstack/refsvfs2
inet.af/netstack/refsvfs2 from inet.af/netstack/tcpip/stack
💣 inet.af/netstack/sleep from inet.af/netstack/tcpip/transport/tcp
💣 inet.af/netstack/state from inet.af/netstack/atomicbitops+
inet.af/netstack/state/wire from inet.af/netstack/state
💣 inet.af/netstack/sync from inet.af/netstack/linewriter+
inet.af/netstack/tcpip from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
💣 inet.af/netstack/tcpip/buffer from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/tcpip/hash/jenkins from inet.af/netstack/tcpip/stack+
inet.af/netstack/tcpip/header from inet.af/netstack/tcpip/header/parse+
inet.af/netstack/tcpip/header/parse from inet.af/netstack/tcpip/network/ipv4+
inet.af/netstack/tcpip/internal/tcp from inet.af/netstack/tcpip/stack+
inet.af/netstack/tcpip/link/channel from tailscale.com/wgengine/netstack
inet.af/netstack/tcpip/network/hash from inet.af/netstack/tcpip/network/ipv4+
inet.af/netstack/tcpip/network/internal/fragmentation from inet.af/netstack/tcpip/network/ipv4+
inet.af/netstack/tcpip/network/internal/ip from inet.af/netstack/tcpip/network/ipv4+
inet.af/netstack/tcpip/network/ipv4 from tailscale.com/net/tstun+
inet.af/netstack/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
inet.af/netstack/tcpip/ports from inet.af/netstack/tcpip/stack+
inet.af/netstack/tcpip/seqnum from inet.af/netstack/tcpip/header+
💣 inet.af/netstack/tcpip/stack from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/tcpip/transport from inet.af/netstack/tcpip/transport/icmp+
inet.af/netstack/tcpip/transport/icmp from tailscale.com/wgengine/netstack
inet.af/netstack/tcpip/transport/internal/network from inet.af/netstack/tcpip/transport/icmp+
inet.af/netstack/tcpip/transport/internal/noop from inet.af/netstack/tcpip/transport/raw
inet.af/netstack/tcpip/transport/packet from inet.af/netstack/tcpip/transport/raw
inet.af/netstack/tcpip/transport/raw from inet.af/netstack/tcpip/transport/icmp+
💣 inet.af/netstack/tcpip/transport/tcp from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/tcpip/transport/tcpconntrack from inet.af/netstack/tcpip/stack
inet.af/netstack/tcpip/transport/udp from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/waiter from inet.af/netstack/tcpip+
inet.af/peercred from tailscale.com/ipn/ipnserver
W 💣 inet.af/wf from tailscale.com/wf
L nhooyr.io/websocket from tailscale.com/derp/derphttp+
@@ -163,12 +170,16 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
LD tailscale.com/chirp from tailscale.com/cmd/tailscaled
tailscale.com/client/tailscale from tailscale.com/derp
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled+
tailscale.com/control/controlbase from tailscale.com/control/controlclient+
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
tailscale.com/derp from tailscale.com/derp/derphttp+
tailscale.com/derp/derphttp from tailscale.com/cmd/tailscaled+
L tailscale.com/derp/wsconn from tailscale.com/derp/derphttp
tailscale.com/disco from tailscale.com/derp+
tailscale.com/envknob from tailscale.com/cmd/tailscaled+
tailscale.com/health from tailscale.com/control/controlclient+
tailscale.com/hostinfo from tailscale.com/control/controlclient+
tailscale.com/ipn from tailscale.com/client/tailscale+
@@ -177,44 +188,53 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver
tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal
tailscale.com/ipn/store/aws from tailscale.com/ipn/ipnserver
tailscale.com/kube from tailscale.com/ipn
tailscale.com/log/filelogger from tailscale.com/ipn/ipnserver
tailscale.com/ipn/store from tailscale.com/cmd/tailscaled
L tailscale.com/ipn/store/awsstore from tailscale.com/ipn/store
L tailscale.com/ipn/store/kubestore from tailscale.com/ipn/store
tailscale.com/ipn/store/mem from tailscale.com/ipn/store+
L tailscale.com/kube from tailscale.com/ipn/store/kubestore
tailscale.com/log/filelogger from tailscale.com/logpolicy
tailscale.com/log/logheap from tailscale.com/control/controlclient
tailscale.com/logpolicy from tailscale.com/cmd/tailscaled
tailscale.com/logtail from tailscale.com/logpolicy+
tailscale.com/logpolicy from tailscale.com/cmd/tailscaled+
tailscale.com/logtail from tailscale.com/cmd/tailscaled+
tailscale.com/logtail/backoff from tailscale.com/cmd/tailscaled+
tailscale.com/logtail/filch from tailscale.com/logpolicy
💣 tailscale.com/metrics from tailscale.com/derp
💣 tailscale.com/metrics from tailscale.com/derp+
tailscale.com/net/dns from tailscale.com/cmd/tailscaled+
tailscale.com/net/dns/resolver from tailscale.com/net/dns+
tailscale.com/net/dns/resolvconffile from tailscale.com/net/dns+
tailscale.com/net/dns/resolver from tailscale.com/ipn/ipnlocal+
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
tailscale.com/net/dnsfallback from tailscale.com/control/controlclient+
tailscale.com/net/flowtrack from tailscale.com/net/packet+
💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscaled+
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
tailscale.com/net/netknob from tailscale.com/ipn/localapi+
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
tailscale.com/net/netknob from tailscale.com/logpolicy+
tailscale.com/net/netns from tailscale.com/cmd/tailscaled+
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver
tailscale.com/net/netutil from tailscale.com/ipn/ipnlocal+
tailscale.com/net/packet from tailscale.com/net/tstun+
tailscale.com/net/portmapper from tailscale.com/cmd/tailscaled+
tailscale.com/net/socks5 from tailscale.com/net/socks5/tssocks
tailscale.com/net/socks5/tssocks from tailscale.com/cmd/tailscaled
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
tailscale.com/net/stun from tailscale.com/net/netcheck+
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+
tailscale.com/net/tsaddr from tailscale.com/ipn+
tailscale.com/net/tsdial from tailscale.com/cmd/tailscaled+
💣 tailscale.com/net/tshttpproxy from tailscale.com/cmd/tailscaled+
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
💣 tailscale.com/paths from tailscale.com/client/tailscale+
tailscale.com/portlist from tailscale.com/ipn/ipnlocal
tailscale.com/safesocket from tailscale.com/client/tailscale+
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
💣 tailscale.com/syncs from tailscale.com/control/controlknobs+
LD 💣 tailscale.com/ssh/tailssh from tailscale.com/wgengine/netstack
tailscale.com/syncs from tailscale.com/control/controlknobs+
tailscale.com/tailcfg from tailscale.com/client/tailscale+
W tailscale.com/tsconst from tailscale.com/net/interfaces
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 from tailscale.com/cmd/tailscaled
tailscale.com/types/dnstype from tailscale.com/ipn/ipnlocal+
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
@@ -228,14 +248,16 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/persist from tailscale.com/control/controlclient+
tailscale.com/types/preftype from tailscale.com/ipn+
tailscale.com/types/structs from tailscale.com/control/controlclient+
tailscale.com/util/clientmetric from tailscale.com/ipn/localapi+
L tailscale.com/util/cmpver from tailscale.com/net/dns
tailscale.com/types/views from tailscale.com/ipn/ipnlocal+
tailscale.com/util/clientmetric from tailscale.com/cmd/tailscaled+
LW tailscale.com/util/cmpver from tailscale.com/net/dns+
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
tailscale.com/util/dnsname from tailscale.com/hostinfo+
LW tailscale.com/util/endian from tailscale.com/net/dns+
tailscale.com/util/groupmember from tailscale.com/ipn/ipnserver
tailscale.com/util/lineread from tailscale.com/hostinfo+
tailscale.com/util/multierr from tailscale.com/cmd/tailscaled+
tailscale.com/util/netconv from tailscale.com/wgengine/magicsock
tailscale.com/util/osshare from tailscale.com/cmd/tailscaled+
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
tailscale.com/util/racebuild from tailscale.com/logpolicy
@@ -248,9 +270,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
W tailscale.com/wf from tailscale.com/cmd/tailscaled
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/magicsock from tailscale.com/wgengine+
tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/monitor from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
@@ -258,29 +280,34 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/acme from tailscale.com/ipn/localapi
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from golang.zx2c4.com/wireguard/device
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/blake2s from golang.zx2c4.com/wireguard/device+
LD golang.org/x/crypto/blowfish from golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+
golang.org/x/crypto/hkdf from crypto/tls
LD golang.org/x/crypto/ed25519 from golang.org/x/crypto/ssh
golang.org/x/crypto/hkdf from crypto/tls+
golang.org/x/crypto/nacl/box from tailscale.com/types/key
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/poly1305 from golang.zx2c4.com/wireguard/device
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/net/bpf from github.com/mdlayher/netlink+
LD golang.org/x/crypto/ssh from github.com/tailscale/ssh+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http+
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
golang.org/x/net/http/httpproxy from net/http
golang.org/x/net/http2/hpack from net/http
golang.org/x/net/http2 from golang.org/x/net/http2/h2c+
golang.org/x/net/http2/h2c from tailscale.com/ipn/ipnlocal
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
golang.org/x/net/ipv4 from golang.zx2c4.com/wireguard/device
golang.org/x/net/ipv6 from golang.zx2c4.com/wireguard/device+
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
golang.org/x/sync/errgroup from github.com/tailscale/goupnp/httpu+
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
golang.org/x/sync/singleflight from tailscale.com/control/controlclient+
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/insomniacslk/dhcp/interfaces+
W golang.org/x/sys/windows from github.com/go-ole/go-ole+
@@ -292,26 +319,26 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from inet.af/netstack/tcpip/stack+
golang.org/x/time/rate from gvisor.dev/gvisor/pkg/tcpip/stack+
bufio from compress/flate+
bytes from bufio+
compress/flate from compress/gzip+
compress/gzip from internal/profile+
container/heap from inet.af/netstack/tcpip/transport/tcp
compress/gzip from golang.org/x/net/http2+
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
container/list from crypto/tls+
context from crypto/tls+
crypto from crypto/ecdsa+
crypto/aes from crypto/ecdsa+
crypto/cipher from crypto/aes+
crypto/des from crypto/tls+
crypto/dsa from crypto/x509
crypto/dsa from crypto/x509+
crypto/ecdsa from crypto/tls+
crypto/ed25519 from crypto/tls+
crypto/elliptic from crypto/ecdsa+
crypto/hmac from crypto/tls+
crypto/md5 from crypto/tls+
crypto/rand from crypto/ed25519+
crypto/rc4 from crypto/tls
crypto/rc4 from crypto/tls+
crypto/rsa from crypto/tls+
crypto/sha1 from crypto/tls+
crypto/sha256 from crypto/tls+
@@ -320,7 +347,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
crypto/tls from github.com/aws/aws-sdk-go-v2/aws/transport/http+
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+
embed from tailscale.com/net/dns+
embed from crypto/elliptic+
encoding from encoding/json+
encoding/asn1 from crypto/x509+
encoding/base64 from encoding/json+
@@ -335,13 +362,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
fmt from compress/flate+
hash from crypto+
hash/crc32 from compress/gzip+
hash/fnv from inet.af/netstack/tcpip/network/ipv6+
hash/fnv from gvisor.dev/gvisor/pkg/tcpip/network/ipv6+
hash/maphash from go4.org/mem
html from net/http/pprof+
io from bufio+
io/fs from crypto/rand+
io/ioutil from github.com/aws/aws-sdk-go-v2/aws/protocol/query+
log from expvar+
LD log/syslog from tailscale.com/ssh/tailssh
math from compress/flate+
math/big from crypto/dsa+
math/bits from compress/flate+
@@ -355,6 +383,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
net/http/httputil from github.com/aws/smithy-go/transport/http+
net/http/internal from net/http+
net/http/pprof from tailscale.com/cmd/tailscaled+
net/netip from golang.zx2c4.com/wireguard/conn+
net/textproto from github.com/aws/aws-sdk-go-v2/aws/signer/v4+
net/url from crypto/x509+
os from crypto/rand+
@@ -364,7 +393,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
path from github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from github.com/aws/aws-sdk-go-v2/internal/endpoints+
regexp from github.com/aws/aws-sdk-go-v2/internal/endpoints/v2+
regexp/syntax from regexp
runtime/debug from github.com/klauspost/compress/zstd+
runtime/pprof from net/http/pprof+

View File

@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
package main
import (

View File

@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
package main
import (

View File

@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
// HTTP proxy code
package main

View File

@@ -0,0 +1,12 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.18
// +build !go1.18
package main
func init() {
you_need_Go_1_18_to_compile_Tailscale()
}

View File

@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
// The tailscaled program is the Tailscale client daemon. It's configured
// and controlled via the tailscale CLI program.
//
@@ -23,21 +26,28 @@ import (
"path/filepath"
"runtime"
"runtime/debug"
"strconv"
"strings"
"syscall"
"time"
"inet.af/netaddr"
"tailscale.com/cmd/tailscaled/childproc"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store"
"tailscale.com/logpolicy"
"tailscale.com/logtail"
"tailscale.com/net/dns"
"tailscale.com/net/netns"
"tailscale.com/net/socks5/tssocks"
"tailscale.com/net/proxymux"
"tailscale.com/net/socks5"
"tailscale.com/net/tsdial"
"tailscale.com/net/tstun"
"tailscale.com/paths"
"tailscale.com/safesocket"
"tailscale.com/tsweb"
"tailscale.com/types/flagtype"
"tailscale.com/types/logger"
"tailscale.com/util/clientmetric"
@@ -63,11 +73,27 @@ func defaultTunName() string {
// as a magic value that uses/creates any free number.
return "utun"
case "linux":
if distro.Get() == distro.Synology {
switch distro.Get() {
case distro.Synology:
// Try TUN, but fall back to userspace networking if needed.
// See https://github.com/tailscale/tailscale-synology/issues/35
return "tailscale0,userspace-networking"
case distro.Gokrazy:
// Gokrazy doesn't yet work in tun mode because the whole
// Gokrazy thing is no C code, and Tailscale currently
// depends on the iptables binary for Linux's
// wgengine/router.
// But on Gokrazy there's no legacy iptables, so we could use netlink
// to program nft-iptables directly. It just isn't done yet;
// see https://github.com/tailscale/tailscale/issues/391
//
// But Gokrazy does have the tun module built-in, so users
// can stil run --tun=tailscale0 if they wish, if they
// arrange for iptables to be present or run in "tailscale
// up --netfilter-mode=off" mode, perhaps. Untested.
return "userspace-networking"
}
}
return "tailscale0"
}
@@ -100,6 +126,7 @@ var subCommands = map[string]*func([]string) error{
"install-system-daemon": &installSystemDaemon,
"uninstall-system-daemon": &uninstallSystemDaemon,
"debug": &debugModeFunc,
"be-child": &beChildFunc,
}
func main() {
@@ -119,7 +146,7 @@ func main() {
flag.StringVar(&args.httpProxyAddr, "outbound-http-proxy-listen", "", `optional [ip]:port to run an outbound HTTP proxy (e.g. "localhost:8080")`)
flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`)
flag.Var(flagtype.PortValue(&args.port, 0), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
flag.StringVar(&args.statepath, "state", paths.DefaultTailscaledStateFile(), "absolute path of state file; use 'kube:<secret-name>' to use Kubernetes secrets or 'arn:aws:ssm:...' to store in AWS SSM. If empty and --statedir is provided, the default is <statedir>/tailscaled.state")
flag.StringVar(&args.statepath, "state", paths.DefaultTailscaledStateFile(), "absolute path of state file; use 'kube:<secret-name>' to use Kubernetes secrets or 'arn:aws:ssm:...' to store in AWS SSM; use 'mem:' to not store state and register as an emphemeral node. If empty and --statedir is provided, the default is <statedir>/tailscaled.state")
flag.StringVar(&args.statedir, "statedir", "", "path to directory for storage of config state, TLS certs, temporary incoming Taildrop files, etc. If empty, it's derived from --state when possible.")
flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
@@ -154,6 +181,9 @@ func main() {
os.Exit(0)
}
if runtime.GOOS == "darwin" && runtime.Version() == "go1.18" {
log.Fatalf("tailscaled is broken on macOS with go1.18 due to upstream bug https://github.com/golang/go/issues/51759; use 1.18.1+ or Tailscale's Go fork")
}
if runtime.GOOS == "darwin" && os.Getuid() != 0 && !strings.Contains(args.tunname, "userspace-networking") && !args.cleanup {
log.SetFlags(0)
log.Fatalf("tailscaled requires root; use sudo tailscaled (or use --tun=userspace-networking)")
@@ -175,8 +205,7 @@ func main() {
osshare.SetFileSharingEnabled(false, logger.Discard)
if err != nil {
// No need to log; the func already did
os.Exit(1)
log.Fatal(err)
}
}
@@ -221,7 +250,7 @@ func statePathOrDefault() string {
func ipnServerOpts() (o ipnserver.Options) {
// Allow changing the OS-specific IPN behavior for tests
// so we can e.g. test Windows-specific behaviors on Linux.
goos := os.Getenv("TS_DEBUG_TAILSCALED_IPN_GOOS")
goos := envknob.String("TS_DEBUG_TAILSCALED_IPN_GOOS")
if goos == "" {
goos = runtime.GOOS
}
@@ -235,8 +264,19 @@ func ipnServerOpts() (o ipnserver.Options) {
o.VarRoot = dir
}
}
if strings.HasPrefix(statePathOrDefault(), "mem:") {
// Register as an ephemeral node.
o.LoginFlags = controlclient.LoginEphemeral
}
switch goos {
case "js":
// The js/wasm client has no state storage so for now
// treat all interactive logins as ephemeral.
// TODO(bradfitz): if we start using browser LocalStorage
// or something, then rethink this.
o.LoginFlags = controlclient.LoginEphemeral
fallthrough
default:
o.SurviveDisconnects = true
o.AutostartStateKey = ipn.GlobalDaemonStateKey
@@ -269,13 +309,13 @@ func run() error {
}
var logf logger.Logf = log.Printf
if v, _ := strconv.ParseBool(os.Getenv("TS_DEBUG_MEMORY")); v {
if envknob.Bool("TS_DEBUG_MEMORY") {
logf = logger.RusagePrefixLog(logf)
}
logf = logger.RateLimitedFn(logf, 5*time.Second, 5, 100)
if args.cleanup {
if os.Getenv("TS_PLEASE_PANIC") != "" {
if envknob.Bool("TS_PLEASE_PANIC") {
panic("TS_PLEASE_PANIC asked us to panic")
}
dns.Cleanup(logf, args.tunname)
@@ -293,48 +333,63 @@ func run() error {
var debugMux *http.ServeMux
if args.debug != "" {
debugMux = newDebugMux()
go runDebugServer(debugMux, args.debug)
}
linkMon, err := monitor.New(logf)
if err != nil {
log.Fatalf("creating link monitor: %v", err)
return fmt.Errorf("monitor.New: %w", err)
}
pol.Logtail.SetLinkMonitor(linkMon)
socksListener := mustStartTCPListener("SOCKS5", args.socksAddr)
httpProxyListener := mustStartTCPListener("HTTP proxy", args.httpProxyAddr)
socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr)
e, useNetstack, err := createEngine(logf, linkMon)
dialer := new(tsdial.Dialer) // mutated below (before used)
e, useNetstack, err := createEngine(logf, linkMon, dialer)
if err != nil {
logf("wgengine.New: %v", err)
return err
return 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)
}
}
go runDebugServer(debugMux, args.debug)
}
ns, err := newNetstack(logf, e)
ns, err := newNetstack(logf, dialer, e)
if err != nil {
return fmt.Errorf("newNetstack: %w", err)
}
ns.ProcessLocalIPs = useNetstack
ns.ProcessSubnets = useNetstack || wrapNetstack
if err := ns.Start(); err != nil {
log.Fatalf("failed to start netstack: %v", err)
}
if useNetstack {
dialer.UseNetstackForIP = func(ip netaddr.IP) bool {
_, ok := e.PeerForIP(ip)
return ok
}
dialer.NetstackDialTCP = func(ctx context.Context, dst netaddr.IPPort) (net.Conn, error) {
return ns.DialContextTCP(ctx, dst)
}
}
if socksListener != nil || httpProxyListener != nil {
srv := tssocks.NewServer(logger.WithPrefix(logf, "socks5: "), e, ns)
if httpProxyListener != nil {
hs := &http.Server{Handler: httpProxyHandler(srv.Dialer)}
hs := &http.Server{Handler: httpProxyHandler(dialer.UserDial)}
go func() {
log.Fatalf("HTTP proxy exited: %v", hs.Serve(httpProxyListener))
}()
}
if socksListener != nil {
ss := &socks5.Server{
Logf: logger.WithPrefix(logf, "socks5: "),
Dialer: dialer.UserDial,
}
go func() {
log.Fatalf("SOCKS5 server exited: %v", srv.Serve(socksListener))
log.Fatalf("SOCKS5 server exited: %v", ss.Serve(socksListener))
}()
}
}
@@ -362,15 +417,17 @@ func run() error {
opts := ipnServerOpts()
store, err := ipnserver.StateStore(statePathOrDefault(), logf)
store, err := store.New(logf, statePathOrDefault())
if err != nil {
logf("ipnserver.StateStore: %v", err)
return err
return fmt.Errorf("store.New: %w", err)
}
srv, err := ipnserver.New(logf, pol.PublicID.String(), store, e, nil, opts)
srv, err := ipnserver.New(logf, pol.PublicID.String(), store, e, dialer, nil, opts)
if err != nil {
logf("ipnserver.New: %v", err)
return err
return fmt.Errorf("ipnserver.New: %w", err)
}
ns.SetLocalBackend(srv.LocalBackend())
if err := ns.Start(); err != nil {
log.Fatalf("failed to start netstack: %v", err)
}
if debugMux != nil {
@@ -385,21 +442,20 @@ func run() error {
err = srv.Run(ctx, ln)
// Cancelation is not an error: it is the only way to stop ipnserver.
if err != nil && err != context.Canceled {
logf("ipnserver.Run: %v", err)
return err
return fmt.Errorf("ipnserver.Run: %w", err)
}
return nil
}
func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, useNetstack bool, err error) {
func createEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer) (e wgengine.Engine, useNetstack bool, err error) {
if args.tunname == "" {
return nil, false, errors.New("no --tun value specified")
}
var errs []error
for _, name := range strings.Split(args.tunname, ",") {
logf("wgengine.NewUserspaceEngine(tun %q) ...", name)
e, useNetstack, err = tryEngine(logf, linkMon, name)
e, useNetstack, err = tryEngine(logf, linkMon, dialer, name)
if err == nil {
return e, useNetstack, nil
}
@@ -412,11 +468,7 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, us
var wrapNetstack = shouldWrapNetstack()
func shouldWrapNetstack() bool {
if e := os.Getenv("TS_DEBUG_WRAP_NETSTACK"); e != "" {
v, err := strconv.ParseBool(e)
if err != nil {
log.Fatalf("invalid TS_DEBUG_WRAP_NETSTACK value: %v", err)
}
if v, ok := envknob.LookupBool("TS_DEBUG_WRAP_NETSTACK"); ok {
return v
}
if distro.Get() == distro.Synology {
@@ -431,10 +483,11 @@ func shouldWrapNetstack() bool {
return false
}
func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.Engine, useNetstack bool, err error) {
func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, name string) (e wgengine.Engine, useNetstack bool, err error) {
conf := wgengine.Config{
ListenPort: args.port,
LinkMonitor: linkMon,
Dialer: dialer,
}
useNetstack = name == "userspace-networking"
@@ -447,7 +500,20 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.
return nil, false, fmt.Errorf("createBIRDClient: %w", err)
}
}
if !useNetstack {
if useNetstack {
if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
// On Synology in netstack mode, still init a DNS
// manager (directManager) to avoid the health check
// warnings in 'tailscale status' about DNS base
// configuration being unavailable (from the noop
// manager). More in Issue 4017.
// 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)
}
}
} else {
dev, devName, err := tstun.New(logf, name)
if err != nil {
tstun.Diagnose(logf, name)
@@ -495,6 +561,7 @@ func newDebugMux() *http.ServeMux {
func servePrometheusMetrics(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
tsweb.VarzHandler(w, r)
clientmetric.WritePrometheusExpositionFormat(w)
}
@@ -508,26 +575,68 @@ func runDebugServer(mux *http.ServeMux, addr string) {
}
}
func newNetstack(logf logger.Logf, e wgengine.Engine) (*netstack.Impl, error) {
func newNetstack(logf logger.Logf, dialer *tsdial.Dialer, e wgengine.Engine) (*netstack.Impl, error) {
tunDev, magicConn, 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)
return netstack.Create(logf, tunDev, e, magicConn, dialer)
}
func mustStartTCPListener(name, addr string) net.Listener {
if addr == "" {
return nil
// mustStartProxyListeners creates listeners for local SOCKS and HTTP
// proxies, if the respective addresses are not empty. socksAddr and
// httpAddr can be the same, in which case socksListener will receive
// connections that look like they're speaking SOCKS and httpListener
// will receive everything else.
//
// socksListener and httpListener can be nil, if their respective
// addrs are empty.
func mustStartProxyListeners(socksAddr, httpAddr string) (socksListener, httpListener net.Listener) {
if socksAddr == httpAddr && socksAddr != "" && !strings.HasSuffix(socksAddr, ":0") {
ln, err := net.Listen("tcp", socksAddr)
if err != nil {
log.Fatalf("proxy listener: %v", err)
}
return proxymux.SplitSOCKSAndHTTP(ln)
}
ln, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("%v listener: %v", name, err)
var err error
if socksAddr != "" {
socksListener, err = net.Listen("tcp", socksAddr)
if err != nil {
log.Fatalf("SOCKS5 listener: %v", err)
}
if strings.HasSuffix(socksAddr, ":0") {
// Log kernel-selected port number so integration tests
// can find it portably.
log.Printf("SOCKS5 listening on %v", socksListener.Addr())
}
}
if strings.HasSuffix(addr, ":0") {
// Log kernel-selected port number so integration tests
// can find it portably.
log.Printf("%v listening on %v", name, ln.Addr())
if httpAddr != "" {
httpListener, err = net.Listen("tcp", httpAddr)
if err != nil {
log.Fatalf("HTTP proxy listener: %v", err)
}
if strings.HasSuffix(httpAddr, ":0") {
// Log kernel-selected port number so integration tests
// can find it portably.
log.Printf("HTTP proxy listening on %v", httpListener.Addr())
}
}
return ln
return socksListener, httpListener
}
var beChildFunc = beChild
func beChild(args []string) error {
if len(args) == 0 {
return errors.New("missing mode argument")
}
typ := args[0]
f, ok := childproc.Code[typ]
if !ok {
return fmt.Errorf("unknown be-child mode %q", typ)
}
return f(args[1:])
}

View File

@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || darwin || freebsd || openbsd
//go:build go1.18 && (linux || darwin || freebsd || openbsd)
// +build go1.18
// +build linux darwin freebsd openbsd
package main

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !windows
// +build !windows
//go:build !windows && go1.18
// +build !windows,go1.18
package main // import "tailscale.com/cmd/tailscaled"

View File

@@ -0,0 +1,13 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main // import "tailscale.com/cmd/tailscaled"
import "testing"
func TestNothing(t *testing.T) {
// This test does nothing on purpose, so we can run
// GODEBUG=memprofilerate=1 go test -v -run=Nothing -memprofile=prof.mem
// without any errors about no matching tests.
}

View File

@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
package main // import "tailscale.com/cmd/tailscaled"
// TODO: check if administrator, like tswin does.
@@ -29,9 +32,12 @@ import (
"golang.org/x/sys/windows/svc"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"inet.af/netaddr"
"tailscale.com/envknob"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store"
"tailscale.com/logpolicy"
"tailscale.com/net/dns"
"tailscale.com/net/tsdial"
"tailscale.com/net/tstun"
"tailscale.com/safesocket"
"tailscale.com/types/logger"
@@ -39,6 +45,7 @@ import (
"tailscale.com/version"
"tailscale.com/wf"
"tailscale.com/wgengine"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/netstack"
"tailscale.com/wgengine/router"
)
@@ -53,6 +60,11 @@ func isWindowsService() bool {
return v
}
// runWindowsService starts running Tailscale under the Windows
// Service environment.
//
// At this point we're still the parent process that
// Windows started.
func runWindowsService(pol *logpolicy.Policy) error {
return svc.Run(serviceName, &ipnService{Policy: pol})
}
@@ -66,7 +78,7 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch
changes <- svc.Status{State: svc.StartPending}
svcAccepts := svc.AcceptStop
if winutil.GetRegInteger("FlushDNSOnSessionUnlock", 0) != 0 {
if winutil.GetPolicyInteger("FlushDNSOnSessionUnlock", 0) != 0 {
svcAccepts |= svc.AcceptSessionChange
}
@@ -78,7 +90,10 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch
// Make a logger without a date prefix, as filelogger
// and logtail both already add their own. All we really want
// from the log package is the automatic newline.
logger := log.New(os.Stderr, "", 0)
// We start with log.Default().Writer(), which is the logtail
// writer that logpolicy already installed as the global
// output.
logger := log.New(log.Default().Writer(), "", 0)
ipnserver.BabysitProc(ctx, args, logger.Printf)
}()
@@ -88,6 +103,7 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch
select {
case <-doneCh:
case cmd := <-r:
log.Printf("Got Windows Service event: %v", cmdName(cmd.Cmd))
switch cmd.Cmd {
case svc.Stop:
cancel()
@@ -104,6 +120,42 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch
return false, windows.NO_ERROR
}
func cmdName(c svc.Cmd) string {
switch c {
case svc.Stop:
return "Stop"
case svc.Pause:
return "Pause"
case svc.Continue:
return "Continue"
case svc.Interrogate:
return "Interrogate"
case svc.Shutdown:
return "Shutdown"
case svc.ParamChange:
return "ParamChange"
case svc.NetBindAdd:
return "NetBindAdd"
case svc.NetBindRemove:
return "NetBindRemove"
case svc.NetBindEnable:
return "NetBindEnable"
case svc.NetBindDisable:
return "NetBindDisable"
case svc.DeviceEvent:
return "DeviceEvent"
case svc.HardwareProfileChange:
return "HardwareProfileChange"
case svc.PowerEvent:
return "PowerEvent"
case svc.SessionChange:
return "SessionChange"
case svc.PreShutdown:
return "PreShutdown"
}
return fmt.Sprintf("Unknown-Service-Cmd-%d", c)
}
func beWindowsSubprocess() bool {
if beFirewallKillswitch() {
return true
@@ -114,6 +166,9 @@ func beWindowsSubprocess() bool {
}
logid := os.Args[2]
// Remove the date/time prefix; the logtail + file logggers add it.
log.SetFlags(0)
log.Printf("Program starting: v%v: %#v", version.Long, os.Args)
log.Printf("subproc mode: logid=%v", logid)
@@ -177,6 +232,12 @@ func beFirewallKillswitch() bool {
func startIPNServer(ctx context.Context, logid string) error {
var logf logger.Logf = log.Printf
linkMon, err := monitor.New(logf)
if err != nil {
return err
}
dialer := new(tsdial.Dialer)
getEngineRaw := func() (wgengine.Engine, error) {
dev, devName, err := tstun.New(logf, "Tailscale")
if err != nil {
@@ -197,17 +258,19 @@ func startIPNServer(ctx context.Context, logid string) error {
return nil, fmt.Errorf("DNS: %w", err)
}
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Tun: dev,
Router: r,
DNS: d,
ListenPort: 41641,
Tun: dev,
Router: r,
DNS: d,
ListenPort: 41641,
LinkMonitor: linkMon,
Dialer: dialer,
})
if err != nil {
r.Close()
dev.Close()
return nil, fmt.Errorf("engine: %w", err)
}
ns, err := newNetstack(logf, eng)
ns, err := newNetstack(logf, dialer, eng)
if err != nil {
return nil, fmt.Errorf("newNetstack: %w", err)
}
@@ -256,7 +319,7 @@ func startIPNServer(ctx context.Context, logid string) error {
// not called concurrently and is not called again once it
// successfully returns an engine.
getEngine := func() (wgengine.Engine, error) {
if msg := os.Getenv("TS_DEBUG_WIN_FAIL"); msg != "" {
if msg := envknob.String("TS_DEBUG_WIN_FAIL"); msg != "" {
return nil, fmt.Errorf("pretending to be a service failure: %v", msg)
}
for {
@@ -276,8 +339,7 @@ func startIPNServer(ctx context.Context, logid string) error {
return nil, fmt.Errorf("%w\n\nlogid: %v", res.Err, logid)
}
}
store, err := ipnserver.StateStore(statePathOrDefault(), logf)
store, err := store.New(logf, statePathOrDefault())
if err != nil {
return err
}
@@ -287,7 +349,7 @@ func startIPNServer(ctx context.Context, logid string) error {
return fmt.Errorf("safesocket.Listen: %v", err)
}
err = ipnserver.Run(ctx, logf, ln, store, logid, getEngine, ipnServerOpts())
err = ipnserver.Run(ctx, logf, ln, store, linkMon, dialer, logid, getEngine, ipnServerOpts())
if err != nil {
logf("ipnserver.Run: %v", err)
}

View File

@@ -46,10 +46,10 @@ type fakeTB struct {
}
func (t fakeTB) Cleanup(_ func()) {}
func (t fakeTB) Error(args ...interface{}) {
func (t fakeTB) Error(args ...any) {
t.Fatal(args...)
}
func (t fakeTB) Errorf(format string, args ...interface{}) {
func (t fakeTB) Errorf(format string, args ...any) {
t.Fatalf(format, args...)
}
func (t fakeTB) Fail() {
@@ -61,17 +61,17 @@ func (t fakeTB) FailNow() {
func (t fakeTB) Failed() bool {
return false
}
func (t fakeTB) Fatal(args ...interface{}) {
func (t fakeTB) Fatal(args ...any) {
log.Fatal(args...)
}
func (t fakeTB) Fatalf(format string, args ...interface{}) {
func (t fakeTB) Fatalf(format string, args ...any) {
log.Fatalf(format, args...)
}
func (t fakeTB) Helper() {}
func (t fakeTB) Log(args ...interface{}) {
func (t fakeTB) Log(args ...any) {
log.Print(args...)
}
func (t fakeTB) Logf(format string, args ...interface{}) {
func (t fakeTB) Logf(format string, args ...any) {
log.Printf(format, args...)
}
func (t fakeTB) Name() string {
@@ -80,13 +80,13 @@ func (t fakeTB) Name() string {
func (t fakeTB) Setenv(key string, value string) {
panic("not implemented")
}
func (t fakeTB) Skip(args ...interface{}) {
func (t fakeTB) Skip(args ...any) {
t.Fatal("skipped")
}
func (t fakeTB) SkipNow() {
t.Fatal("skipnow")
}
func (t fakeTB) Skipf(format string, args ...interface{}) {
func (t fakeTB) Skipf(format string, args ...any) {
t.Logf(format, args...)
t.Fatal("skipped")
}

View File

@@ -31,7 +31,7 @@ import (
"unsafe"
"github.com/creack/pty"
"github.com/gliderlabs/ssh"
"github.com/tailscale/ssh"
gossh "golang.org/x/crypto/ssh"
"inet.af/netaddr"
"tailscale.com/net/interfaces"

View File

@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package noise implements the base transport of the Tailscale 2021
// control protocol.
// Package controlbase implements the base transport of the Tailscale
// 2021 control protocol.
//
// The base transport implements Noise IK, instantiated with
// Curve25519, ChaCha20Poly1305 and BLAKE2s.
package noise
package controlbase
import (
"crypto/cipher"
@@ -267,18 +267,16 @@ func (c *Conn) Write(bs []byte) (n int, err error) {
ciphertext, err := c.encryptLocked(toSend)
if err != nil {
return 0, err
return sent, err
}
n, err := c.conn.Write(ciphertext)
sent += n
if err != nil {
if _, err := c.conn.Write(ciphertext); err != nil {
// Return the raw error on the Write that actually
// failed. For future writes, return that error wrapped in
// a desync error.
c.tx.err = errPartialWrite{err}
return sent, err
}
sent += len(toSend)
}
return sent, nil
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package noise
package controlbase
import (
"bufio"
@@ -202,7 +202,7 @@ func TestConnStd(t *testing.T) {
serverErr := make(chan error, 1)
go func() {
var err error
c2, err = Server(context.Background(), s2, controlKey)
c2, err = Server(context.Background(), s2, controlKey, nil)
serverErr <- err
}()
c1, err = Client(context.Background(), s1, machineKey, controlKey.Public())
@@ -319,7 +319,7 @@ func pairWithConns(t *testing.T, clientConn, serverConn net.Conn) (*Conn, *Conn)
)
go func() {
var err error
server, err = Server(context.Background(), serverConn, controlKey)
server, err = Server(context.Background(), serverConn, controlKey, nil)
serverErr <- err
}()

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package noise
package controlbase
import (
"context"
@@ -50,21 +50,23 @@ func protocolVersionPrologue(version uint16) []byte {
return strconv.AppendUint(ret, uint64(version), 10)
}
// Client initiates a control client handshake, returning the resulting
// control connection.
//
// The context deadline, if any, covers the entire handshaking
// process. Any preexisting Conn deadline is removed.
func Client(ctx context.Context, conn net.Conn, machineKey key.MachinePrivate, controlKey key.MachinePublic) (*Conn, error) {
if deadline, ok := ctx.Deadline(); ok {
if err := conn.SetDeadline(deadline); err != nil {
return nil, fmt.Errorf("setting conn deadline: %w", err)
}
defer func() {
conn.SetDeadline(time.Time{})
}()
}
// HandshakeContinuation upgrades a net.Conn to a Conn. The net.Conn
// is assumed to have already sent the client>server handshake
// initiation message.
type HandshakeContinuation func(context.Context, net.Conn) (*Conn, error)
// ClientDeferred initiates a control client handshake, returning the
// initial message to send to the server and a continuation to
// finalize the handshake.
//
// ClientDeferred is split in this way for RTT reduction: we run this
// protocol after negotiating a protocol switch from HTTP/HTTPS. If we
// completely serialized the negotiation followed by the handshake,
// we'd pay an extra RTT to transmit the handshake initiation after
// protocol switching. By splitting the handshake into an initial
// message and a continuation, we can embed the handshake initiation
// into the HTTP protocol switching request and avoid a bit of delay.
func ClientDeferred(machineKey key.MachinePrivate, controlKey key.MachinePublic) (initialHandshake []byte, continueHandshake HandshakeContinuation, err error) {
var s symmetricState
s.Initialize()
@@ -83,18 +85,53 @@ func Client(ctx context.Context, conn net.Conn, machineKey key.MachinePrivate, c
s.MixHash(machineEphemeralPub.UntypedBytes())
cipher, err := s.MixDH(machineEphemeral, controlKey)
if err != nil {
return nil, fmt.Errorf("computing es: %w", err)
return nil, nil, fmt.Errorf("computing es: %w", err)
}
machineKeyPub := machineKey.Public()
s.EncryptAndHash(cipher, init.MachinePub(), machineKeyPub.UntypedBytes())
cipher, err = s.MixDH(machineKey, controlKey)
if err != nil {
return nil, fmt.Errorf("computing ss: %w", err)
return nil, nil, fmt.Errorf("computing ss: %w", err)
}
s.EncryptAndHash(cipher, init.Tag(), nil) // empty message payload
if _, err := conn.Write(init[:]); err != nil {
return nil, fmt.Errorf("writing initiation: %w", err)
cont := func(ctx context.Context, conn net.Conn) (*Conn, error) {
return continueClientHandshake(ctx, conn, &s, machineKey, machineEphemeral, controlKey)
}
return init[:], cont, nil
}
// Client wraps ClientDeferred and immediately invokes the returned
// continuation with conn.
//
// This is a helper for when you don't need the fancy
// continuation-style handshake, and just want to synchronously
// upgrade a net.Conn to a secure transport.
func Client(ctx context.Context, conn net.Conn, machineKey key.MachinePrivate, controlKey key.MachinePublic) (*Conn, error) {
init, cont, err := ClientDeferred(machineKey, controlKey)
if err != nil {
return nil, err
}
if _, err := conn.Write(init); err != nil {
return nil, err
}
return cont(ctx, conn)
}
func continueClientHandshake(ctx context.Context, conn net.Conn, s *symmetricState, machineKey, machineEphemeral key.MachinePrivate, controlKey key.MachinePublic) (*Conn, error) {
// No matter what, this function can only run once per s. Ensure
// attempted reuse causes a panic.
defer func() {
s.finished = true
}()
if deadline, ok := ctx.Deadline(); ok {
if err := conn.SetDeadline(deadline); err != nil {
return nil, fmt.Errorf("setting conn deadline: %w", err)
}
defer func() {
conn.SetDeadline(time.Time{})
}()
}
// Read in the payload and look for errors/protocol violations from the server.
@@ -122,10 +159,10 @@ func Client(ctx context.Context, conn net.Conn, machineKey key.MachinePrivate, c
// <- e, ee, se
controlEphemeralPub := key.MachinePublicFromRaw32(mem.B(resp.EphemeralPub()))
s.MixHash(controlEphemeralPub.UntypedBytes())
if _, err = s.MixDH(machineEphemeral, controlEphemeralPub); err != nil {
if _, err := s.MixDH(machineEphemeral, controlEphemeralPub); err != nil {
return nil, fmt.Errorf("computing ee: %w", err)
}
cipher, err = s.MixDH(machineKey, controlEphemeralPub)
cipher, err := s.MixDH(machineKey, controlEphemeralPub)
if err != nil {
return nil, fmt.Errorf("computing se: %w", err)
}
@@ -156,9 +193,13 @@ func Client(ctx context.Context, conn net.Conn, machineKey key.MachinePrivate, c
// Server initiates a control server handshake, returning the resulting
// control connection.
//
// optionalInit can be the client's initial handshake message as
// returned by ClientDeferred, or nil in which case the initial
// message is read from conn.
//
// The context deadline, if any, covers the entire handshaking
// process.
func Server(ctx context.Context, conn net.Conn, controlKey key.MachinePrivate) (*Conn, error) {
func Server(ctx context.Context, conn net.Conn, controlKey key.MachinePrivate, optionalInit []byte) (*Conn, error) {
if deadline, ok := ctx.Deadline(); ok {
if err := conn.SetDeadline(deadline); err != nil {
return nil, fmt.Errorf("setting conn deadline: %w", err)
@@ -190,7 +231,12 @@ func Server(ctx context.Context, conn net.Conn, controlKey key.MachinePrivate) (
s.Initialize()
var init initiationMessage
if _, err := io.ReadFull(conn, init.Header()); err != nil {
if optionalInit != nil {
if len(optionalInit) != len(init) {
return nil, sendErr("wrong handshake initiation size")
}
copy(init[:], optionalInit)
} else if _, err := io.ReadFull(conn, init.Header()); err != nil {
return nil, err
}
if init.Version() != protocolVersion {
@@ -202,8 +248,11 @@ func Server(ctx context.Context, conn net.Conn, controlKey key.MachinePrivate) (
if init.Length() != len(init.Payload()) {
return nil, sendErr("wrong handshake initiation length")
}
if _, err := io.ReadFull(conn, init.Payload()); err != nil {
return nil, err
// if optionalInit was provided, we have the payload already.
if optionalInit == nil {
if _, err := io.ReadFull(conn, init.Payload()); err != nil {
return nil, err
}
}
// prologue. Can only do this once we at least think the client is

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package noise
package controlbase
import (
"bytes"
@@ -26,7 +26,7 @@ func TestHandshake(t *testing.T) {
)
go func() {
var err error
server, err = Server(context.Background(), serverConn, serverKey)
server, err = Server(context.Background(), serverConn, serverKey, nil)
serverErr <- err
}()
@@ -78,7 +78,7 @@ func TestNoReuse(t *testing.T) {
)
go func() {
var err error
server, err = Server(context.Background(), serverConn, serverKey)
server, err = Server(context.Background(), serverConn, serverKey, nil)
serverErr <- err
}()
@@ -139,6 +139,9 @@ func TestNoReuse(t *testing.T) {
t.Fatalf("server wire traffic seen twice")
}
packets[serverWire] = true
server.Close()
client.Close()
}
}
@@ -169,7 +172,7 @@ func TestTampering(t *testing.T) {
serverErr = make(chan error, 1)
)
go func() {
_, err := Server(context.Background(), serverConn, serverKey)
_, err := Server(context.Background(), serverConn, serverKey, nil)
// If the server failed, we have to close the Conn to
// unblock the client.
if err != nil {
@@ -197,7 +200,7 @@ func TestTampering(t *testing.T) {
serverErr = make(chan error, 1)
)
go func() {
_, err := Server(context.Background(), serverConn, serverKey)
_, err := Server(context.Background(), serverConn, serverKey, nil)
serverErr <- err
}()
@@ -222,7 +225,7 @@ func TestTampering(t *testing.T) {
serverErr = make(chan error, 1)
)
go func() {
server, err := Server(context.Background(), serverConn, serverKey)
server, err := Server(context.Background(), serverConn, serverKey, nil)
serverErr <- err
_, err = io.WriteString(server, strings.Repeat("a", 14))
serverErr <- err
@@ -263,7 +266,7 @@ func TestTampering(t *testing.T) {
serverErr = make(chan error, 1)
)
go func() {
server, err := Server(context.Background(), serverConn, serverKey)
server, err := Server(context.Background(), serverConn, serverKey, nil)
serverErr <- err
var bs [100]byte
// The server needs a timeout if the tampering is hitting the length header.

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package noise
package controlbase
import (
"context"
@@ -29,7 +29,7 @@ func TestInteropClient(t *testing.T) {
)
go func() {
server, err := Server(context.Background(), s2, controlKey)
server, err := Server(context.Background(), s2, controlKey, nil)
serverErr <- err
if err != nil {
return

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package noise
package controlbase
import "encoding/binary"

View File

@@ -24,7 +24,7 @@ IK:
* PARAMETERS *
* ---------------------------------------------------------------- */
package noise
package controlbase
import (
"crypto/rand"

View File

@@ -7,6 +7,7 @@ package controlclient
import (
"context"
"fmt"
"net/http"
"sync"
"time"
@@ -91,7 +92,7 @@ func NewNoStart(opts Options) (*Auto, error) {
return nil, err
}
if opts.Logf == nil {
opts.Logf = func(fmt string, args ...interface{}) {}
opts.Logf = func(fmt string, args ...any) {}
}
if opts.TimeNow == nil {
opts.TimeNow = time.Now
@@ -266,9 +267,9 @@ func (c *Auto) authRoutine() {
goal := c.loginGoal
ctx := c.authCtx
if goal != nil {
c.logf("authRoutine: %s; wantLoggedIn=%v", c.state, goal.wantLoggedIn)
c.logf("[v1] authRoutine: %s; wantLoggedIn=%v", c.state, goal.wantLoggedIn)
} else {
c.logf("authRoutine: %s; goal=nil paused=%v", c.state, c.paused)
c.logf("[v1] authRoutine: %s; goal=nil paused=%v", c.state, c.paused)
}
c.mu.Unlock()
@@ -339,11 +340,9 @@ func (c *Auto) authRoutine() {
continue
}
if url != "" {
if goal.url != "" {
err = fmt.Errorf("[unexpected] server required a new URL?")
report(err, "WaitLoginURL")
}
// goal.url ought to be empty here.
// However, not all control servers get this right,
// and logging about it here just generates noise.
c.mu.Lock()
c.loginGoal = &LoginGoal{
wantLoggedIn: true,
@@ -416,7 +415,7 @@ func (c *Auto) mapRoutine() {
}
continue
}
c.logf("mapRoutine: %s", c.state)
c.logf("[v1] mapRoutine: %s", c.state)
loggedIn := c.loggedIn
ctx := c.mapCtx
c.mu.Unlock()
@@ -447,9 +446,9 @@ func (c *Auto) mapRoutine() {
select {
case <-ctx.Done():
c.logf("mapRoutine: context done.")
c.logf("[v1] mapRoutine: context done.")
case <-c.newMapCh:
c.logf("mapRoutine: new map needed while idle.")
c.logf("[v1] mapRoutine: new map needed while idle.")
}
} else {
// Be sure this is false when we're not inside
@@ -659,6 +658,10 @@ func (c *Auto) Logout(ctx context.Context) error {
}
}
func (c *Auto) SetExpirySooner(ctx context.Context, expiry time.Time) error {
return c.direct.SetExpirySooner(ctx, expiry)
}
// UpdateEndpoints sets the client's discovered endpoints and sends
// them to the control server if they've changed.
//
@@ -679,6 +682,7 @@ func (c *Auto) Shutdown() {
c.mu.Lock()
inSendStatus := c.inSendStatus
closed := c.closed
direct := c.direct
if !closed {
c.closed = true
c.statusFunc = nil
@@ -693,6 +697,9 @@ func (c *Auto) Shutdown() {
<-c.authDone
c.cancelMapUnsafely()
<-c.mapDone
if direct != nil {
direct.Close()
}
c.logf("Client.Shutdown done.")
}
}
@@ -719,3 +726,7 @@ func (c *Auto) TestOnlyTimeNow() time.Time {
func (c *Auto) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) error {
return c.direct.SetDNS(ctx, req)
}
func (c *Auto) DoNoiseRequest(req *http.Request) (*http.Response, error) {
return c.direct.DoNoiseRequest(req)
}

View File

@@ -11,6 +11,8 @@ package controlclient
import (
"context"
"net/http"
"time"
"tailscale.com/tailcfg"
)
@@ -45,6 +47,9 @@ type Client interface {
// Logout starts a synchronous logout process. It doesn't return
// until the logout operation has been completed.
Logout(context.Context) error
// SetExpirySooner sets the node's expiry time via the controlclient,
// as long as it's shorter than the current expiry time.
SetExpirySooner(context.Context, time.Time) error
// SetPaused pauses or unpauses the controlclient activity as much
// as possible, without losing its internal state, to minimize
// unnecessary network activity.
@@ -78,6 +83,9 @@ type Client interface {
// SetDNS sends the SetDNSRequest request to the control plane server,
// requesting a DNS record be created or updated.
SetDNS(context.Context, *tailcfg.SetDNSRequest) error
// DoNoiseRequest sends an HTTP request to the control plane
// over the Noise transport.
DoNoiseRequest(*http.Request) (*http.Response, error)
}
// UserVisibleError is an error that should be shown to users.

View File

@@ -21,15 +21,16 @@ import (
"os/exec"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"go4.org/mem"
"golang.org/x/sync/singleflight"
"inet.af/netaddr"
"tailscale.com/control/controlknobs"
"tailscale.com/envknob"
"tailscale.com/health"
"tailscale.com/hostinfo"
"tailscale.com/ipn/ipnstate"
@@ -47,6 +48,7 @@ import (
"tailscale.com/types/opt"
"tailscale.com/types/persist"
"tailscale.com/util/clientmetric"
"tailscale.com/util/multierr"
"tailscale.com/util/systemd"
"tailscale.com/wgengine/monitor"
)
@@ -68,8 +70,13 @@ type Direct struct {
skipIPForwardingCheck bool
pinger Pinger
mu sync.Mutex // mutex guards the following fields
serverKey key.MachinePublic
mu sync.Mutex // mutex guards the following fields
serverKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key
serverNoiseKey key.MachinePublic
sfGroup singleflight.Group // protects noiseClient creation.
noiseClient *noiseClient
persist persist.Persist
authKey string
tryingNewKey key.NodePrivate
@@ -168,6 +175,10 @@ func NewDirect(opts Options) (*Direct, error) {
tr.DialContext = dnscache.Dialer(dialer.DialContext, dnsCache)
tr.DialTLSContext = dnscache.TLSDialer(dialer.DialContext, dnsCache, tr.TLSClientConfig)
tr.ForceAttemptHTTP2 = true
// Disable implicit gzip compression; the various
// handlers (register, map, set-dns, etc) do their own
// zstd compression per naclbox.
tr.DisableCompression = true
httpc = &http.Client{Transport: tr}
}
@@ -196,6 +207,19 @@ func NewDirect(opts Options) (*Direct, error) {
return c, nil
}
// Close closes the underlying Noise connection(s).
func (c *Direct) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.noiseClient != nil {
if err := c.noiseClient.Close(); err != nil {
return err
}
}
c.noiseClient = nil
return nil
}
// SetHostinfo clones the provided Hostinfo and remembers it for the
// next update. It reports whether the Hostinfo has changed.
func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool {
@@ -210,7 +234,7 @@ func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool {
}
c.hostinfo = hi.Clone()
j, _ := json.Marshal(c.hostinfo)
c.logf("HostInfo: %s", j)
c.logf("[v1] HostInfo: %s", j)
return true
}
@@ -241,10 +265,10 @@ func (c *Direct) GetPersist() persist.Persist {
}
func (c *Direct) TryLogout(ctx context.Context) error {
c.logf("direct.TryLogout()")
c.logf("[v1] direct.TryLogout()")
mustRegen, newURL, err := c.doLogin(ctx, loginOpt{Logout: true})
c.logf("TryLogout control response: mustRegen=%v, newURL=%v, err=%v", mustRegen, newURL, err)
c.logf("[v1] TryLogout control response: mustRegen=%v, newURL=%v, err=%v", mustRegen, newURL, err)
c.mu.Lock()
c.persist = persist.Persist{}
@@ -254,7 +278,7 @@ func (c *Direct) TryLogout(ctx context.Context) error {
}
func (c *Direct) TryLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags) (url string, err error) {
c.logf("direct.TryLogin(token=%v, flags=%v)", t != nil, flags)
c.logf("[v1] direct.TryLogin(token=%v, flags=%v)", t != nil, flags)
return c.doLoginOrRegen(ctx, loginOpt{Token: t, Flags: flags})
}
@@ -262,7 +286,7 @@ func (c *Direct) TryLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags Log
//
// On success, newURL and err will both be nil.
func (c *Direct) WaitLoginURL(ctx context.Context, url string) (newURL string, err error) {
c.logf("direct.WaitLoginURL")
c.logf("[v1] direct.WaitLoginURL")
return c.doLoginOrRegen(ctx, loginOpt{URL: url})
}
@@ -278,12 +302,33 @@ func (c *Direct) doLoginOrRegen(ctx context.Context, opt loginOpt) (newURL strin
return url, err
}
// SetExpirySooner attempts to shorten the expiry to the specified time.
func (c *Direct) SetExpirySooner(ctx context.Context, expiry time.Time) error {
c.logf("[v1] direct.SetExpirySooner()")
newURL, err := c.doLoginOrRegen(ctx, loginOpt{Expiry: &expiry})
c.logf("[v1] SetExpirySooner control response: newURL=%v, err=%v", newURL, err)
return err
}
type loginOpt struct {
Token *tailcfg.Oauth2Token
Flags LoginFlags
Regen bool
Regen bool // generate a new nodekey, can be overridden in doLogin
URL string
Logout bool
Logout bool // set the expiry to the far past, expiring the node
// Expiry, if non-nil, attempts to set the node expiry to the
// specified time and cannot be used to extend the expiry.
// It is ignored if Logout is set since Logout works by setting a
// expiry time in the far past.
Expiry *time.Time
}
// httpClient provides a common interface for the noiseClient and
// the NaCl box http.Client.
type httpClient interface {
Do(req *http.Request) (*http.Response, error)
}
func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, newURL string, err error) {
@@ -291,6 +336,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
persist := c.persist
tryingNewKey := c.tryingNewKey
serverKey := c.serverKey
serverNoiseKey := c.serverNoiseKey
authKey := c.authKey
hi := c.hostinfo.Clone()
backendLogID := hi.BackendLogID
@@ -322,18 +368,27 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
c.logf("doLogin(regen=%v, hasUrl=%v)", regen, opt.URL != "")
if serverKey.IsZero() {
var err error
serverKey, err = loadServerKey(ctx, c.httpc, c.serverURL)
keys, err := loadServerPubKeys(ctx, c.httpc, c.serverURL)
if err != nil {
return regen, opt.URL, err
}
c.logf("control server key %s from %s", serverKey.ShortString(), c.serverURL)
c.mu.Lock()
c.serverKey = serverKey
c.serverKey = keys.LegacyPublicKey
c.serverNoiseKey = keys.PublicKey
c.mu.Unlock()
}
serverKey = keys.LegacyPublicKey
serverNoiseKey = keys.PublicKey
// For servers supporting the Noise transport,
// proactively shut down our TLS TCP connection.
// We're not going to need it and it's nicer to the
// server.
if !serverNoiseKey.IsZero() {
c.httpc.CloseIdleConnections()
}
}
var oldNodeKey key.NodePublic
switch {
case opt.Logout:
@@ -374,6 +429,8 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
}
if opt.Logout {
request.Expiry = time.Unix(123, 0) // far in the past
} else if opt.Expiry != nil {
request.Expiry = *opt.Expiry
}
c.logf("RegisterReq: onode=%v node=%v fup=%v",
request.OldNodeKey.ShortString(),
@@ -401,22 +458,32 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
c.logf("RegisterRequest: %s", j)
}
bodyData, err := encode(request, serverKey, machinePrivKey)
// URL and httpc are protocol specific.
var url string
var httpc httpClient
if serverNoiseKey.IsZero() {
httpc = c.httpc
url = fmt.Sprintf("%s/machine/%s", c.serverURL, machinePrivKey.Public().UntypedHexString())
} else {
request.Version = tailcfg.CurrentCapabilityVersion
httpc, err = c.getNoiseClient()
if err != nil {
return regen, opt.URL, fmt.Errorf("getNoiseClient: %w", err)
}
url = fmt.Sprintf("%s/machine/register", c.serverURL)
url = strings.Replace(url, "http:", "https:", 1)
}
bodyData, err := encode(request, serverKey, serverNoiseKey, machinePrivKey)
if err != nil {
return regen, opt.URL, err
}
body := bytes.NewReader(bodyData)
u := fmt.Sprintf("%s/machine/%s", c.serverURL, machinePrivKey.Public().UntypedHexString())
req, err := http.NewRequest("POST", u, body)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyData))
if err != nil {
return regen, opt.URL, err
}
req = req.WithContext(ctx)
res, err := c.httpc.Do(req)
res, err := httpc.Do(req)
if err != nil {
return regen, opt.URL, fmt.Errorf("register request: %v", err)
return regen, opt.URL, fmt.Errorf("register request: %w", err)
}
if res.StatusCode != 200 {
msg, _ := ioutil.ReadAll(res.Body)
@@ -425,7 +492,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
res.StatusCode, strings.TrimSpace(string(msg)))
}
resp := tailcfg.RegisterResponse{}
if err := decode(res, &resp, serverKey, machinePrivKey); err != nil {
if err := decode(res, &resp, serverKey, serverNoiseKey, machinePrivKey); err != nil {
c.logf("error decoding RegisterResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
return regen, opt.URL, fmt.Errorf("register request: %v", err)
}
@@ -465,7 +532,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
if resp.AuthURL != "" {
c.logf("AuthURL is %v", resp.AuthURL)
} else {
c.logf("No AuthURL")
c.logf("[v1] No AuthURL")
}
c.mu.Lock()
@@ -516,7 +583,7 @@ func (c *Direct) newEndpoints(localPort uint16, endpoints []tailcfg.Endpoint) (c
for _, ep := range endpoints {
epStrs = append(epStrs, ep.Addr.String())
}
c.logf("client.newEndpoints(%v, %v)", localPort, epStrs)
c.logf("[v2] client.newEndpoints(%v, %v)", localPort, epStrs)
c.localPort = localPort
c.endpoints = append(c.endpoints[:0], endpoints...)
if len(endpoints) > 0 {
@@ -572,6 +639,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
persist := c.persist
serverURL := c.serverURL
serverKey := c.serverKey
serverNoiseKey := c.serverNoiseKey
hi := c.hostinfo.Clone()
backendLogID := hi.BackendLogID
localPort := c.localPort
@@ -610,7 +678,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
}
request := &tailcfg.MapRequest{
Version: tailcfg.CurrentMapRequestVersion,
Version: tailcfg.CurrentCapabilityVersion,
KeepAlive: c.keepAlive,
NodeKey: persist.PrivateNodeKey.Public(),
DiscoKey: c.discoPubKey,
@@ -654,7 +722,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
request.ReadOnly = true
}
bodyData, err := encode(request, serverKey, machinePrivKey)
bodyData, err := encode(request, serverKey, serverNoiseKey, machinePrivKey)
if err != nil {
vlogf("netmap: encode: %v", err)
return err
@@ -665,14 +733,28 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
machinePubKey := machinePrivKey.Public()
t0 := time.Now()
u := fmt.Sprintf("%s/machine/%s/map", serverURL, machinePubKey.UntypedHexString())
req, err := http.NewRequestWithContext(ctx, "POST", u, bytes.NewReader(bodyData))
// Url and httpc are protocol specific.
var url string
var httpc httpClient
if serverNoiseKey.IsZero() {
httpc = c.httpc
url = fmt.Sprintf("%s/machine/%s/map", serverURL, machinePubKey.UntypedHexString())
} else {
httpc, err = c.getNoiseClient()
if err != nil {
return fmt.Errorf("getNoiseClient: %w", err)
}
url = fmt.Sprintf("%s/machine/map", serverURL)
url = strings.Replace(url, "http:", "https:", 1)
}
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyData))
if err != nil {
return err
}
res, err := c.httpc.Do(req)
res, err := httpc.Do(req)
if err != nil {
vlogf("netmap: Do: %v", err)
return err
@@ -768,6 +850,9 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
go answerPing(c.logf, c.httpc, pr)
}
if resp.ControlTime != nil && !resp.ControlTime.IsZero() {
c.logf.JSON(1, "controltime", resp.ControlTime.UTC())
}
if resp.KeepAlive {
vlogf("netmap: got keep-alive")
} else {
@@ -821,10 +906,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
if Debug.StripEndpoints {
for _, p := range resp.Peers {
// We need at least one endpoint here for now else
// other code doesn't even create the discoEndpoint.
// TODO(bradfitz): fix that and then just nil this out.
p.Endpoints = []string{"127.9.9.9:456"}
p.Endpoints = nil
}
}
if Debug.StripCaps {
@@ -861,7 +943,9 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
return nil
}
func decode(res *http.Response, v interface{}, serverKey key.MachinePublic, mkey key.MachinePrivate) error {
// decode JSON decodes the res.Body into v. If serverNoiseKey is not specified,
// it uses the serverKey and mkey to decode the message from the NaCl-crypto-box.
func decode(res *http.Response, v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.MachinePrivate) error {
defer res.Body.Close()
msg, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
if err != nil {
@@ -870,24 +954,37 @@ func decode(res *http.Response, v interface{}, serverKey key.MachinePublic, mkey
if res.StatusCode != 200 {
return fmt.Errorf("%d: %v", res.StatusCode, string(msg))
}
if !serverNoiseKey.IsZero() {
return json.Unmarshal(msg, v)
}
return decodeMsg(msg, v, serverKey, mkey)
}
var (
debugMap, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_MAP"))
debugRegister, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_REGISTER"))
debugMap = envknob.Bool("TS_DEBUG_MAP")
debugRegister = envknob.Bool("TS_DEBUG_REGISTER")
)
var jsonEscapedZero = []byte(`\u0000`)
func (c *Direct) decodeMsg(msg []byte, v interface{}, machinePrivKey key.MachinePrivate) error {
// decodeMsg is responsible for uncompressing msg and unmarshaling into v.
// If c.serverNoiseKey is not specified, it uses the c.serverKey and mkey
// to first the decrypt msg from the NaCl-crypto-box.
func (c *Direct) decodeMsg(msg []byte, v any, mkey key.MachinePrivate) error {
c.mu.Lock()
serverKey := c.serverKey
serverNoiseKey := c.serverNoiseKey
c.mu.Unlock()
decrypted, ok := machinePrivKey.OpenFrom(serverKey, msg)
if !ok {
return errors.New("cannot decrypt response")
var decrypted []byte
if serverNoiseKey.IsZero() {
var ok bool
decrypted, ok = mkey.OpenFrom(serverKey, msg)
if !ok {
return errors.New("cannot decrypt response")
}
} else {
decrypted = msg
}
var b []byte
if c.newDecompressor == nil {
@@ -919,7 +1016,7 @@ func (c *Direct) decodeMsg(msg []byte, v interface{}, machinePrivKey key.Machine
}
func decodeMsg(msg []byte, v interface{}, serverKey key.MachinePublic, machinePrivKey key.MachinePrivate) error {
func decodeMsg(msg []byte, v any, serverKey key.MachinePublic, machinePrivKey key.MachinePrivate) error {
decrypted, ok := machinePrivKey.OpenFrom(serverKey, msg)
if !ok {
return errors.New("cannot decrypt response")
@@ -933,7 +1030,9 @@ func decodeMsg(msg []byte, v interface{}, serverKey key.MachinePublic, machinePr
return nil
}
func encode(v interface{}, serverKey key.MachinePublic, mkey key.MachinePrivate) ([]byte, error) {
// encode JSON encodes v. If serverNoiseKey is not specified, it uses the serverKey and mkey to
// seal the message into a NaCl-crypto-box.
func encode(v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.MachinePrivate) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
@@ -943,32 +1042,45 @@ func encode(v interface{}, serverKey key.MachinePublic, mkey key.MachinePrivate)
log.Printf("MapRequest: %s", b)
}
}
if !serverNoiseKey.IsZero() {
return b, nil
}
return mkey.SealTo(serverKey, b), nil
}
func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (key.MachinePublic, error) {
req, err := http.NewRequest("GET", serverURL+"/key", nil)
func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string) (*tailcfg.OverTLSPublicKeyResponse, error) {
keyURL := fmt.Sprintf("%v/key?v=%d", serverURL, tailcfg.CurrentCapabilityVersion)
req, err := http.NewRequestWithContext(ctx, "GET", keyURL, nil)
if err != nil {
return key.MachinePublic{}, fmt.Errorf("create control key request: %v", err)
return nil, fmt.Errorf("create control key request: %v", err)
}
req = req.WithContext(ctx)
res, err := httpc.Do(req)
if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %v", err)
return nil, fmt.Errorf("fetch control key: %v", err)
}
defer res.Body.Close()
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<16))
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 64<<10))
if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key response: %v", err)
return nil, fmt.Errorf("fetch control key response: %v", err)
}
if res.StatusCode != 200 {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %d: %s", res.StatusCode, string(b))
return nil, fmt.Errorf("fetch control key: %d", res.StatusCode)
}
var out tailcfg.OverTLSPublicKeyResponse
jsonErr := json.Unmarshal(b, &out)
if jsonErr == nil {
return &out, nil
}
// Some old control servers might not be updated to send the new format.
// Accept the old pre-JSON format too.
out = tailcfg.OverTLSPublicKeyResponse{}
k, err := key.ParseMachinePublicUntyped(mem.B(b))
if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %v", err)
return nil, multierr.New(jsonErr, err)
}
return k, nil
out.LegacyPublicKey = k
return &out, nil
}
// Debug contains temporary internal-only debug knobs.
@@ -985,26 +1097,14 @@ type debug struct {
func initDebug() debug {
return debug{
NetMap: envBool("TS_DEBUG_NETMAP"),
ProxyDNS: envBool("TS_DEBUG_PROXY_DNS"),
StripEndpoints: envBool("TS_DEBUG_STRIP_ENDPOINTS"),
StripCaps: envBool("TS_DEBUG_STRIP_CAPS"),
Disco: os.Getenv("TS_DEBUG_USE_DISCO") == "" || envBool("TS_DEBUG_USE_DISCO"),
NetMap: envknob.Bool("TS_DEBUG_NETMAP"),
ProxyDNS: envknob.Bool("TS_DEBUG_PROXY_DNS"),
StripEndpoints: envknob.Bool("TS_DEBUG_STRIP_ENDPOINTS"),
StripCaps: envknob.Bool("TS_DEBUG_STRIP_CAPS"),
Disco: envknob.BoolDefaultTrue("TS_DEBUG_USE_DISCO"),
}
}
func envBool(k string) bool {
e := os.Getenv(k)
if e == "" {
return false
}
v, err := strconv.ParseBool(e)
if err != nil {
panic(fmt.Sprintf("invalid non-bool %q for env var %q", e, k))
}
return v
}
var clockNow = time.Now
// opt.Bool configs from control.
@@ -1198,6 +1298,78 @@ func sleepAsRequested(ctx context.Context, logf logger.Logf, timeoutReset chan<-
}
}
// getNoiseClient returns the noise client, creating one if one doesn't exist.
func (c *Direct) getNoiseClient() (*noiseClient, error) {
c.mu.Lock()
serverNoiseKey := c.serverNoiseKey
nc := c.noiseClient
c.mu.Unlock()
if serverNoiseKey.IsZero() {
return nil, errors.New("zero serverNoiseKey")
}
if nc != nil {
return nc, nil
}
np, err, _ := c.sfGroup.Do("noise", func() (any, error) {
k, err := c.getMachinePrivKey()
if err != nil {
return nil, err
}
nc, err = newNoiseClient(k, serverNoiseKey, c.serverURL)
if err != nil {
return nil, err
}
c.mu.Lock()
defer c.mu.Unlock()
c.noiseClient = nc
return nc, nil
})
if err != nil {
return nil, err
}
return np.(*noiseClient), nil
}
// setDNSNoise sends the SetDNSRequest request to the control plane server over Noise,
// requesting a DNS record be created or updated.
func (c *Direct) setDNSNoise(ctx context.Context, req *tailcfg.SetDNSRequest) error {
newReq := *req
newReq.Version = tailcfg.CurrentCapabilityVersion
np, err := c.getNoiseClient()
if err != nil {
return err
}
bodyData, err := json.Marshal(newReq)
if err != nil {
return err
}
res, err := np.Post(fmt.Sprintf("https://%v/%v", np.serverHost, "machine/set-dns"), "application/json", bytes.NewReader(bodyData))
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
msg, _ := ioutil.ReadAll(res.Body)
return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
}
var setDNSRes tailcfg.SetDNSResponse
if err := json.NewDecoder(res.Body).Decode(&setDNSRes); err != nil {
c.logf("error decoding SetDNSResponse: %v", err)
return fmt.Errorf("set-dns-response: %w", err)
}
return nil
}
// noiseConfigured reports whether the client can communicate with Control
// over Noise.
func (c *Direct) noiseConfigured() bool {
c.mu.Lock()
defer c.mu.Unlock()
return !c.serverNoiseKey.IsZero()
}
// SetDNS sends the SetDNSRequest request to the control plane server,
// requesting a DNS record be created or updated.
func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err error) {
@@ -1207,6 +1379,9 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err er
metricSetDNSError.Add(1)
}
}()
if c.noiseConfigured() {
return c.setDNSNoise(ctx, req)
}
c.mu.Lock()
serverKey := c.serverKey
c.mu.Unlock()
@@ -1222,7 +1397,9 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err er
return errors.New("getMachinePrivKey returned zero key")
}
bodyData, err := encode(req, serverKey, machinePrivKey)
// TODO(maisem): dedupe this codepath from SetDNSNoise.
var serverNoiseKey key.MachinePublic
bodyData, err := encode(req, serverKey, serverNoiseKey, machinePrivKey)
if err != nil {
return err
}
@@ -1242,15 +1419,23 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err er
msg, _ := ioutil.ReadAll(res.Body)
return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
}
var setDNSRes struct{} // no fields yet
if err := decode(res, &setDNSRes, serverKey, machinePrivKey); err != nil {
var setDNSRes tailcfg.SetDNSResponse
if err := decode(res, &setDNSRes, serverKey, serverNoiseKey, machinePrivKey); err != nil {
c.logf("error decoding SetDNSResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
return fmt.Errorf("set-dns-response: %v", err)
return fmt.Errorf("set-dns-response: %w", err)
}
return nil
}
func (c *Direct) DoNoiseRequest(req *http.Request) (*http.Response, error) {
nc, err := c.getNoiseClient()
if err != nil {
return nil, err
}
return nc.Do(req)
}
// tsmpPing sends a Ping to pr.IP, and sends an http request back to pr.URL
// with ping response data.
func tsmpPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger) error {

View File

@@ -6,11 +6,10 @@ package controlclient
import (
"log"
"os"
"sort"
"strconv"
"inet.af/netaddr"
"tailscale.com/envknob"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -40,6 +39,7 @@ type mapSession struct {
lastDERPMap *tailcfg.DERPMap
lastUserProfile map[tailcfg.UserID]tailcfg.UserProfile
lastParsedPacketFilter []filter.Match
lastSSHPolicy *tailcfg.SSHPolicy
collectServices bool
previousPeers []*tailcfg.Node // for delta-purposes
lastDomain string
@@ -98,6 +98,9 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
if c := resp.DNSConfig; c != nil {
ms.lastDNSConfig = c
}
if p := resp.SSHPolicy; p != nil {
ms.lastSSHPolicy = p
}
if v, ok := resp.CollectServices.Get(); ok {
ms.collectServices = v
@@ -118,6 +121,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
Domain: ms.lastDomain,
DNS: *ms.lastDNSConfig,
PacketFilter: ms.lastParsedPacketFilter,
SSHPolicy: ms.lastSSHPolicy,
CollectServices: ms.collectServices,
DERPMap: ms.lastDERPMap,
Debug: resp.Debug,
@@ -134,7 +138,9 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
nm.Name = node.Name
nm.Addresses = filterSelfAddresses(node.Addresses)
nm.User = node.User
nm.Hostinfo = node.Hostinfo
if node.Hostinfo.Valid() {
nm.Hostinfo = *node.Hostinfo.AsStruct()
}
if node.MachineAuthorized {
nm.MachineStatus = tailcfg.MachineAuthorized
} else {
@@ -289,7 +295,7 @@ func cloneNodes(v1 []*tailcfg.Node) []*tailcfg.Node {
return v2
}
var debugSelfIPv6Only, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_SELF_V6_ONLY"))
var debugSelfIPv6Only = envknob.Bool("TS_DEBUG_SELF_V6_ONLY")
func filterSelfAddresses(in []netaddr.IPPrefix) (ret []netaddr.IPPrefix) {
switch {

View File

@@ -0,0 +1,159 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package controlclient
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"
"golang.org/x/net/http2"
"tailscale.com/control/controlbase"
"tailscale.com/control/controlhttp"
"tailscale.com/types/key"
"tailscale.com/util/multierr"
)
// noiseConn is a wrapper around controlbase.Conn.
// It allows attaching an ID to a connection to allow
// cleaning up references in the pool when the connection
// is closed.
type noiseConn struct {
*controlbase.Conn
id int
pool *noiseClient
}
func (c *noiseConn) Close() error {
if err := c.Conn.Close(); err != nil {
return err
}
c.pool.connClosed(c.id)
return nil
}
// noiseClient provides a http.Client to connect to tailcontrol over
// the ts2021 protocol.
type noiseClient struct {
*http.Client // HTTP client used to talk to tailcontrol
privKey key.MachinePrivate
serverPubKey key.MachinePublic
serverHost string // the host:port part of serverURL
// mu only protects the following variables.
mu sync.Mutex
nextID int
connPool map[int]*noiseConn // active connections not yet closed; see noiseConn.Close
}
// newNoiseClient returns a new noiseClient for the provided server and machine key.
// serverURL is of the form https://<host>:<port> (no trailing slash).
func newNoiseClient(priKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string) (*noiseClient, error) {
u, err := url.Parse(serverURL)
if err != nil {
return nil, err
}
var host string
if u.Port() != "" {
// If there is an explicit port specified use it.
host = u.Host
} else {
// Otherwise, controlhttp.Dial expects an http endpoint.
host = fmt.Sprintf("%v:80", u.Hostname())
}
np := &noiseClient{
serverPubKey: serverPubKey,
privKey: priKey,
serverHost: host,
}
// Create the HTTP/2 Transport using a net/http.Transport
// (which only does HTTP/1) because it's the only way to
// configure certain properties on the http2.Transport. But we
// never actually use the net/http.Transport for any HTTP/1
// requests.
h2Transport, err := http2.ConfigureTransports(&http.Transport{
IdleConnTimeout: time.Minute,
})
if err != nil {
return nil, err
}
// Let the HTTP/2 Transport think it's dialing out using TLS,
// but it's actually our Noise dialer:
h2Transport.DialTLS = np.dial
// ConfigureTransports assumes it's being used to wire up an HTTP/1
// and HTTP/2 Transport together, so its returned http2.Transport
// has a ConnPool already initialized that's configured to not dial
// (assuming it's only called from the HTTP/1 Transport). But we
// want it to dial, so nil it out before use. On first use it has
// a sync.Once that lazily initializes the ConnPool to its default
// one that dials.
h2Transport.ConnPool = nil
np.Client = &http.Client{Transport: h2Transport}
return np, nil
}
// connClosed removes the connection with the provided ID from the pool
// of active connections.
func (nc *noiseClient) connClosed(id int) {
nc.mu.Lock()
defer nc.mu.Unlock()
delete(nc.connPool, id)
}
// Close closes all the underlying noise connections.
// It is a no-op and returns nil if the connection is already closed.
func (nc *noiseClient) Close() error {
nc.mu.Lock()
conns := nc.connPool
nc.connPool = nil
nc.mu.Unlock()
var errors []error
for _, c := range conns {
if err := c.Close(); err != nil {
errors = append(errors, err)
}
}
return multierr.New(errors...)
}
// dial opens a new connection to tailcontrol, fetching the server noise key
// if not cached. It implements the signature needed by http2.Transport.DialTLS
// but ignores all params as it only dials out to the server the noiseClient was
// created for.
func (nc *noiseClient) dial(_, _ string, _ *tls.Config) (net.Conn, error) {
nc.mu.Lock()
connID := nc.nextID
if nc.connPool == nil {
nc.connPool = make(map[int]*noiseConn)
}
nc.nextID++
nc.mu.Unlock()
// Timeout is a little arbitrary, but plenty long enough for even the
// highest latency links.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := controlhttp.Dial(ctx, nc.serverHost, nc.privKey, nc.serverPubKey)
if err != nil {
return nil, err
}
nc.mu.Lock()
defer nc.mu.Unlock()
ncc := &noiseConn{Conn: conn, id: connID, pool: nc}
nc.connPool[ncc.id] = ncc
return ncc, nil
}

View File

@@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"sync"
"time"
"github.com/tailscale/certstore"
"tailscale.com/tailcfg"
@@ -73,23 +74,46 @@ func isSubjectInChain(subject string, chain []*x509.Certificate) bool {
return false
}
func selectIdentityFromSlice(subject string, ids []certstore.Identity) (certstore.Identity, []*x509.Certificate) {
func selectIdentityFromSlice(subject string, ids []certstore.Identity, now time.Time) (certstore.Identity, []*x509.Certificate) {
var bestCandidate struct {
id certstore.Identity
chain []*x509.Certificate
}
for _, id := range ids {
chain, err := id.CertificateChain()
if err != nil {
continue
}
if len(chain) < 1 {
continue
}
if !isSupportedCertificate(chain[0]) {
continue
}
if isSubjectInChain(subject, chain) {
return id, chain
if now.Before(chain[0].NotBefore) || now.After(chain[0].NotAfter) {
// Certificate is not valid at this time
continue
}
if !isSubjectInChain(subject, chain) {
continue
}
// Select the most recently issued certificate. If there is a tie, pick
// one arbitrarily.
if len(bestCandidate.chain) > 0 && bestCandidate.chain[0].NotBefore.After(chain[0].NotBefore) {
continue
}
bestCandidate.id = id
bestCandidate.chain = chain
}
return nil, nil
return bestCandidate.id, bestCandidate.chain
}
// findIdentity locates an identity from the Windows or Darwin certificate
@@ -105,7 +129,7 @@ func findIdentity(subject string, st certstore.Store) (certstore.Identity, []*x5
return nil, nil, err
}
selected, chain := selectIdentityFromSlice(subject, ids)
selected, chain := selectIdentityFromSlice(subject, ids, time.Now())
for _, id := range ids {
if id != selected {

View File

@@ -0,0 +1,238 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows && cgo
// +build windows,cgo
package controlclient
import (
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"reflect"
"testing"
"time"
"github.com/tailscale/certstore"
)
const (
testRootCommonName = "testroot"
testRootSubject = "CN=testroot"
)
type testIdentity struct {
chain []*x509.Certificate
}
func makeChain(rootCommonName string, notBefore, notAfter time.Time) []*x509.Certificate {
return []*x509.Certificate{
{
NotBefore: notBefore,
NotAfter: notAfter,
PublicKeyAlgorithm: x509.RSA,
},
{
Subject: pkix.Name{
CommonName: rootCommonName,
},
PublicKeyAlgorithm: x509.RSA,
},
}
}
func (t *testIdentity) Certificate() (*x509.Certificate, error) {
return t.chain[0], nil
}
func (t *testIdentity) CertificateChain() ([]*x509.Certificate, error) {
return t.chain, nil
}
func (t *testIdentity) Signer() (crypto.Signer, error) {
return nil, errors.New("not implemented")
}
func (t *testIdentity) Delete() error {
return errors.New("not implemented")
}
func (t *testIdentity) Close() {}
func TestSelectIdentityFromSlice(t *testing.T) {
var times []time.Time
for _, ts := range []string{
"2000-01-01T00:00:00Z",
"2001-01-01T00:00:00Z",
"2002-01-01T00:00:00Z",
"2003-01-01T00:00:00Z",
} {
tm, err := time.Parse(time.RFC3339, ts)
if err != nil {
t.Fatal(err)
}
times = append(times, tm)
}
tests := []struct {
name string
subject string
ids []certstore.Identity
now time.Time
// wantIndex is an index into ids, or -1 for nil.
wantIndex int
}{
{
name: "single unexpired identity",
subject: testRootSubject,
ids: []certstore.Identity{
&testIdentity{
chain: makeChain(testRootCommonName, times[0], times[2]),
},
},
now: times[1],
wantIndex: 0,
},
{
name: "single expired identity",
subject: testRootSubject,
ids: []certstore.Identity{
&testIdentity{
chain: makeChain(testRootCommonName, times[0], times[1]),
},
},
now: times[2],
wantIndex: -1,
},
{
name: "unrelated ids",
subject: testRootSubject,
ids: []certstore.Identity{
&testIdentity{
chain: makeChain("something", times[0], times[2]),
},
&testIdentity{
chain: makeChain(testRootCommonName, times[0], times[2]),
},
&testIdentity{
chain: makeChain("else", times[0], times[2]),
},
},
now: times[1],
wantIndex: 1,
},
{
name: "expired with unrelated ids",
subject: testRootSubject,
ids: []certstore.Identity{
&testIdentity{
chain: makeChain("something", times[0], times[3]),
},
&testIdentity{
chain: makeChain(testRootCommonName, times[0], times[1]),
},
&testIdentity{
chain: makeChain("else", times[0], times[3]),
},
},
now: times[2],
wantIndex: -1,
},
{
name: "one expired",
subject: testRootSubject,
ids: []certstore.Identity{
&testIdentity{
chain: makeChain(testRootCommonName, times[0], times[1]),
},
&testIdentity{
chain: makeChain(testRootCommonName, times[1], times[3]),
},
},
now: times[2],
wantIndex: 1,
},
{
name: "two certs both unexpired",
subject: testRootSubject,
ids: []certstore.Identity{
&testIdentity{
chain: makeChain(testRootCommonName, times[0], times[3]),
},
&testIdentity{
chain: makeChain(testRootCommonName, times[1], times[3]),
},
},
now: times[2],
wantIndex: 1,
},
{
name: "two unexpired one expired",
subject: testRootSubject,
ids: []certstore.Identity{
&testIdentity{
chain: makeChain(testRootCommonName, times[0], times[3]),
},
&testIdentity{
chain: makeChain(testRootCommonName, times[1], times[3]),
},
&testIdentity{
chain: makeChain(testRootCommonName, times[0], times[1]),
},
},
now: times[2],
wantIndex: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotId, gotChain := selectIdentityFromSlice(tt.subject, tt.ids, tt.now)
if gotId == nil && gotChain != nil {
t.Error("id is nil: got non-nil chain, want nil chain")
return
}
if gotId != nil && gotChain == nil {
t.Error("id is not nil: got nil chain, want non-nil chain")
return
}
if tt.wantIndex == -1 {
if gotId != nil {
t.Error("got non-nil id, want nil id")
}
return
}
if gotId == nil {
t.Error("got nil id, want non-nil id")
return
}
if gotId != tt.ids[tt.wantIndex] {
found := -1
for i := range tt.ids {
if tt.ids[i] == gotId {
found = i
break
}
}
if found == -1 {
t.Errorf("got unknown id, want id at index %v", tt.wantIndex)
} else {
t.Errorf("got id at index %v, want id at index %v", found, tt.wantIndex)
}
}
tid, ok := tt.ids[tt.wantIndex].(*testIdentity)
if !ok {
t.Error("got non-testIdentity, want testIdentity")
return
}
if !reflect.DeepEqual(tid.chain, gotChain) {
t.Errorf("got unknown chain, want chain from id at index %v", tt.wantIndex)
}
})
}
}

View File

@@ -0,0 +1,253 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package controlhttp implements the Tailscale 2021 control protocol
// base transport over HTTP.
//
// This tunnels the protocol in control/controlbase over HTTP with a
// variety of compatibility fallbacks for handling picky or deep
// inspecting proxies.
//
// In the happy path, a client makes a single cleartext HTTP request
// to the server, the server responds with 101 Switching Protocols,
// and the control base protocol takes place over plain TCP.
//
// In the compatibility path, the client does the above over HTTPS,
// resulting in double encryption (once for the control transport, and
// once for the outer TLS layer).
package controlhttp
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/httptrace"
"net/url"
"tailscale.com/control/controlbase"
"tailscale.com/net/dnscache"
"tailscale.com/net/dnsfallback"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
"tailscale.com/types/key"
)
const (
// upgradeHeader is the value of the Upgrade HTTP header used to
// indicate the Tailscale control protocol.
upgradeHeaderValue = "tailscale-control-protocol"
// handshakeHeaderName is the HTTP request header that can
// optionally contain base64-encoded initial handshake
// payload, to save an RTT.
handshakeHeaderName = "X-Tailscale-Handshake"
// serverUpgradePath is where the server-side HTTP handler to
// to do the protocol switch is located.
serverUpgradePath = "/ts2021"
)
// Dial connects to the HTTP server at addr, requests to switch to the
// Tailscale control protocol, and returns an established control
// protocol connection.
//
// If Dial fails to connect using addr, it also tries to tunnel over
// TLS to <addr's host>:443 as a compatibility fallback.
//
// The provided ctx is only used for the initial connection, until
// Dial returns. It does not affect the connection once established.
func Dial(ctx context.Context, addr string, machineKey key.MachinePrivate, controlKey key.MachinePublic) (*controlbase.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
a := &dialParams{
ctx: ctx,
host: host,
httpPort: port,
httpsPort: "443",
machineKey: machineKey,
controlKey: controlKey,
proxyFunc: tshttpproxy.ProxyFromEnvironment,
}
return a.dial()
}
type dialParams struct {
ctx context.Context
host string
httpPort string
httpsPort string
machineKey key.MachinePrivate
controlKey key.MachinePublic
proxyFunc func(*http.Request) (*url.URL, error) // or nil
// For tests only
insecureTLS bool
}
func (a *dialParams) dial() (*controlbase.Conn, error) {
init, cont, err := controlbase.ClientDeferred(a.machineKey, a.controlKey)
if err != nil {
return nil, err
}
u := &url.URL{
Scheme: "http",
Host: net.JoinHostPort(a.host, a.httpPort),
Path: serverUpgradePath,
}
conn, httpErr := a.tryURL(u, init)
if httpErr == nil {
ret, err := cont(a.ctx, conn)
if err != nil {
conn.Close()
return nil, err
}
return ret, nil
}
// Connecting over plain HTTP failed, assume it's an HTTP proxy
// being difficult and see if we can get through over HTTPS.
u.Scheme = "https"
u.Host = net.JoinHostPort(a.host, a.httpsPort)
init, cont, err = controlbase.ClientDeferred(a.machineKey, a.controlKey)
if err != nil {
return nil, err
}
conn, tlsErr := a.tryURL(u, init)
if tlsErr == nil {
ret, err := cont(a.ctx, conn)
if err != nil {
conn.Close()
return nil, err
}
return ret, nil
}
return nil, fmt.Errorf("all connection attempts failed (HTTP: %v, HTTPS: %v)", httpErr, tlsErr)
}
func (a *dialParams) tryURL(u *url.URL, init []byte) (net.Conn, error) {
dns := &dnscache.Resolver{
Forward: dnscache.Get().Forward,
LookupIPFallback: dnsfallback.Lookup,
UseLastGood: true,
}
dialer := netns.NewDialer(log.Printf)
tr := http.DefaultTransport.(*http.Transport).Clone()
defer tr.CloseIdleConnections()
tr.Proxy = a.proxyFunc
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
tr.DialContext = dnscache.Dialer(dialer.DialContext, dns)
// Disable HTTP2, since h2 can't do protocol switching.
tr.TLSClientConfig.NextProtos = []string{}
tr.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
tr.TLSClientConfig = tlsdial.Config(a.host, tr.TLSClientConfig)
if a.insecureTLS {
tr.TLSClientConfig.InsecureSkipVerify = true
tr.TLSClientConfig.VerifyConnection = nil
}
tr.DialTLSContext = dnscache.TLSDialer(dialer.DialContext, dns, tr.TLSClientConfig)
tr.DisableCompression = true
// (mis)use httptrace to extract the underlying net.Conn from the
// transport. We make exactly 1 request using this transport, so
// there will be exactly 1 GotConn call. Additionally, the
// transport handles 101 Switching Protocols correctly, such that
// the Conn will not be reused or kept alive by the transport once
// the response has been handed back from RoundTrip.
//
// In theory, the machinery of net/http should make it such that
// the trace callback happens-before we get the response, but
// there's no promise of that. So, to make sure, we use a buffered
// channel as a synchronization step to avoid data races.
//
// Note that even though we're able to extract a net.Conn via this
// mechanism, we must still keep using the eventual resp.Body to
// read from, because it includes a buffer we can't get rid of. If
// the server never sends any data after sending the HTTP
// response, we could get away with it, but violating this
// assumption leads to very mysterious transport errors (lockups,
// unexpected EOFs...), and we're bound to forget someday and
// introduce a protocol optimization at a higher level that starts
// eagerly transmitting from the server.
connCh := make(chan net.Conn, 1)
trace := httptrace.ClientTrace{
GotConn: func(info httptrace.GotConnInfo) {
connCh <- info.Conn
},
}
ctx := httptrace.WithClientTrace(a.ctx, &trace)
req := &http.Request{
Method: "POST",
URL: u,
Header: http.Header{
"Upgrade": []string{upgradeHeaderValue},
"Connection": []string{"upgrade"},
handshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
},
}
req = req.WithContext(ctx)
resp, err := tr.RoundTrip(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusSwitchingProtocols {
return nil, fmt.Errorf("unexpected HTTP response: %s", resp.Status)
}
// From here on, the underlying net.Conn is ours to use, but there
// is still a read buffer attached to it within resp.Body. So, we
// must direct I/O through resp.Body, but we can still use the
// underlying net.Conn for stuff like deadlines.
var switchedConn net.Conn
select {
case switchedConn = <-connCh:
default:
}
if switchedConn == nil {
resp.Body.Close()
return nil, fmt.Errorf("httptrace didn't provide a connection")
}
if next := resp.Header.Get("Upgrade"); next != upgradeHeaderValue {
resp.Body.Close()
return nil, fmt.Errorf("server switched to unexpected protocol %q", next)
}
rwc, ok := resp.Body.(io.ReadWriteCloser)
if !ok {
resp.Body.Close()
return nil, errors.New("http Transport did not provide a writable body")
}
return &wrappedConn{switchedConn, rwc}, nil
}
type wrappedConn struct {
net.Conn
rwc io.ReadWriteCloser
}
func (w *wrappedConn) Read(bs []byte) (int, error) {
return w.rwc.Read(bs)
}
func (w *wrappedConn) Write(bs []byte) (int, error) {
return w.rwc.Write(bs)
}
func (w *wrappedConn) Close() error {
return w.rwc.Close()
}

View File

@@ -0,0 +1,398 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package controlhttp
import (
"context"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"sync"
"testing"
"tailscale.com/control/controlbase"
"tailscale.com/net/socks5"
"tailscale.com/types/key"
)
func TestControlHTTP(t *testing.T) {
tests := []struct {
name string
proxy proxy
}{
// direct connection
{
name: "no_proxy",
proxy: nil,
},
// SOCKS5
{
name: "socks5",
proxy: &socksProxy{},
},
// HTTP->HTTP
{
name: "http_to_http",
proxy: &httpProxy{
useTLS: false,
allowConnect: false,
allowHTTP: true,
},
},
// HTTP->HTTPS
{
name: "http_to_https",
proxy: &httpProxy{
useTLS: false,
allowConnect: true,
allowHTTP: false,
},
},
// HTTP->any (will pick HTTP)
{
name: "http_to_any",
proxy: &httpProxy{
useTLS: false,
allowConnect: true,
allowHTTP: true,
},
},
// HTTPS->HTTP
{
name: "https_to_http",
proxy: &httpProxy{
useTLS: true,
allowConnect: false,
allowHTTP: true,
},
},
// HTTPS->HTTPS
{
name: "https_to_https",
proxy: &httpProxy{
useTLS: true,
allowConnect: true,
allowHTTP: false,
},
},
// HTTPS->any (will pick HTTP)
{
name: "https_to_any",
proxy: &httpProxy{
useTLS: true,
allowConnect: true,
allowHTTP: true,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
testControlHTTP(t, test.proxy)
})
}
}
func testControlHTTP(t *testing.T, proxy proxy) {
client, server := key.NewMachine(), key.NewMachine()
sch := make(chan serverResult, 1)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := AcceptHTTP(context.Background(), w, r, server)
if err != nil {
log.Print(err)
}
res := serverResult{
err: err,
}
if conn != nil {
res.clientAddr = conn.RemoteAddr().String()
res.version = conn.ProtocolVersion()
res.peer = conn.Peer()
res.conn = conn
}
sch <- res
})
httpLn, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("HTTP listen: %v", err)
}
httpsLn, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("HTTPS listen: %v", err)
}
httpServer := &http.Server{Handler: handler}
go httpServer.Serve(httpLn)
defer httpServer.Close()
httpsServer := &http.Server{
Handler: handler,
TLSConfig: tlsConfig(t),
}
go httpsServer.ServeTLS(httpsLn, "", "")
defer httpsServer.Close()
//ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
//defer cancel()
a := dialParams{
ctx: context.Background(), //ctx,
host: "localhost",
httpPort: strconv.Itoa(httpLn.Addr().(*net.TCPAddr).Port),
httpsPort: strconv.Itoa(httpsLn.Addr().(*net.TCPAddr).Port),
machineKey: client,
controlKey: server.Public(),
insecureTLS: true,
}
if proxy != nil {
proxyEnv := proxy.Start(t)
defer proxy.Close()
proxyURL, err := url.Parse(proxyEnv)
if err != nil {
t.Fatal(err)
}
a.proxyFunc = func(*http.Request) (*url.URL, error) {
return proxyURL, nil
}
} else {
a.proxyFunc = func(*http.Request) (*url.URL, error) {
return nil, nil
}
}
conn, err := a.dial()
if err != nil {
t.Fatalf("dialing controlhttp: %v", err)
}
defer conn.Close()
si := <-sch
if si.conn != nil {
defer si.conn.Close()
}
if si.err != nil {
t.Fatalf("controlhttp server got error: %v", err)
}
if clientVersion := conn.ProtocolVersion(); si.version != clientVersion {
t.Fatalf("client and server don't agree on protocol version: %d vs %d", clientVersion, si.version)
}
if si.peer != client.Public() {
t.Fatalf("server got peer pubkey %s, want %s", si.peer, client.Public())
}
if spub := conn.Peer(); spub != server.Public() {
t.Fatalf("client got peer pubkey %s, want %s", spub, server.Public())
}
if proxy != nil && !proxy.ConnIsFromProxy(si.clientAddr) {
t.Fatalf("client connected from %s, which isn't the proxy", si.clientAddr)
}
}
type serverResult struct {
err error
clientAddr string
version int
peer key.MachinePublic
conn *controlbase.Conn
}
type proxy interface {
Start(*testing.T) string
Close()
ConnIsFromProxy(string) bool
}
type socksProxy struct {
sync.Mutex
proxy socks5.Server
ln net.Listener
clientConnAddrs map[string]bool // addrs of the local end of outgoing conns from proxy
}
func (s *socksProxy) Start(t *testing.T) (url string) {
t.Helper()
s.Lock()
defer s.Unlock()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listening for SOCKS server: %v", err)
}
s.ln = ln
s.clientConnAddrs = map[string]bool{}
s.proxy.Logf = t.Logf
s.proxy.Dialer = s.dialAndRecord
go s.proxy.Serve(ln)
return fmt.Sprintf("socks5://%s", ln.Addr().String())
}
func (s *socksProxy) Close() {
s.Lock()
defer s.Unlock()
s.ln.Close()
}
func (s *socksProxy) dialAndRecord(ctx context.Context, network, addr string) (net.Conn, error) {
var d net.Dialer
conn, err := d.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
s.Lock()
defer s.Unlock()
s.clientConnAddrs[conn.LocalAddr().String()] = true
return conn, nil
}
func (s *socksProxy) ConnIsFromProxy(addr string) bool {
s.Lock()
defer s.Unlock()
return s.clientConnAddrs[addr]
}
type httpProxy struct {
useTLS bool // take incoming connections over TLS
allowConnect bool // allow CONNECT for TLS
allowHTTP bool // allow plain HTTP proxying
sync.Mutex
ln net.Listener
rp httputil.ReverseProxy
s http.Server
clientConnAddrs map[string]bool // addrs of the local end of outgoing conns from proxy
}
func (h *httpProxy) Start(t *testing.T) (url string) {
t.Helper()
h.Lock()
defer h.Unlock()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listening for HTTP proxy: %v", err)
}
h.ln = ln
h.rp = httputil.ReverseProxy{
Director: func(*http.Request) {},
Transport: &http.Transport{
DialContext: h.dialAndRecord,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
TLSNextProto: map[string]func(string, *tls.Conn) http.RoundTripper{},
},
}
h.clientConnAddrs = map[string]bool{}
h.s.Handler = h
if h.useTLS {
h.s.TLSConfig = tlsConfig(t)
go h.s.ServeTLS(h.ln, "", "")
return fmt.Sprintf("https://%s", ln.Addr().String())
} else {
go h.s.Serve(h.ln)
return fmt.Sprintf("http://%s", ln.Addr().String())
}
}
func (h *httpProxy) Close() {
h.Lock()
defer h.Unlock()
h.s.Close()
}
func (h *httpProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "CONNECT" {
if !h.allowHTTP {
http.Error(w, "http proxy not allowed", 500)
return
}
h.rp.ServeHTTP(w, r)
return
}
if !h.allowConnect {
http.Error(w, "connect not allowed", 500)
return
}
dst := r.RequestURI
c, err := h.dialAndRecord(context.Background(), "tcp", dst)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer c.Close()
cc, ccbuf, err := w.(http.Hijacker).Hijack()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer cc.Close()
io.WriteString(cc, "HTTP/1.1 200 OK\r\n\r\n")
errc := make(chan error, 1)
go func() {
_, err := io.Copy(cc, c)
errc <- err
}()
go func() {
_, err := io.Copy(c, ccbuf)
errc <- err
}()
<-errc
}
func (h *httpProxy) dialAndRecord(ctx context.Context, network, addr string) (net.Conn, error) {
var d net.Dialer
conn, err := d.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
h.Lock()
defer h.Unlock()
h.clientConnAddrs[conn.LocalAddr().String()] = true
return conn, nil
}
func (h *httpProxy) ConnIsFromProxy(addr string) bool {
h.Lock()
defer h.Unlock()
return h.clientConnAddrs[addr]
}
func tlsConfig(t *testing.T) *tls.Config {
// Cert and key taken from the example code in the crypto/tls
// package.
certPem := []byte(`-----BEGIN CERTIFICATE-----
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
6MF9+Yw1Yy0t
-----END CERTIFICATE-----`)
keyPem := []byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
-----END EC PRIVATE KEY-----`)
cert, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
t.Fatal(err)
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
}
}

View File

@@ -0,0 +1,95 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package controlhttp
import (
"bufio"
"context"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"tailscale.com/control/controlbase"
"tailscale.com/types/key"
)
// AcceptHTTP upgrades the HTTP request given by w and r into a
// Tailscale control protocol base transport connection.
//
// AcceptHTTP always writes an HTTP response to w. The caller must not
// attempt their own response after calling AcceptHTTP.
func AcceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, private key.MachinePrivate) (*controlbase.Conn, error) {
next := r.Header.Get("Upgrade")
if next == "" {
http.Error(w, "missing next protocol", http.StatusBadRequest)
return nil, errors.New("no next protocol in HTTP request")
}
if next != upgradeHeaderValue {
http.Error(w, "unknown next protocol", http.StatusBadRequest)
return nil, fmt.Errorf("client requested unhandled next protocol %q", next)
}
initB64 := r.Header.Get(handshakeHeaderName)
if initB64 == "" {
http.Error(w, "missing Tailscale handshake header", http.StatusBadRequest)
return nil, errors.New("no tailscale handshake header in HTTP request")
}
init, err := base64.StdEncoding.DecodeString(initB64)
if err != nil {
http.Error(w, "invalid tailscale handshake header", http.StatusBadRequest)
return nil, fmt.Errorf("decoding base64 handshake header: %v", err)
}
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "make request over HTTP/1", http.StatusBadRequest)
return nil, errors.New("can't hijack client connection")
}
w.Header().Set("Upgrade", upgradeHeaderValue)
w.Header().Set("Connection", "upgrade")
w.WriteHeader(http.StatusSwitchingProtocols)
conn, brw, err := hijacker.Hijack()
if err != nil {
return nil, fmt.Errorf("hijacking client connection: %w", err)
}
if err := brw.Flush(); err != nil {
conn.Close()
return nil, fmt.Errorf("flushing hijacked HTTP buffer: %w", err)
}
if brw.Reader.Buffered() > 0 {
conn = &drainBufConn{conn, brw.Reader}
}
nc, err := controlbase.Server(ctx, conn, private, init)
if err != nil {
conn.Close()
return nil, fmt.Errorf("noise handshake failed: %w", err)
}
return nc, nil
}
// drainBufConn is a net.Conn with an initial bunch of bytes in a
// bufio.Reader. Read drains the bufio.Reader until empty, then passes
// through subsequent reads to the Conn directly.
type drainBufConn struct {
net.Conn
r *bufio.Reader
}
func (b *drainBufConn) Read(bs []byte) (int, error) {
if b.r == nil {
return b.Conn.Read(bs)
}
n, err := b.r.Read(bs)
if b.r.Buffered() == 0 {
b.r = nil
}
return n, err
}

View File

@@ -7,9 +7,7 @@
package controlknobs
import (
"os"
"strconv"
"tailscale.com/envknob"
"tailscale.com/syncs"
)
@@ -17,8 +15,7 @@ import (
var disableUPnP syncs.AtomicBool
func init() {
v, _ := strconv.ParseBool(os.Getenv("TS_DISABLE_UPNP"))
SetDisableUPnP(v)
SetDisableUPnP(envknob.Bool("TS_DISABLE_UPNP"))
}
// DisableUPnP reports the last reported value from control

View File

@@ -12,10 +12,12 @@ import (
"fmt"
"io"
"sync"
"sync/atomic"
"time"
"go4.org/mem"
"golang.org/x/time/rate"
"inet.af/netaddr"
"tailscale.com/types/key"
"tailscale.com/types/logger"
)
@@ -37,8 +39,8 @@ type Client struct {
rate *rate.Limiter // if non-nil, rate limiter to use
// Owned by Recv:
peeked int // bytes to discard on next Recv
readErr error // sticky read error
peeked int // bytes to discard on next Recv
readErr atomic.Value // of error; sticky (set by Recv)
}
// ClientOpt is an option passed to NewClient.
@@ -261,10 +263,18 @@ func (c *Client) ForwardPacket(srcKey, dstKey key.NodePublic, pkt []byte) (err e
func (c *Client) writeTimeoutFired() { c.nc.Close() }
func (c *Client) SendPing(data [8]byte) error {
return c.sendPingOrPong(framePing, data)
}
func (c *Client) SendPong(data [8]byte) error {
return c.sendPingOrPong(framePong, data)
}
func (c *Client) sendPingOrPong(typ frameType, data [8]byte) error {
c.wmu.Lock()
defer c.wmu.Unlock()
if err := writeFrameHeader(c.bw, framePong, 8); err != nil {
if err := writeFrameHeader(c.bw, typ, 8); err != nil {
return err
}
if _, err := c.bw.Write(data[:]); err != nil {
@@ -375,6 +385,12 @@ type PingMessage [8]byte
func (PingMessage) msg() {}
// PongMessage is a reply to a PingMessage from a client or server
// with the payload sent previously in a PingMessage.
type PongMessage [8]byte
func (PongMessage) msg() {}
// KeepAliveMessage is a one-way empty message from server to client, just to
// keep the connection alive. It's like a PingMessage, but doesn't solicit
// a reply from the client.
@@ -427,13 +443,14 @@ func (c *Client) Recv() (m ReceivedMessage, err error) {
}
func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err error) {
if c.readErr != nil {
return nil, c.readErr
readErr, _ := c.readErr.Load().(error)
if readErr != nil {
return nil, readErr
}
defer func() {
if err != nil {
err = fmt.Errorf("derp.Recv: %w", err)
c.readErr = err
c.readErr.Store(err)
}
}()
@@ -536,6 +553,15 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
copy(pm[:], b[:])
return pm, nil
case framePong:
var pm PongMessage
if n < 8 {
c.logf("[unexpected] dropping short ping frame")
continue
}
copy(pm[:], b[:])
return pm, nil
case frameHealth:
return HealthMessage{Problem: string(b[:])}, nil
@@ -564,3 +590,22 @@ func (c *Client) setSendRateLimiter(sm ServerInfoMessage) {
sm.TokenBucketBytesBurst)
}
}
// LocalAddr returns the TCP connection's local address.
//
// If the client is broken in some previously detectable way, it
// returns an error.
func (c *Client) LocalAddr() (netaddr.IPPort, error) {
readErr, _ := c.readErr.Load().(error)
if readErr != nil {
return netaddr.IPPort{}, readErr
}
if c.nc == nil {
return netaddr.IPPort{}, errors.New("nil conn")
}
a := c.nc.LocalAddr()
if a == nil {
return netaddr.IPPort{}, errors.New("nil addr")
}
return netaddr.ParseIPPort(a.String())
}

View File

@@ -23,8 +23,8 @@ import (
"math"
"math/big"
"math/rand"
"net"
"net/http"
"os"
"os/exec"
"runtime"
"strconv"
@@ -39,6 +39,7 @@ import (
"inet.af/netaddr"
"tailscale.com/client/tailscale"
"tailscale.com/disco"
"tailscale.com/envknob"
"tailscale.com/metrics"
"tailscale.com/syncs"
"tailscale.com/types/key"
@@ -47,14 +48,14 @@ import (
"tailscale.com/version"
)
var debug, _ = strconv.ParseBool(os.Getenv("DERP_DEBUG_LOGS"))
var debug = envknob.Bool("DERP_DEBUG_LOGS")
// verboseDropKeys is the set of destination public keys that should
// verbosely log whenever DERP drops a packet.
var verboseDropKeys = map[key.NodePublic]bool{}
func init() {
keys := os.Getenv("TS_DEBUG_VERBOSE_DROPS")
keys := envknob.String("TS_DEBUG_VERBOSE_DROPS")
if keys == "" {
return
}
@@ -124,6 +125,8 @@ type Server struct {
packetsForwardedOut expvar.Int
packetsForwardedIn expvar.Int
peerGoneFrames expvar.Int // number of peer gone frames sent
gotPing expvar.Int // number of ping frames from client
sentPong expvar.Int // number of pong frames enqueued to client
accepts expvar.Int
curClients expvar.Int
curHomeClients expvar.Int // ones with preferred
@@ -283,9 +286,8 @@ type PacketForwarder interface {
// It is a defined type so that non-net connections can be used.
type Conn interface {
io.WriteCloser
LocalAddr() net.Addr
// The *Deadline methods follow the semantics of net.Conn.
SetDeadline(time.Time) error
SetReadDeadline(time.Time) error
SetWriteDeadline(time.Time) error
@@ -451,6 +453,9 @@ func (s *Server) initMetacert() {
// Windows requires NotAfter and NotBefore set:
NotAfter: time.Now().Add(30 * 24 * time.Hour),
NotBefore: time.Now().Add(-30 * 24 * time.Hour),
// Per https://github.com/golang/go/issues/51759#issuecomment-1071147836,
// macOS requires BasicConstraints when subject == issuer:
BasicConstraintsValid: true,
}
cert, err := x509.CreateCertificate(crand.Reader, tmpl, tmpl, pub, priv)
if err != nil {
@@ -662,6 +667,7 @@ func (s *Server) accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string, connN
connectedAt: time.Now(),
sendQueue: make(chan pkt, perClientSendQueueDepth),
discoSendQueue: make(chan pkt, perClientSendQueueDepth),
sendPongCh: make(chan [8]byte, 1),
peerGone: make(chan key.NodePublic),
canMesh: clientInfo.MeshKey != "" && clientInfo.MeshKey == s.meshKey,
}
@@ -729,6 +735,8 @@ func (c *sclient) run(ctx context.Context) error {
err = c.handleFrameWatchConns(ft, fl)
case frameClosePeer:
err = c.handleFrameClosePeer(ft, fl)
case framePing:
err = c.handleFramePing(ft, fl)
default:
err = c.handleUnknownFrame(ft, fl)
}
@@ -766,6 +774,33 @@ func (c *sclient) handleFrameWatchConns(ft frameType, fl uint32) error {
return nil
}
func (c *sclient) handleFramePing(ft frameType, fl uint32) error {
c.s.gotPing.Add(1)
var m PingMessage
if fl < uint32(len(m)) {
return fmt.Errorf("short ping: %v", fl)
}
if fl > 1000 {
// unreasonably extra large. We leave some extra
// space for future extensibility, but not too much.
return fmt.Errorf("ping body too large: %v", fl)
}
_, err := io.ReadFull(c.br, m[:])
if err != nil {
return err
}
if extra := int64(fl) - int64(len(m)); extra > 0 {
_, err = io.CopyN(ioutil.Discard, c.br, extra)
}
select {
case c.sendPongCh <- [8]byte(m):
default:
// They're pinging too fast. Ignore.
// TODO(bradfitz): add a rate limiter too.
}
return err
}
func (c *sclient) handleFrameClosePeer(ft frameType, fl uint32) error {
if fl != keyLen {
return fmt.Errorf("handleFrameClosePeer wrong size")
@@ -1202,6 +1237,7 @@ type sclient struct {
remoteIPPort netaddr.IPPort // zero if remoteAddr is not ip:port.
sendQueue chan pkt // packets queued to this client; never closed
discoSendQueue chan pkt // important packets queued to this client; never closed
sendPongCh chan [8]byte // pong replies to send to the client; never closed
peerGone chan key.NodePublic // write request that a previous sender has disconnected (not used by mesh peers)
meshUpdate chan struct{} // write request to write peerStateChange
canMesh bool // clientInfo had correct mesh token for inter-region routing
@@ -1342,6 +1378,9 @@ func (c *sclient) sendLoop(ctx context.Context) error {
werr = c.sendPacket(msg.src, msg.bs)
c.recordQueueTime(msg.enqueuedAt)
continue
case msg := <-c.sendPongCh:
werr = c.sendPong(msg)
continue
case <-keepAliveTick.C:
werr = c.sendKeepAlive()
continue
@@ -1368,6 +1407,9 @@ func (c *sclient) sendLoop(ctx context.Context) error {
case msg := <-c.discoSendQueue:
werr = c.sendPacket(msg.src, msg.bs)
c.recordQueueTime(msg.enqueuedAt)
case msg := <-c.sendPongCh:
werr = c.sendPong(msg)
continue
case <-keepAliveTick.C:
werr = c.sendKeepAlive()
}
@@ -1384,6 +1426,17 @@ func (c *sclient) sendKeepAlive() error {
return writeFrameHeader(c.bw.bw(), frameKeepAlive, 0)
}
// sendPong sends a pong reply, without flushing.
func (c *sclient) sendPong(data [8]byte) error {
c.s.sentPong.Add(1)
c.setWriteDeadline()
if err := writeFrameHeader(c.bw.bw(), framePong, uint32(len(data))); err != nil {
return err
}
_, err := c.bw.Write(data[:])
return err
}
// sendPeerGone sends a peerGone frame, without flushing.
func (c *sclient) sendPeerGone(peer key.NodePublic) error {
c.s.peerGoneFrames.Add(1)
@@ -1591,8 +1644,8 @@ func (m multiForwarder) ForwardPacket(src, dst key.NodePublic, payload []byte) e
return fwd.ForwardPacket(src, dst, payload)
}
func (s *Server) expVarFunc(f func() interface{}) expvar.Func {
return expvar.Func(func() interface{} {
func (s *Server) expVarFunc(f func() any) expvar.Func {
return expvar.Func(func() any {
s.mu.Lock()
defer s.mu.Unlock()
return f()
@@ -1602,14 +1655,14 @@ func (s *Server) expVarFunc(f func() interface{}) expvar.Func {
// ExpVar returns an expvar variable suitable for registering with expvar.Publish.
func (s *Server) ExpVar() expvar.Var {
m := new(metrics.Set)
m.Set("gauge_memstats_sys0", expvar.Func(func() interface{} { return int64(s.memSys0) }))
m.Set("gauge_watchers", s.expVarFunc(func() interface{} { return len(s.watchers) }))
m.Set("gauge_current_file_descriptors", expvar.Func(func() interface{} { return metrics.CurrentFDs() }))
m.Set("gauge_memstats_sys0", expvar.Func(func() any { return int64(s.memSys0) }))
m.Set("gauge_watchers", s.expVarFunc(func() any { return len(s.watchers) }))
m.Set("gauge_current_file_descriptors", expvar.Func(func() any { return metrics.CurrentFDs() }))
m.Set("gauge_current_connections", &s.curClients)
m.Set("gauge_current_home_connections", &s.curHomeClients)
m.Set("gauge_clients_total", expvar.Func(func() interface{} { return len(s.clientsMesh) }))
m.Set("gauge_clients_local", expvar.Func(func() interface{} { return len(s.clients) }))
m.Set("gauge_clients_remote", expvar.Func(func() interface{} { return len(s.clientsMesh) - len(s.clients) }))
m.Set("gauge_clients_total", expvar.Func(func() any { return len(s.clientsMesh) }))
m.Set("gauge_clients_local", expvar.Func(func() any { return len(s.clients) }))
m.Set("gauge_clients_remote", expvar.Func(func() any { return len(s.clientsMesh) - len(s.clients) }))
m.Set("gauge_current_dup_client_keys", &s.dupClientKeys)
m.Set("gauge_current_dup_client_conns", &s.dupClientConns)
m.Set("counter_total_dup_client_conns", &s.dupClientConnTotal)
@@ -1625,13 +1678,15 @@ func (s *Server) ExpVar() expvar.Var {
m.Set("unknown_frames", &s.unknownFrames)
m.Set("home_moves_in", &s.homeMovesIn)
m.Set("home_moves_out", &s.homeMovesOut)
m.Set("got_ping", &s.gotPing)
m.Set("sent_pong", &s.sentPong)
m.Set("peer_gone_frames", &s.peerGoneFrames)
m.Set("packets_forwarded_out", &s.packetsForwardedOut)
m.Set("packets_forwarded_in", &s.packetsForwardedIn)
m.Set("multiforwarder_created", &s.multiForwarderCreated)
m.Set("multiforwarder_deleted", &s.multiForwarderDeleted)
m.Set("packet_forwarder_delete_other_value", &s.removePktForwardOther)
m.Set("average_queue_duration_ms", expvar.Func(func() interface{} {
m.Set("average_queue_duration_ms", expvar.Func(func() any {
return math.Float64frombits(atomic.LoadUint64(s.avgQueueDuration))
}))
var expvarVersion expvar.String
@@ -1761,7 +1816,7 @@ func (s *Server) ServeDebugTraffic(w http.ResponseWriter, r *http.Request) {
}
var bufioWriterPool = &sync.Pool{
New: func() interface{} {
New: func() any {
return bufio.NewWriterSize(ioutil.Discard, 2<<10)
},
}

View File

@@ -9,6 +9,7 @@ import (
"bytes"
"context"
"crypto/x509"
"encoding/asn1"
"encoding/json"
"errors"
"expvar"
@@ -790,6 +791,17 @@ func TestMetaCert(t *testing.T) {
if g, w := cert.Subject.CommonName, fmt.Sprintf("derpkey%s", pub.UntypedHexString()); g != w {
t.Errorf("CommonName = %q; want %q", g, w)
}
if n := len(cert.Extensions); n != 1 {
t.Fatalf("got %d extensions; want 1", n)
}
// oidExtensionBasicConstraints is the Basic Constraints ID copied
// from the x509 package.
oidExtensionBasicConstraints := asn1.ObjectIdentifier{2, 5, 29, 19}
if id := cert.Extensions[0].Id; !id.Equal(oidExtensionBasicConstraints) {
t.Errorf("extension ID = %v; want %v", id, oidExtensionBasicConstraints)
}
}
type dummyNetConn struct {
@@ -802,7 +814,7 @@ func TestClientRecv(t *testing.T) {
tests := []struct {
name string
input []byte
want interface{}
want any
}{
{
name: "ping",
@@ -812,6 +824,14 @@ func TestClientRecv(t *testing.T) {
},
want: PingMessage{1, 2, 3, 4, 5, 6, 7, 8},
},
{
name: "pong",
input: []byte{
byte(framePong), 0, 0, 0, 8,
1, 2, 3, 4, 5, 6, 7, 8,
},
want: PongMessage{1, 2, 3, 4, 5, 6, 7, 8},
},
{
name: "health_bad",
input: []byte{
@@ -858,6 +878,23 @@ func TestClientRecv(t *testing.T) {
}
}
func TestClientSendPing(t *testing.T) {
var buf bytes.Buffer
c := &Client{
bw: bufio.NewWriter(&buf),
}
if err := c.SendPing([8]byte{1, 2, 3, 4, 5, 6, 7, 8}); err != nil {
t.Fatal(err)
}
want := []byte{
byte(framePing), 0, 0, 0, 8,
1, 2, 3, 4, 5, 6, 7, 8,
}
if !bytes.Equal(buf.Bytes(), want) {
t.Errorf("unexpected output\nwrote: % 02x\n want: % 02x", buf.Bytes(), want)
}
}
func TestClientSendPong(t *testing.T) {
var buf bytes.Buffer
c := &Client{
@@ -873,7 +910,6 @@ func TestClientSendPong(t *testing.T) {
if !bytes.Equal(buf.Bytes(), want) {
t.Errorf("unexpected output\nwrote: % 02x\n want: % 02x", buf.Bytes(), want)
}
}
func TestServerDupClients(t *testing.T) {
@@ -1316,3 +1352,30 @@ func TestClientSendRateLimiting(t *testing.T) {
t.Errorf("limited conn's bytes count = %v; want >=%v, <%v", bytesLimited, bytes1K*2, bytes1K)
}
}
func TestServerRepliesToPing(t *testing.T) {
ts := newTestServer(t)
defer ts.close(t)
tc := newRegularClient(t, ts, "alice")
data := [8]byte{1, 2, 3, 4, 5, 6, 7, 42}
if err := tc.c.SendPing(data); err != nil {
t.Fatal(err)
}
for {
m, err := tc.c.recvTimeout(time.Second)
if err != nil {
t.Fatal(err)
}
switch m := m.(type) {
case PongMessage:
if ([8]byte(m)) != data {
t.Fatalf("got pong %2x; want %2x", [8]byte(m), data)
}
return
}
}
}

View File

@@ -13,6 +13,7 @@ package derphttp
import (
"bufio"
"context"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"errors"
@@ -22,16 +23,16 @@ import (
"net"
"net/http"
"net/url"
"os"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/derp"
"tailscale.com/envknob"
"tailscale.com/net/dnscache"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
@@ -64,6 +65,12 @@ type Client struct {
ctx context.Context // closed via cancelCtx in Client.Close
cancelCtx context.CancelFunc
// addrFamSelAtomic is the last AddressFamilySelector set
// by SetAddressFamilySelector. It's an atomic because it needs
// to be accessed by multiple racing routines started while
// Client.conn holds mu.
addrFamSelAtomic atomic.Value // of AddressFamilySelector
mu sync.Mutex
preferred bool
canAckPings bool
@@ -72,6 +79,8 @@ type Client struct {
client *derp.Client
connGen int // incremented once per new connection; valid values are >0
serverPubKey key.NodePublic
tlsState *tls.ConnectionState
pingOut map[derp.PingMessage]chan<- bool // chan to send to on pong
}
// NewRegionClient returns a new DERP-over-HTTP client. It connects lazily.
@@ -123,6 +132,17 @@ func (c *Client) Connect(ctx context.Context) error {
return err
}
// TLSConnectionState returns the last TLS connection state, if any.
// The client must already be connected.
func (c *Client) TLSConnectionState() (_ *tls.ConnectionState, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed || c.client == nil {
return nil, false
}
return c.tlsState, c.tlsState != nil
}
// ServerPublicKey returns the server's public key.
//
// It only returns a non-zero value once a connection has succeeded
@@ -180,6 +200,32 @@ func (c *Client) urlString(node *tailcfg.DERPNode) string {
return fmt.Sprintf("https://%s/derp", node.HostName)
}
// AddressFamilySelector decides whethers IPv6 is preferred for
// outbound dials.
type AddressFamilySelector interface {
// PreferIPv6 reports whether IPv4 dials should be slightly
// delayed to give IPv6 a better chance of winning dial races.
// Implementations should only return true if IPv6 is expected
// to succeed. (otherwise delaying IPv4 will delay the
// connection overall)
PreferIPv6() bool
}
// SetAddressFamilySelector sets the AddressFamilySelector that this
// connection will use. It should be called before any dials.
// The value must not be nil. If called more than once, s must
// be the same concrete type as any prior calls.
func (c *Client) SetAddressFamilySelector(s AddressFamilySelector) {
c.addrFamSelAtomic.Store(s)
}
func (c *Client) preferIPv6() bool {
if s, ok := c.addrFamSelAtomic.Load().(AddressFamilySelector); ok {
return s.PreferIPv6()
}
return false
}
// dialWebsocketFunc is non-nil (set by websocket.go's init) when compiled in.
var dialWebsocketFunc func(ctx context.Context, urlStr string) (net.Conn, error)
@@ -188,8 +234,7 @@ func useWebsockets() bool {
return true
}
if dialWebsocketFunc != nil {
v, _ := strconv.ParseBool(os.Getenv("TS_DEBUG_DERP_WS_CLIENT"))
return v
return envknob.Bool("TS_DEBUG_DERP_WS_CLIENT")
}
return false
}
@@ -318,6 +363,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
var httpConn net.Conn // a TCP conn or a TLS conn; what we speak HTTP to
var serverPub key.NodePublic // or zero if unknown (if not using TLS or TLS middlebox eats it)
var serverProtoVersion int
var tlsState *tls.ConnectionState
if c.useHTTPS() {
tlsConn := c.tlsClient(tcpConn, node)
httpConn = tlsConn
@@ -340,9 +386,10 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
// Note that we're not specifically concerned about TLS downgrade
// attacks. TLS handles that fine:
// https://blog.gypsyengineer.com/en/security/how-does-tls-1-3-protect-against-downgrade-attacks.html
connState := tlsConn.ConnectionState()
if connState.Version >= tls.VersionTLS13 {
serverPub, serverProtoVersion = parseMetaCert(connState.PeerCertificates)
cs := tlsConn.ConnectionState()
tlsState = &cs
if cs.Version >= tls.VersionTLS13 {
serverPub, serverProtoVersion = parseMetaCert(cs.PeerCertificates)
}
} else {
httpConn = tcpConn
@@ -409,6 +456,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
c.serverPubKey = derpClient.ServerPublicKey()
c.client = derpClient
c.netConn = tcpConn
c.tlsState = tlsState
c.connGen++
return c.client, c.connGen, nil
}
@@ -568,6 +616,18 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
startDial := func(dstPrimary, proto string) {
nwait++
go func() {
if proto == "tcp4" && c.preferIPv6() {
t := time.NewTimer(200 * time.Millisecond)
select {
case <-ctx.Done():
// Either user canceled original context,
// it timed out, or the v6 dial succeeded.
t.Stop()
return
case <-t.C:
// Start v4 dial
}
}
dst := dstPrimary
if dst == "" {
dst = n.HostName
@@ -698,6 +758,95 @@ func (c *Client) Send(dstKey key.NodePublic, b []byte) error {
return err
}
func (c *Client) registerPing(m derp.PingMessage, ch chan<- bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.pingOut == nil {
c.pingOut = map[derp.PingMessage]chan<- bool{}
}
c.pingOut[m] = ch
}
func (c *Client) unregisterPing(m derp.PingMessage) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.pingOut, m)
}
func (c *Client) handledPong(m derp.PongMessage) bool {
c.mu.Lock()
defer c.mu.Unlock()
k := derp.PingMessage(m)
if ch, ok := c.pingOut[k]; ok {
ch <- true
delete(c.pingOut, k)
return true
}
return false
}
// Ping sends a ping to the peer and waits for it either to be
// acknowledged (in which case Ping returns nil) or waits for ctx to
// be over and returns an error. It will wait at most 5 seconds
// before returning an error.
//
// Another goroutine must be in a loop calling Recv or
// RecvDetail or ping responses won't be handled.
func (c *Client) Ping(ctx context.Context) error {
maxDL := time.Now().Add(5 * time.Second)
if dl, ok := ctx.Deadline(); !ok || dl.After(maxDL) {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, maxDL)
defer cancel()
}
var data derp.PingMessage
rand.Read(data[:])
gotPing := make(chan bool, 1)
c.registerPing(data, gotPing)
defer c.unregisterPing(data)
if err := c.SendPing(data); err != nil {
return err
}
select {
case <-gotPing:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// SendPing writes a ping message, without any implicit connect or
// reconnect. This is a lower-level interface that writes a frame
// without any implicit handling of the response pong, if any. For a
// higher-level interface, use Ping.
func (c *Client) SendPing(data [8]byte) error {
c.mu.Lock()
closed, client := c.closed, c.client
c.mu.Unlock()
if closed {
return ErrClientClosed
}
if client == nil {
return errors.New("client not connected")
}
return client.SendPing(data)
}
// LocalAddr reports c's local TCP address, without any implicit
// connect or reconnect.
func (c *Client) LocalAddr() (netaddr.IPPort, error) {
c.mu.Lock()
closed, client := c.closed, c.client
c.mu.Unlock()
if closed {
return netaddr.IPPort{}, ErrClientClosed
}
if client == nil {
return netaddr.IPPort{}, errors.New("client not connected")
}
return client.LocalAddr()
}
func (c *Client) ForwardPacket(from, to key.NodePublic, b []byte) error {
client, _, err := c.connect(context.TODO(), "derphttp.Client.ForwardPacket")
if err != nil {
@@ -805,14 +954,22 @@ func (c *Client) RecvDetail() (m derp.ReceivedMessage, connGen int, err error) {
if err != nil {
return nil, 0, err
}
m, err = client.Recv()
if err != nil {
c.closeForReconnect(client)
if c.isClosed() {
err = ErrClientClosed
for {
m, err = client.Recv()
switch m := m.(type) {
case derp.PongMessage:
if c.handledPong(m) {
continue
}
}
if err != nil {
c.closeForReconnect(client)
if c.isClosed() {
err = ErrClientClosed
}
}
return m, connGen, err
}
return m, connGen, err
}
func (c *Client) isClosed() bool {

View File

@@ -154,3 +154,55 @@ func waitConnect(t testing.TB, c *Client) {
t.Fatalf("client first Recv was unexpected type %T", v)
}
}
func TestPing(t *testing.T) {
serverPrivateKey := key.NewNode()
s := derp.NewServer(serverPrivateKey, t.Logf)
defer s.Close()
httpsrv := &http.Server{
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
Handler: Handler(s),
}
ln, err := net.Listen("tcp4", "localhost:0")
if err != nil {
t.Fatal(err)
}
serverURL := "http://" + ln.Addr().String()
t.Logf("server URL: %s", serverURL)
go func() {
if err := httpsrv.Serve(ln); err != nil {
if err == http.ErrServerClosed {
return
}
panic(err)
}
}()
c, err := NewClient(key.NewNode(), serverURL, t.Logf)
if err != nil {
t.Fatalf("NewClient: %v", err)
}
defer c.Close()
if err := c.Connect(context.Background()); err != nil {
t.Fatalf("client Connect: %v", err)
}
errc := make(chan error, 1)
go func() {
for {
m, err := c.Recv()
if err != nil {
errc <- err
return
}
t.Logf("Recv: %T", m)
}
}()
err = c.Ping(context.Background())
if err != nil {
t.Fatalf("Ping: %v", err)
}
}

144
envknob/envknob.go Normal file
View File

@@ -0,0 +1,144 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package envknob provides access to environment-variable tweakable
// debug settings.
//
// These are primarily knobs used by Tailscale developers during
// development or by users when instructed to by Tailscale developers
// when debugging something. They are not a stable interface and may
// be removed or any time.
//
// A related package, control/controlknobs, are knobs that can be
// changed at runtime by the control plane. Sometimes both are used:
// an envknob for the default/explicit value, else falling back
// to the controlknob value.
package envknob
import (
"log"
"os"
"strconv"
"sync"
"tailscale.com/types/opt"
)
var (
mu sync.Mutex
set = map[string]string{}
list []string
)
func noteEnv(k, v string) {
if v == "" {
return
}
mu.Lock()
defer mu.Unlock()
if _, ok := set[v]; !ok {
list = append(list, k)
}
set[k] = v
}
// logf is logger.Logf, but logger depends on envknob, so for circular
// dependency reasons, make a type alias (so it's still assignable,
// but has nice docs here).
type logf = func(format string, args ...any)
// LogCurrent logs the currently set environment knobs.
func LogCurrent(logf logf) {
mu.Lock()
defer mu.Unlock()
for _, k := range list {
logf("envknob: %s=%q", k, set[k])
}
}
// String returns the named environment variable, using os.Getenv.
//
// If the variable is non-empty, it's also tracked & logged as being
// an in-use knob.
func String(envVar string) string {
v := os.Getenv(envVar)
noteEnv(envVar, v)
return v
}
// Bool returns the boolean value of the named environment variable.
// If the variable is not set, it returns false.
// An invalid value exits the binary with a failure.
func Bool(envVar string) bool {
return boolOr(envVar, false)
}
// BoolDefaultTrue is like Bool, but returns true by default if the
// environment variable isn't present.
func BoolDefaultTrue(envVar string) bool {
return boolOr(envVar, true)
}
func boolOr(envVar string, implicitValue bool) bool {
val := os.Getenv(envVar)
if val == "" {
return implicitValue
}
b, err := strconv.ParseBool(val)
if err == nil {
noteEnv(envVar, strconv.FormatBool(b)) // canonicalize
return b
}
log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
panic("unreachable")
}
// LookupBool returns the boolean value of the named environment value.
// The ok result is whether a value was set.
// If the value isn't a valid int, it exits the program with a failure.
func LookupBool(envVar string) (v bool, ok bool) {
val := os.Getenv(envVar)
if val == "" {
return false, false
}
b, err := strconv.ParseBool(val)
if err == nil {
return b, true
}
log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
panic("unreachable")
}
// OptBool is like Bool, but returns an opt.Bool, so the caller can
// distinguish between implicitly and explicitly false.
func OptBool(envVar string) opt.Bool {
b, ok := LookupBool(envVar)
if !ok {
return ""
}
var ret opt.Bool
ret.Set(b)
return ret
}
// LookupInt returns the integer value of the named environment value.
// The ok result is whether a value was set.
// If the value isn't a valid int, it exits the program with a failure.
func LookupInt(envVar string) (v int, ok bool) {
val := os.Getenv(envVar)
if val == "" {
return 0, false
}
v, err := strconv.Atoi(val)
if err == nil {
noteEnv(envVar, val)
return v, true
}
log.Fatalf("invalid integer environment variable %s: %v", envVar, val)
panic("unreachable")
}
// UseWIPCode is whether TAILSCALE_USE_WIP_CODE is set to permit use
// of Work-In-Progress code.
func UseWIPCode() bool { return Bool("TAILSCALE_USE_WIP_CODE") }

273
go.mod
View File

@@ -1,214 +1,267 @@
module tailscale.com
go 1.17
go 1.18
require (
filippo.io/mkcert v1.4.3
github.com/akutz/memconn v0.1.0
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go v1.38.52
github.com/aws/aws-sdk-go-v2 v1.9.2
github.com/aws/aws-sdk-go-v2/config v1.8.3
github.com/aws/aws-sdk-go-v2/service/ssm v1.12.0
github.com/aws/aws-sdk-go-v2 v1.11.2
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.17.1
github.com/coreos/go-iptables v0.6.0
github.com/creack/pty v1.1.17
github.com/dave/jennifer v1.4.1
github.com/frankban/quicktest v1.14.0
github.com/gliderlabs/ssh v0.3.3
github.com/go-ole/go-ole v1.2.6-0.20210915003542-8b1f7f90f6b1
github.com/go-ole/go-ole v1.2.6
github.com/godbus/dbus/v5 v5.0.6
github.com/google/go-cmp v0.5.6
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/google/go-cmp v0.5.7
github.com/google/uuid v1.3.0
github.com/goreleaser/nfpm v1.10.3
github.com/iancoleman/strcase v0.2.0
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e
github.com/jsimonetti/rtnetlink v0.0.0-20211203074127-fd9a11f42291
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.13.6
github.com/mdlayher/netlink v1.4.1
github.com/mdlayher/genetlink v1.2.0
github.com/mdlayher/netlink v1.6.0
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697
github.com/miekg/dns v1.1.43
github.com/mitchellh/go-ps v1.0.0
github.com/pborman/getopt v1.1.0
github.com/peterbourgon/ff/v3 v3.1.0
github.com/peterbourgon/ff/v3 v3.1.2
github.com/pkg/sftp v1.13.4
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
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/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
github.com/tailscale/hujson v0.0.0-20200924210142-dde312d0d6a2
github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
github.com/tailscale/ssh v0.3.4-0.20220313013419-be8b7add4057
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/vishvananda/netlink v1.1.1-0.20211101163509-b10eb8fe5cf6
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa
golang.org/x/net v0.0.0-20211111083644-e5c967477495
github.com/u-root/u-root v0.8.0
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
go4.org/mem v0.0.0-20210711025021-927187094b94
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
golang.org/x/tools v0.1.7
golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
golang.org/x/tools v0.1.10
golang.zx2c4.com/wireguard v0.0.0-20220317000134-95b48cdb3961
golang.zx2c4.com/wireguard/windows v0.4.10
honnef.co/go/tools v0.2.2
gvisor.dev/gvisor v0.0.0-20220126021142-d8aa030b2591
honnef.co/go/tools v0.3.0-0.dev.0.20220306074811-23e1086441d2
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c
inet.af/peercred v0.0.0-20210318190834-4259e17bb763
inet.af/wf v0.0.0-20210516214145-a5343001b756
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
inet.af/wf v0.0.0-20211204062712-86aaea0a7310
nhooyr.io/websocket v1.8.7
)
require (
4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
4d63.com/gochecknoglobals v0.1.0 // indirect
github.com/Antonboom/errname v0.1.5 // indirect
github.com/Antonboom/nilnil v0.1.0 // indirect
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/Djarvur/go-err113 v0.1.0 // indirect
github.com/Masterminds/goutils v1.1.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/sprig v2.22.0+incompatible // indirect
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/OpenPeeDeeP/depguard v1.0.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.4.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2 // indirect
github.com/aws/smithy-go v1.8.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/ashanbrown/forbidigo v1.2.0 // indirect
github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde // 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.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2 // 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/smithy-go v1.9.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.0 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/bombsimon/wsl/v3 v3.1.0 // indirect
github.com/blizzy78/varnamelen v0.5.0 // indirect
github.com/bombsimon/wsl/v3 v3.3.0 // indirect
github.com/breml/bidichk v0.2.1 // indirect
github.com/butuzov/ireturn v0.1.1 // indirect
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
github.com/daixiang0/gci v0.2.7 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/charithe/durationcheck v0.0.9 // indirect
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
github.com/daixiang0/gci v0.2.9 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingajkin/go-header v0.3.1 // indirect
github.com/denis-tingajkin/go-header v0.4.2 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fatih/color v1.10.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-critic/go-critic v0.5.2 // indirect
github.com/esimonov/ifshort v1.0.3 // indirect
github.com/ettle/strcase v0.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fzipp/gocyclo v0.3.1 // indirect
github.com/gliderlabs/ssh v0.3.3 // indirect
github.com/go-critic/go-critic v0.6.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.0.0 // indirect
github.com/go-git/go-git/v5 v5.2.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-toolsmith/astcast v1.0.0 // indirect
github.com/go-toolsmith/astcopy v1.0.0 // indirect
github.com/go-toolsmith/astequal v1.0.0 // indirect
github.com/go-toolsmith/astequal v1.0.1 // indirect
github.com/go-toolsmith/astfmt v1.0.0 // indirect
github.com/go-toolsmith/astp v1.0.0 // indirect
github.com/go-toolsmith/strparse v1.0.0 // indirect
github.com/go-toolsmith/typep v1.0.2 // indirect
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/flock v0.8.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 // indirect
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 // indirect
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d // indirect
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a // indirect
github.com/golangci/golangci-lint v1.33.0 // indirect
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc // indirect
github.com/golangci/golangci-lint v1.43.0 // indirect
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
github.com/golangci/misspell v0.3.5 // indirect
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 // indirect
github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 // indirect
github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2 // indirect
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 // indirect
github.com/goreleaser/chglog v0.1.2 // indirect
github.com/goreleaser/fileglob v0.3.1 // indirect
github.com/gostaticanalysis/analysisutil v0.6.1 // indirect
github.com/gostaticanalysis/comment v1.4.1 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jgautheron/goconst v0.0.0-20201117150253-ccae5bf973f3 // indirect
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a // indirect
github.com/jgautheron/goconst v1.5.1 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/julz/importas v0.0.0-20210922140945-27e0a5d4dee2 // indirect
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/kisielk/errcheck v1.6.0 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kunwardeep/paralleltest v1.0.2 // indirect
github.com/kulti/thelper v0.4.0 // indirect
github.com/kunwardeep/paralleltest v1.0.3 // indirect
github.com/kyoh86/exportloopref v0.1.8 // indirect
github.com/magiconair/properties v1.8.4 // indirect
github.com/ldez/gomoddirectives v0.2.2 // indirect
github.com/ldez/tagliatelle v0.2.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/maratori/testpackage v1.0.1 // indirect
github.com/matoous/godox v0.0.0-20200801072554-4fb83dc2941e // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mbilski/exhaustivestruct v1.1.0 // indirect
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mdlayher/socket v0.1.1 // indirect
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
github.com/mgechev/revive v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.0 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moricho/tparallel v0.2.1 // indirect
github.com/nakabonne/nestif v0.3.0 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect
github.com/nishanths/exhaustive v0.1.0 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/nishanths/exhaustive v0.7.11 // indirect
github.com/nishanths/predeclared v0.2.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 // indirect
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polyfloyd/go-errorlint v0.0.0-20201127212506-19bd8db6546f // indirect
github.com/quasilyte/go-ruleguard v0.2.1 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20200805063351-8f842688393c // indirect
github.com/rogpeppe/go-internal v1.6.2 // indirect
github.com/ryancurrah/gomodguard v1.1.0 // indirect
github.com/polyfloyd/go-errorlint v0.0.0-20211125173453-6d6d39c5bb8b // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/quasilyte/go-ruleguard v0.3.13 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect
github.com/ryancurrah/gomodguard v1.2.3 // indirect
github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b // indirect
github.com/securego/gosec/v2 v2.5.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/securego/gosec/v2 v2.9.3 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.7.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/sivchari/tenv v1.4.7 // indirect
github.com/sonatard/noctx v0.0.1 // indirect
github.com/sourcegraph/go-diff v0.6.1 // indirect
github.com/spf13/afero v1.5.1 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.1.1 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.2.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1 // indirect
github.com/ssgreg/nlreturn/v2 v2.1.0 // indirect
github.com/spf13/viper v1.9.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b // indirect
github.com/tetafro/godot v1.3.2 // indirect
github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94 // indirect
github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756 // indirect
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa // indirect
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
github.com/sylvia7788/contextcheck v1.0.4 // indirect
github.com/tdakkota/asciicheck v0.1.1 // indirect
github.com/tetafro/godot v1.4.11 // indirect
github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect
github.com/tomarrell/wrapcheck/v2 v2.4.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.4.0 // indirect
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/ultraware/funlen v0.0.3 // indirect
github.com/ultraware/whitespace v0.0.4 // indirect
github.com/uudashr/gocognit v1.0.1 // indirect
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
github.com/uudashr/gocognit v1.0.5 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/yeya24/promlinter v0.1.0 // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
howett.net/plist v1.0.0 // indirect
mvdan.cc/gofumpt v0.2.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-20200501210554-b37ab49443f7 // indirect
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237 // indirect
mvdan.cc/unparam v0.0.0-20211002134041-24922b6997ca // indirect
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 // indirect
)

1301
go.sum

File diff suppressed because it is too large Load Diff

1
go.toolchain.branch Normal file
View File

@@ -0,0 +1 @@
tailscale.go1.18

1
go.toolchain.rev Normal file
View File

@@ -0,0 +1 @@
68c97fb924bbcacd951dc01b292c679aaeff5802

View File

@@ -9,13 +9,14 @@ package health
import (
"errors"
"fmt"
"os"
"net/http"
"runtime"
"sort"
"sync"
"sync/atomic"
"time"
"tailscale.com/envknob"
"tailscale.com/tailcfg"
"tailscale.com/util/multierr"
)
@@ -28,6 +29,8 @@ var (
watchers = map[*watchHandle]func(Subsystem, error){} // opt func to run if error state changes
timer *time.Timer
debugHandler = map[string]http.Handler{}
inMapPoll bool
inMapPollSince time.Time
lastMapPollEndedAt time.Time
@@ -61,6 +64,9 @@ const (
// SysDNSOS is the name of the net/dns OSConfigurator subsystem.
SysDNSOS = Subsystem("dns-os")
// SysDNSManager is the name of the net/dns manager subsystem.
SysDNSManager = Subsystem("dns-manager")
// SysNetworkCategory is the name of the subsystem that sets
// the Windows network adapter's "category" (public, private, domain).
// If it's unhealthy, the Windows firewall rules won't match.
@@ -107,6 +113,10 @@ func DNSHealth() error { return get(SysDNS) }
// SetDNSOSHealth sets the state of the net/dns.OSConfigurator
func SetDNSOSHealth(err error) { set(SysDNSOS, err) }
// SetDNSManagerHealth sets the state of the Linux net/dns manager's
// discovery of the /etc/resolv.conf situation.
func SetDNSManagerHealth(err error) { set(SysDNSManager, err) }
// DNSOSHealth returns the net/dns.OSConfigurator error state.
func DNSOSHealth() error { return get(SysDNSOS) }
@@ -116,6 +126,18 @@ func SetNetworkCategoryHealth(err error) { set(SysNetworkCategory, err) }
func NetworkCategoryHealth() error { return get(SysNetworkCategory) }
func RegisterDebugHandler(typ string, h http.Handler) {
mu.Lock()
defer mu.Unlock()
debugHandler[typ] = h
}
func DebugHandler(typ string) http.Handler {
mu.Lock()
defer mu.Unlock()
return debugHandler[typ]
}
func get(key Subsystem) error {
mu.Lock()
defer mu.Unlock()
@@ -168,7 +190,8 @@ func GotStreamedMapResponse() {
selfCheckLocked()
}
// SetInPollNetMap records that we're in
// SetInPollNetMap records whether the client has an open
// HTTP long poll open to the control plane.
func SetInPollNetMap(v bool) {
mu.Lock()
defer mu.Unlock()
@@ -183,6 +206,14 @@ func SetInPollNetMap(v bool) {
}
}
// GetInPollNetMap reports whether the client has an open
// HTTP long poll open to the control plane.
func GetInPollNetMap() bool {
mu.Lock()
defer mu.Unlock()
return inMapPoll
}
// SetMagicSockDERPHome notes what magicsock's view of its home DERP is.
func SetMagicSockDERPHome(region int) {
mu.Lock()
@@ -284,7 +315,7 @@ func OverallError() error {
return overallErrorLocked()
}
var fakeErrForTesting = os.Getenv("TS_DEBUG_FAKE_HEALTH_ERROR")
var fakeErrForTesting = envknob.String("TS_DEBUG_FAKE_HEALTH_ERROR")
func overallErrorLocked() error {
if !anyInterfaceUp {

View File

@@ -10,7 +10,6 @@ import (
"bufio"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"sync/atomic"
@@ -32,13 +31,17 @@ func New() *tailcfg.Hostinfo {
Hostname: hostname,
OS: version.OS(),
OSVersion: GetOSVersion(),
Package: packageType(),
Package: packageTypeCached(),
GoArch: runtime.GOARCH,
DeviceModel: deviceModel(),
}
}
var osVersion func() string // non-nil on some platforms
// non-nil on some platforms
var (
osVersion func() string
packageType func() string
)
// GetOSVersion returns the OSVersion of current host if available.
func GetOSVersion() string {
@@ -51,28 +54,18 @@ func GetOSVersion() string {
return ""
}
func packageType() string {
func packageTypeCached() string {
if v, _ := packagingType.Load().(string); v != "" {
return v
}
switch runtime.GOOS {
case "windows":
if _, err := os.Stat(`C:\ProgramData\chocolatey\lib\tailscale`); err == nil {
return "choco"
}
case "darwin":
// Using tailscaled or IPNExtension?
exe, _ := os.Executable()
return filepath.Base(exe)
case "linux":
// Report whether this is in a snap.
// See https://snapcraft.io/docs/environment-variables
// We just look at two somewhat arbitrarily.
if os.Getenv("SNAP_NAME") != "" && os.Getenv("SNAP") != "" {
return "snap"
}
if packageType == nil {
return ""
}
return ""
v := packageType()
if v != "" {
SetPackage(v)
}
return v
}
// EnvType represents a known environment type.

View File

@@ -0,0 +1,23 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin
// +build darwin
package hostinfo
import (
"os"
"path/filepath"
)
func init() {
packageType = packageTypeDarwin
}
func packageTypeDarwin() string {
// Using tailscaled or IPNExtension?
exe, _ := os.Executable()
return filepath.Base(exe)
}

View File

@@ -21,6 +21,7 @@ import (
func init() {
osVersion = osVersionLinux
packageType = packageTypeLinux
if v := linuxDeviceModel(); v != "" {
SetDeviceModel(v)
@@ -112,6 +113,18 @@ func osVersionLinux() string {
return fmt.Sprintf("Synology %s%s", m["productversion"], attr)
case distro.OpenWrt:
return fmt.Sprintf("OpenWrt %s%s", m["DISTRIB_RELEASE"], attr)
case distro.Gokrazy:
return fmt.Sprintf("Gokrazy%s", attr)
}
return fmt.Sprintf("Other%s", attr)
}
func packageTypeLinux() string {
// Report whether this is in a snap.
// See https://snapcraft.io/docs/environment-variables
// We just look at two somewhat arbitrarily.
if os.Getenv("SNAP_NAME") != "" && os.Getenv("SNAP") != "" {
return "snap"
}
return ""
}

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