Compare commits

..

205 Commits

Author SHA1 Message Date
Brad Fitzpatrick
8b2b679ba3 api.md: add TOC 2021-01-19 12:34:22 -08:00
David Anderson
da4ec54756 tailcfg: remove v6-overlay debug option.
It's about to become a no-op in control.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-18 17:47:23 -08:00
Brad Fitzpatrick
5c619882bc wgengine/magicsock: simplify ReceiveIPv4+DERP path
name           old time/op  new time/op  delta
ReceiveFrom-4  35.8µs ± 3%  21.9µs ± 5%  -38.92%  (p=0.008 n=5+5)

Fixes #1145
Updates #414

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-18 15:23:17 -08:00
David Anderson
9936cffc1a wgengine: correctly track all node IPs in lazy config.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-18 13:32:16 -08:00
Brad Fitzpatrick
3fa86a8b23 wgengine/magicsock: use relatively new netaddr.IPPort.IsZero method 2021-01-15 19:21:10 -08:00
Brad Fitzpatrick
4811236189 wgengine/magicsock: speed up BenchmarkReceiveFrom, store context.Done chan
context.cancelCtx.Done involves a mutex and isn't as cheap as I
previously assumed. Convert the donec method into a struct field and
store the channel value once. Our one magicsock.Conn gets one pointer
larger, but it cuts ~1% of the CPU time of the ReceiveFrom benchmark
and removes a bubble from the --svg output :)
2021-01-15 19:19:27 -08:00
Brad Fitzpatrick
c78ed5b399 go.sum: update (forgotten after earlier wireguard-go update again) 2021-01-15 19:19:27 -08:00
Denton Gentry
013da6660e logtail: add tests
+ add a test for parseAndRemoveLogLevel()
+ add a test for drainPendingMessages()
+ test JSON log encoding including several special cases

Other tests frequently send logs but a) don't check the result and
b) do so by happenstance, such that the code in encode() was not
consistently being exercised and leading to spurious changes in
code coverage. These tests attempt to more systematically test
the logging function.

This is the second attempt to add these tests, the first attempt
(in https://github.com/tailscale/tailscale/pull/1114) had two issues:
1. httptest.NewServer creates multiple goroutine handlers, and
   logtail uses goroutines to upload, but the first version had no
   locking in the server to guard this.
   Moved data handling into channels to get synchronization.
2. The channel to notify the test of the arrival of data had a depth
   of 1, in cases where the Logger sent multiple uploads it would
   block the server.

This resulted in the first iteration of these tests being flaky,
and we reverted it.

This new version of the tests has passed with
    go test -race -count=10000
and seems solid.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-15 19:11:40 -08:00
Denton Gentry
8578b0445d tstun: add test to send a packet after Close()
This test serves two purposes:
+ check that Write() returns an error if the tstun has been
  closed.
+ ensure that the close-related code in tstun is exercised in
  a test case. We were getting spurious code coverage adds/drops
  based on timing of when the test case finished.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-15 19:11:40 -08:00
Josh Bleecher Snyder
7c1a9e8616 net/nettest: de-flake tests on Windows
Windows has a low resolution timer.
Some of the tests assumed that unblock takes effect immediately.

Consider:

t := time.Now()
elapsed := time.Now().After(t)

It seems plausible that elapsed should always be true.
However, with a low resolution timer, that might fail.

Change time.Now().After to !time.Now().Before,
so that unblocking always takes effect immediately.

Fixes #873.
2021-01-15 18:21:56 -08:00
Josh Bleecher Snyder
a64d06f15c net/nettest: remove pointless checks in tests
If err == nil, then !errors.Is(err, anything).
2021-01-15 18:21:56 -08:00
Josh Bleecher Snyder
503db5540f net/nettest: add missing check at end of TestLimit
This appears to have been an oversight.
2021-01-15 18:21:56 -08:00
Josh Bleecher Snyder
ed2169ae99 wgengine/magicsock: prevent logging after TestActiveDiscovery completes 2021-01-15 18:19:20 -08:00
Josh Bleecher Snyder
12bb949178 go.mod: bump to pull in minor wireguard-go changes 2021-01-15 17:35:03 -08:00
Josh Bleecher Snyder
63af950d8c wgengine/magicsock: adapt to wireguard-go without UpdateDst
22507adf54 stopped relying on
our fork of wireguard-go's UpdateDst callback.
As a result, we can unwind that code,
and the extra return value of ReceiveIPv{4,6}.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-15 17:13:58 -08:00
Denton Gentry
23c2dc2165 magicksock: remove TestConnClosing. (#1140)
Test is flakey, remove it and figure out what to do differently later.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-15 16:55:30 -08:00
David Anderson
e23b4191c4 wgengine/magicsock: disable legacy networking everywhere except TwoDevicePing.
TwoDevicePing is explicitly testing the behavior of the legacy codepath, everything
else is happy to assume that code no longer exists.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-15 16:02:31 -08:00
David Anderson
0733c5d2e0 wgengine/magicsock: disable legacy behavior in a few more tests.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-15 15:57:41 -08:00
David Anderson
57d95dd005 wgengine/magicsock: default legacy networking to off for some tests.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-15 15:54:45 -08:00
David Anderson
a2463e8948 wgengine/magicsock: add an option to disable legacy peer handling.
Used in tests to ensure we're not relying on behavior we're going
to remove eventually.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-15 15:01:33 -08:00
David Anderson
d456bfdc6d wgengine/magicsock: fix BenchmarkReceiveFrom.
Previously, this benchmark relied on behavior of the legacy
receive codepath, which I changed in 22507adf. With this
change, the benchmark instead relies on the new active discovery
path.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-15 15:01:33 -08:00
Josh Bleecher Snyder
2d837f79dc wgengine/magicsock: close test loggers once we're done with them
This is a big hammer approach to helping with #1132.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-15 14:44:56 -08:00
Josh Bleecher Snyder
08baa17d9a wgengine/magicsock: shortcircuit discoEndpoint.heartbeat when its connection is closed
This prevents us from continuing to do unnecessary work
(including logging) after the connection has closed.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-15 14:44:56 -08:00
Josh Bleecher Snyder
7c76435bf7 wgengine/magicsock: simplify
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-15 14:44:56 -08:00
Josh Bleecher Snyder
d2529affa2 wgengine/magicsock: quiet wireguard-go logging in tests
We already do this in newUserspaceEngineAdvanced.
Apply it to newMagicStack as well.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-15 14:44:56 -08:00
Josh Bleecher Snyder
3ad7c2133a wgengine/userspace: make wireguard-go log silencing include peer routines
Also suppress log lines like:

peer(Kksd…ySmc) - Routine: sequential sender - stopped

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-15 14:44:56 -08:00
Brad Fitzpatrick
b560386c1a net/packet, wgengine, tstun: add inter-node TSMP protocol for connect errors
This adds a new IP Protocol type, TSMP on protocol number 99 for
sending inter-tailscale messages over WireGuard, currently just for
why a peer rejects TCP SYNs (ACL rejection, shields up, and in the
future: nothing listening, something listening on that port but wrong
interface, etc)

Updates #1094
Updates tailscale/corp#1185

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-15 14:03:57 -08:00
David Anderson
01e8b7fb7e go.mod: bump wireguard-go version.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-15 10:53:49 -08:00
Brad Fitzpatrick
5611f290eb ipn, ipnserver: only require sudo on Linux for mutable CLI actions
This partially reverts d6e9fb1df0, which modified the permissions
on the tailscaled Unix socket and thus required "sudo tailscale" even
for "tailscale status".

Instead, open the permissions back up (on Linux only) but have the
server look at the peer creds and only permit read-only actions unless
you're root.

In the future we'll also have a group that can do mutable actions.

On OpenBSD and FreeBSD, the permissions on the socket remain locked
down to 0600 from d6e9fb1df0.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-15 10:13:00 -08:00
Brad Fitzpatrick
a45665426b cmd/tailscale/cli: tweak the status name column a bit
* make peers without DNS names show their hostnames as always one column, for cut/etc users
* remove trailing dot from shared peers' DNS names
2021-01-15 07:46:58 -08:00
Naman Sood
420c7a35e2 wgengine/netstack: use tailscale IPs instead of a hardcoded one (#1131)
Signed-off-by: Naman Sood <mail@nsood.in>
2021-01-15 09:16:28 -05:00
Brad Fitzpatrick
3ac952d4e9 go.sum: update 2021-01-14 20:19:44 -08:00
Brad Fitzpatrick
a4b39022e0 wgengine/tsdns: fix MagicDNS lookups of shared nodes
Fixes tailscale/corp#1184
2021-01-14 14:49:32 -08:00
Brad Fitzpatrick
b00c0e5f60 go.sum: update 2021-01-14 14:49:32 -08:00
Alex Brainman
6e4231c03c wgengine/router/dns: remove unused code
Commit 68ddf1 removed code that reads
`SOFTWARE\Tailscale IPN\SearchList` registry value. But the commit
left code that writes that value.

So now this package writes and never reads the value.

Remove the code to stop pointless work.

Updates #853

Signed-off-by: Alex Brainman <alex.brainman@gmail.com>
2021-01-14 14:04:35 -08:00
Josh Bleecher Snyder
654b5f1570 all: convert from []wgcfg.Endpoint to string
This eliminates a dependency on wgcfg.Endpoint,
as part of the effort to eliminate our wireguard-go fork.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-14 13:54:07 -08:00
David Anderson
9abcb18061 wgengine/magicsock: import more of wireguard-go, update docstrings.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-14 12:56:48 -08:00
David Anderson
22507adf54 wgengine/magicsock: stop depending on UpdateDst in legacy codepaths.
This makes connectivity between ancient and new tailscale nodes slightly
worse in some cases, but only in cases where the ancient version would
likely have failed to get connectivity anyway.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-14 12:56:48 -08:00
Brad Fitzpatrick
017dcd520f tsweb: export VarzHandler 2021-01-14 11:49:44 -08:00
Brad Fitzpatrick
c1dabd9436 control/controlclient: let clients opt in to Sharer-vs-User split model
Updates tailscale/corp#1183
2021-01-13 15:03:15 -08:00
Josh Bleecher Snyder
b38fa7de29 go.mod: update to latest wireguard-go 2021-01-13 14:41:25 -08:00
Josh Bleecher Snyder
020084e84d wgengine: adapt to removal of wgcfg.Key in wireguard-go 2021-01-13 14:39:34 -08:00
Smitty
2bf49ddf90 Provide example when format string is rate limited
Here's an example log line in the new format:
    [RATE LIMITED] format string "open-conn-track: timeout opening %v; no associated peer node" (example: "open-conn-track: timeout opening ([ip] => [ip]); no associated peer node")
This should make debugging logging issues a bit easier, and give more
context as to why something was rate limited. This change was proposed
in a comment on #1110.

Signed-off-by: Smitty <me@smitop.com>
2021-01-13 13:57:23 -08:00
Denton Gentry
ce058c8280 Revert "Add logtail tests (#1114)" (#1116)
This reverts commit e4f53e9b6f.

At least two of these tests are flakey, reverting until they can be
made more robust.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 15:48:11 -08:00
Smitty
b2a08ddacd wgengine/tsdns: return NOERROR instead of NOTIMP for most records
This is what every other DNS resolver I could find does, so tsdns
should do it to. This also helps avoid weird error messages about
non-existent records being unimplemented, and thus fixes #848.

Signed-off-by: Smitty <me@smitop.com>
2021-01-12 15:12:53 -08:00
Denton Gentry
e4f53e9b6f Add logtail tests (#1114)
* logtail: test parseAndRemoveLogLevel()

Signed-off-by: Denton Gentry <dgentry@tailscale.com>

* logtail: test JSON log encoding.

Expand TestUploadMessages to also exercise the encoding functions
in logtail, like JSON logging and timestamps.

Other tests frequently send logs but a) don't check the result and
b) do so by happenstance, such that the lines in encode() were not
consistently being exercised and leading to spurious changes in
code coverage.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>

* logtail: add a test for drainPendingMessages

Make the client buffer some messages before the upload server
becomes available.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>

* logtail: use %q, raw strings, and io.WriteString

%q escapes binary characters for us.

raw strings avoid so much backslash escaping

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 13:31:45 -08:00
Brad Fitzpatrick
b987b2ab18 control/controlclient: treat node sharer as owner for display purposes
This make clients (macOS, Windows, tailscale status) show the node
sharer's profile rather than the node owner (which may be anonymized).

Updates #992
2021-01-12 12:15:35 -08:00
Brad Fitzpatrick
7acd3397d5 README: names of contributors, link to them instead 2021-01-12 08:24:32 -08:00
Brad Fitzpatrick
9d73f84a71 tailcfg, control/controlclient: make MapResponse.CollectServices an opt.Bool
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-12 08:08:00 -08:00
Christina Wen
a746ff5de7 API.md: add documentation for deleting a device
Signed-off-by: Christina Wen <christina@tailscale.com>
2021-01-12 06:01:00 -08:00
Christina Wen
8d7ddf5e94 API.md: rename "domain" to "tailnet"
Signed-off-by: Christina Wen <christina@tailscale.com>
2021-01-12 06:01:00 -08:00
Denton Gentry
ac42757cd7 netcheck: use reflect in sortRegions test.
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Denton Gentry
43e060b0e5 netcheck: test sortRegions
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Denton Gentry
8349e10907 magicsock: add description of testClosingContext
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Denton Gentry
b771a1363b logtail: start a local server for TestFastShutdown
Right now TestFastShutdown tries to upload logs to localhost:1234,
which will most likely respond with an error. However if one has an
actual service running on port 1234, it would receive a connection
attempting to POST every time the unit test runs.

Start a local server and direct the upload there instead.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Denton Gentry
2e9728023b magicsock: test error case in sendDiscoMessage
In sendDiscoMessage there is a check of whether the connection is
closed, which is not being reliably exercised by other tests.
This shows up in code coverage reports, the lines of code in
sendDiscoMessage are alternately added and subtracted from
code coverage.

Add a test to specifically exercise and verify this code path.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Denton Gentry
2c328da094 logtail: add a test to upload logs to local server
Start an HTTP server to accept POST requests, and upload some logs to
it. Check that uploaded logs were received.

Code in logtail:drainPending was not being reliably exercised by other
tests. This shows up in code coverage reports, as lines of code in
drainPending are alternately added and subtracted from code coverage.
This test will reliably exercise and verify this code.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Denton Gentry
0aed59b691 portlist: add a test for SameInodes
Exercise all cases.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Denton Gentry
07e4009e15 portlist: fully exercise lessThan in tests
All cases in lessThan are not reliably exercised by other tests.
This shows up in code coverage metrics as lines in lessThan are
alternately added and removed from coverage.

Add a test case to systematically test all conditions.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Denton Gentry
0aa55bffce magicsock: test error case in derpWriteChanOfAddr
In derpWriteChanOfAddr when we call derphttp.NewRegionClient(),
there is a check of whether the connection is already errored and
if so it returns before grabbing the lock. The lock might already
be held and would be a deadlock.

This corner case is not being reliably exercised by other tests.
This shows up in code coverage reports, the lines of code in
derpWriteChanOfAddr are alternately added and subtracted from
code coverage.

Add a test to specifically exercise this code path, and verify that
it doesn't deadlock.

This is the best tradeoff I could come up with:
+ the moment code calls Err() to check if there is an error, we
  grab the lock to make sure it would deadlock if it tries to grab
  the lock itself.
+ if a new call to Err() is added in this code path, only the
  first one will be covered and the rest will not be tested.
+ this test doesn't verify whether code is checking for Err() in
  the right place, which ideally I guess it would.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-12 04:29:28 -08:00
Brad Fitzpatrick
85e54af0d7 wgengine: on TCP connect fail/timeout, log some clues about why it failed
So users can see why things aren't working.

A start. More diagnostics coming.

Updates #1094
2021-01-11 22:09:09 -08:00
Brad Fitzpatrick
5eeaea9ef9 net/packet: add TCPFlag type and some more constants
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-11 22:09:09 -08:00
Brad Fitzpatrick
ad3fb6125d net/flowtrack: add Tuple.String method
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-11 22:09:09 -08:00
Brad Fitzpatrick
d6e9fb1df0 all: adjust Unix permissions for those without umasks
Fixes tailscale/corp#1165

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-11 19:24:41 -08:00
Brad Fitzpatrick
6b08303b0f Dockerfile: add big warning banner
Updates #504
2021-01-11 19:23:47 -08:00
Brad Fitzpatrick
676b5b7946 net/netcheck: improve the preferred DERP hysteresis
Users in Amsterdam (as one example) were flipping back and forth
between equidistant London & Frankfurt relays too much.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-11 14:50:47 -08:00
Sonia Appasamy
024671406b ipn: only send services in Hostinfo if Tailnet has opted-in to services collection (#1107)
Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
2021-01-11 17:24:32 -05:00
Brad Fitzpatrick
f85769b1ed wgengine/magicsock: drop netaddr.IPPort cache
netaddr.IP no longer allocates, so don't need a cache or all its associated
code/complexity.

This totally removes groupcache/lru from the deps.

Also go mod tidy.
2021-01-11 13:23:04 -08:00
Brad Fitzpatrick
a80446c026 Update depaware (removes lru from wgengine/filter) 2021-01-11 13:17:18 -08:00
Brad Fitzpatrick
4d15e954bd net/flowtrack: add new package to specialize groupcache/lru key type
Reduces allocs.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-11 13:08:03 -08:00
Naman Sood
f69e46175d wengine/netstack: bump gvisor to latest version
* wengine/netstack: bump gvisor to latest version

Signed-off-by: Naman Sood <naman@tailscale.com>

* update dependencies

Signed-off-by: Naman Sood <naman@tailscale.com>

* Don't change hardcoded IP

Signed-off-by: Naman Sood <naman@tailscale.com>
2021-01-11 15:46:48 -05:00
Brad Fitzpatrick
8b0112649a wgengine/netstack: don't build netstack on 32-bit platforms
See google/gvisor#5241
2021-01-11 09:56:05 -08:00
Brad Fitzpatrick
5aa5db89d6 cmd/tailscaled, wgengine/netstack: add start of gvisor userspace netstack work
Not usefully functional yet (mostly a proof of concept), but getting
it submitted for some work @namansood is going to do atop this.

Updates #707
Updates #634
Updates #48
Updates #835
2021-01-11 09:31:14 -08:00
Brad Fitzpatrick
5efb0a8bca cmd/tailscale: change formatting of "tailscale status"
* show DNS name over hostname, removing domain's common MagicDNS suffix.
  only show hostname if there's no DNS name.
  but still show shared devices' MagicDNS FQDN.

* remove nerdy low-level details by default: endpoints, DERP relay,
  public key.  They're available in JSON mode still for those who need
  them.

* only show endpoint or DERP relay when it's active with the goal of
  making debugging easier. (so it's easier for users to understand
  what's happening) The asterisks are gone.

* remove Tx/Rx numbers by default for idle peers; only show them when
  there's traffic.

* include peers' owner login names

* add CLI option to not show peers (matching --self=true, --peers= also
  defaults to true)

* sort by DNS/host name, not public key

* reorder columns
2021-01-10 12:11:22 -08:00
Brad Fitzpatrick
c09d5a9e28 go.mod: bump wireguard-go to match our meta repo 2021-01-08 21:15:32 -08:00
Brad Fitzpatrick
b5b9866ba2 wgengine/magicsock: copy self DNS name to PeerStatus, re-fill OS
The OS used to be sent back from the server but that has since
been removed as being redundant.
2021-01-08 20:55:57 -08:00
Brad Fitzpatrick
a4cc31e7d8 go.sum: update 2021-01-08 20:55:03 -08:00
Josh Bleecher Snyder
1271e135cd wgengine/tstun: initialize wireguard-go TUN parameters
This will enable us to remove the corresponding code from
our fork of wireguard-go.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-08 17:22:04 -08:00
Josh Bleecher Snyder
18471a8792 ipn: close logger at the end of TestLocalLogLines
If any goroutine continues to use the logger in TestLocalLogLines
after the test finishes, the test panics.

The culprit for this was wireguard-go; the previous commit fixed that.
This commit adds suspenders: When the test is done, make logging calls
into no-ops.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-08 17:10:48 -08:00
Josh Bleecher Snyder
1e4604f60e wgengine: quiet some wireguard-go logging
The log lines that wireguard-go prints as it starts
and stops its worker routines are mostly noise.
They also happen after other work is completed,
which causes failures in some of the log testing packages.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-08 17:10:48 -08:00
Josh Bleecher Snyder
c580d2eab1 go.mod: change wireguard-go version spelling
Our toolchains disagree about the spelling.
Sigh.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-08 15:58:11 -08:00
Josh Bleecher Snyder
53f9dcdf05 go.mod: update wireguard-go to fix windows build failure
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-08 15:56:45 -08:00
Josh Bleecher Snyder
80c33f4fa1 go.mod: update to latest wireguard-go 2021-01-08 15:44:07 -08:00
Josh Bleecher Snyder
e0c4ffa71f wgengine/tsdns: respond with any available addrs for ALL queries
This appears to have been the intent of the previous code,
but in practice, it only returned A records.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-01-08 14:23:11 -08:00
Denton Gentry
fa3e8e1a28 Add names to test cases in ipn/local_test.go.
There are so many now that just a number doesn't work well.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-08 10:23:32 -08:00
Denton Gentry
67ebc7c0e7 Allow 2021 in LICENSE header.
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-08 10:23:32 -08:00
Denton Gentry
02c34881b5 Add more tests for Direct.
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-08 10:23:32 -08:00
Denton Gentry
df64b7abf8 Add IPv6 Reverse DNS Lookup test.
To be honest I'm not fond of Golden Bytes tests like this, but
not so much as to want to rewrite the whole test. The DNS byte
format is essentially immutable at this point, the encoded bytes
aren't going to change. The rest of the test assumptions about
hostnames might, but we can fix that when it comes.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-08 10:23:32 -08:00
Denton Gentry
414cb4a695 Add test for dnsMapsEqual.
Exercises most cases in the function.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-08 10:23:32 -08:00
Denton Gentry
b716c76df9 cover one more case in TestStatusEqual.
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-08 10:23:32 -08:00
Brad Fitzpatrick
2f04f49376 control/controlclient: use lite map request handler to avoid aborting streams
Previously, any change to endpoints or hostinfo (or hostinfo's
netinfo) would result in the long-running map request HTTP stream
being torn down and restarted, losing all compression context along
with it.

This change makes us instead send a lite map request (OmitPeers: true,
Stream: false) that doesn't subscribe to anything, and then the
coordination server knows to not close other streams for that node
when it recives a lite request.

Fixes tailscale/corp#797

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-08 09:30:08 -08:00
Denton Gentry
e692e3866b Cache go modules.
Apply Go actions cache, as described in
https://markphelps.me/2019/11/speed-up-your-go-builds-with-actions-cache/

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-07 21:34:43 -08:00
Denton Gentry
d12add6e22 Adjust coverage options.
+ we don't need an exactly accurate count of the number of times each
  time ran. Remove -covermode, the default "set" will be fine to just
  track whether a given line ran at all.
+ add -benchtime=1x. We only need to run the benchmarks once.
+ -bench=. to match any character.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-07 21:34:43 -08:00
Denton Gentry
332759ef73 Add coveralls.io support.
We include -bench because some parts of the codebase, like
smallzstd, do not have regular unit tests but do have very
good benchmark tests that covers all functions.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-01-07 21:34:43 -08:00
Alex Brainman
9985b3f1ed wgengine/monitor: close closeHandle
eccc167 introduced closeHandle which opened the handle,
but never closed it.

Windows handles should be closed.

Updates #921

Signed-off-by: Alex Brainman <alex.brainman@gmail.com>
2021-01-07 20:18:02 -08:00
Frederik “Freso” S. Olesen
83fccf9fe5 tailscaled.service: Lock down clock and /dev (#1071)
Research in issue #1063 uncovered why tailscaled would fail with
ProtectClock enabled (it implicitly enabled DevicePolicy=closed).

This knowledge in turn also opens the door for locking down /dev
further, e.g. explicitly setting DevicePolicy=strict (instead of
closed), and making /dev private for the unit.

Additional possible future (or downstream) lockdown that can be done
is setting `PrivateDevices=true` (with `BindPaths=/dev/net/`), however,
systemd 233 or later is required for this, and tailscaled currently need
to work for systemd down to version 215.

Closes https://github.com/tailscale/tailscale/issues/1063

Signed-off-by: Frederik “Freso” S. Olesen <freso.dk@gmail.com>
2021-01-07 10:18:55 -08:00
Brad Fitzpatrick
b5129dadfd ipn: fix buggy-looking format string in error log
On shutdown, logs showed:
wgengine status error: &errors.errorString{s:"engine closing; no status"}
2021-01-06 20:18:29 -08:00
Brad Fitzpatrick
66be052a70 net/dnscache: work on IPv6-only hosts (again)
This fixes the regression where we had stopped working on IPv6-only
hosts.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-06 19:53:13 -08:00
Brad Fitzpatrick
560da4884f tailcfg: add Node.Sharer field
Updates #992

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-05 13:53:00 -08:00
Christina Wen
d8a5b3f22f API.md: revise documentation to be more consistent
Signed-off-by: Christina Wen <christina@tailscale.com>
2021-01-05 14:33:28 -05:00
Christina Wen
3e3bd5f169 API.md: release API documentation
Co-authored-by: Daniel Chung <daniel@tailscale.com>
Signed-off-by: Christina Wen <christina@tailscale.com>
2021-01-05 14:33:28 -05:00
Brad Fitzpatrick
312646c516 tailcfg: add omitempty to FilterRule.SrcBits (#1089)
It's not used by recent clients, so even more reason to omit it.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-05 10:54:01 -08:00
Brad Fitzpatrick
e8ae355bb8 ipn: delete domainsForProxying, require explicit DNS search domains (mapver 9) (#1078)
Previously the client had heuristics to calculate which DNS search domains
to set, based on the peers' names. Unfortunately that prevented us from
doing some things we wanted to do server-side related to node sharing.

So, bump MapRequest.Version to 9 to signal that the client only uses the
explicitly configured DNS search domains and doesn't augment it with its own
list.

Updates tailscale/corp#1026

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-01-05 10:37:15 -08:00
Brad Fitzpatrick
1ccf997699 version: new version for a new year 2021-01-04 08:58:05 -08:00
David Anderson
8fc11d582d go.sum: update to match wireguard-go version update.
Signed-off-by: David Anderson <dave@natulte.net>
2021-01-02 16:27:06 -08:00
Josh Bleecher Snyder
14af677332 go.mod: update wireguard-go version
To pick up netaddr deps change
2020-12-30 17:41:14 -08:00
David Anderson
86fe22a1b1 Update netaddr, and adjust wgengine/magicsock due to API change.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-30 17:36:03 -08:00
Josh Bleecher Snyder
56a7652dc9 wgkey: new package
This is a replacement for the key-related parts
of the wireguard-go wgcfg package.

This is almost a straight copy/paste from the wgcfg package.
I have slightly changed some of the exported functions and types
to avoid stutter, added and tweaked some comments,
and removed some now-unused code.

To avoid having wireguard-go depend on this new package,
wgcfg will keep its key types.

We translate into and out of those types at the last minute.
These few remaining uses will be eliminated alongside
the rest of the wgcfg package.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-30 17:33:02 -08:00
Brad Fitzpatrick
13b554fed9 version: bump for the last time in 2020 2020-12-30 12:07:25 -08:00
Brad Fitzpatrick
c2edb2865b go.sum: update 2020-12-30 12:07:25 -08:00
Christine Dodrill
70f14af21e add nix-shell boilerplate (#1028)
This enables users of nix-shell to automagically have the correct 
development environment by simply changing directory into a
checkout of this repo. For more information on this see the following
links:

- https://christine.website/blog/how-i-start-nix-2020-03-08
- https://direnv.net/
2020-12-29 12:17:03 -05:00
Brad Fitzpatrick
0d94fe5f69 wgengine/router: disable IPv6 on Linux if ip rule -6 fails (#1074)
Updates #562
Fixes #973

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-29 08:26:17 -08:00
Josh Bleecher Snyder
1e88050403 net/tsaddr: add ChromeOS contains tests
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-28 16:48:43 -08:00
Josh Bleecher Snyder
cf2ac2d123 go.mod: upgrade inet.af/netaddr
To pick up IPPrefix.Contains fix.
2020-12-28 15:46:46 -08:00
Josh Bleecher Snyder
2fe770ed72 all: replace wgcfg.IP and wgcfg.CIDR with netaddr types
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-28 13:00:42 -08:00
Charlotte Brandhorst-Satzkorn
ff2b3d02e6 Fix typo in cmd/tailscale/cli/cli.go (#1069)
Remove duplicate 'to connect' in error message.

Fixes #1068

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@catzkorn.dev>
2020-12-25 07:32:37 -08:00
Frederik “Freso” S. Olesen
a9a80ab372 tailscaled.service: Harden systemd unit somewhat (#1062)
While not a full capability lockdown of the systemd unit, this still
improves sandboxing and security of the running process a good deal.

Signed-off-by: Frederik “Freso” S. Olesen <freso.dk@gmail.com>
2020-12-24 16:14:58 -08:00
Matt Layher
1a42cef3a2 cmd/tailscale*: make updatedeps
Signed-off-by: Matt Layher <mdlayher@gmail.com>
2020-12-23 08:08:02 -08:00
Matt Layher
bfbd6b9241 go.mod: bump github.com/mdlayher/netlink to v1.2.0
Signed-off-by: Matt Layher <mdlayher@gmail.com>
2020-12-23 08:08:02 -08:00
Brad Fitzpatrick
80c94168ae wgengine: finish updating isTrimmablePeer
I accidentally merged Dave's change with the XXXX DO NOT SUBMIT comment
in it.
2020-12-22 14:48:24 -08:00
David Anderson
cb96b14bf4 net/packet: remove the custom IP4/IP6 types in favor of netaddr.IP.
Upstream netaddr has a change that makes it alloc-free, so it's safe to
use in hot codepaths. This gets rid of one of the many IP types in our
codebase.

Performance is currently worse across the board. This is likely due in
part to netaddr.IP being a larger value type (4b -> 24b for IPv4,
16b -> 24b for IPv6), and in other part due to missing low-hanging fruit
optimizations in netaddr. However, the regression is less bad than
it looks at first glance, because we'd micro-optimized packet.IP* in
the past few weeks. This change drops us back to roughly where we
were at the 1.2 release, but with the benefit of a significant
code and architectural simplification.

name                   old time/op    new time/op    delta
pkg:tailscale.com/net/packet goos:linux goarch:amd64
Decode/tcp4-8            12.2ns ± 5%    29.7ns ± 2%  +142.32%  (p=0.008 n=5+5)
Decode/tcp6-8            12.6ns ± 3%    65.1ns ± 2%  +418.47%  (p=0.008 n=5+5)
Decode/udp4-8            11.8ns ± 3%    30.5ns ± 2%  +157.94%  (p=0.008 n=5+5)
Decode/udp6-8            27.1ns ± 1%    65.7ns ± 2%  +142.36%  (p=0.016 n=4+5)
Decode/icmp4-8           24.6ns ± 2%    30.5ns ± 2%   +23.65%  (p=0.016 n=4+5)
Decode/icmp6-8           22.9ns ±51%    65.5ns ± 2%  +186.19%  (p=0.008 n=5+5)
Decode/igmp-8            18.1ns ±44%    30.2ns ± 1%   +66.89%  (p=0.008 n=5+5)
Decode/unknown-8         20.8ns ± 1%    10.6ns ± 9%   -49.11%  (p=0.016 n=4+5)
pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64
Filter/icmp4-8           30.5ns ± 1%    77.9ns ± 3%  +155.01%  (p=0.008 n=5+5)
Filter/tcp4_syn_in-8     43.7ns ± 3%   123.0ns ± 3%  +181.72%  (p=0.008 n=5+5)
Filter/tcp4_syn_out-8    24.5ns ± 2%    45.7ns ± 6%   +86.22%  (p=0.008 n=5+5)
Filter/udp4_in-8         64.8ns ± 1%   210.0ns ± 2%  +223.87%  (p=0.008 n=5+5)
Filter/udp4_out-8         119ns ± 0%     278ns ± 0%  +133.78%  (p=0.016 n=4+5)
Filter/icmp6-8           40.3ns ± 2%   204.4ns ± 4%  +407.70%  (p=0.008 n=5+5)
Filter/tcp6_syn_in-8     35.3ns ± 3%   199.2ns ± 2%  +464.95%  (p=0.008 n=5+5)
Filter/tcp6_syn_out-8    32.8ns ± 2%    81.0ns ± 2%  +147.10%  (p=0.008 n=5+5)
Filter/udp6_in-8          106ns ± 2%     290ns ± 2%  +174.48%  (p=0.008 n=5+5)
Filter/udp6_out-8         184ns ± 2%     314ns ± 3%   +70.43%  (p=0.016 n=4+5)
pkg:tailscale.com/wgengine/tstun goos:linux goarch:amd64
Write-8                  9.02ns ± 3%    8.92ns ± 1%      ~     (p=0.421 n=5+5)

name                   old alloc/op   new alloc/op   delta
pkg:tailscale.com/net/packet goos:linux goarch:amd64
Decode/tcp4-8             0.00B          0.00B           ~     (all equal)
Decode/tcp6-8             0.00B          0.00B           ~     (all equal)
Decode/udp4-8             0.00B          0.00B           ~     (all equal)
Decode/udp6-8             0.00B          0.00B           ~     (all equal)
Decode/icmp4-8            0.00B          0.00B           ~     (all equal)
Decode/icmp6-8            0.00B          0.00B           ~     (all equal)
Decode/igmp-8             0.00B          0.00B           ~     (all equal)
Decode/unknown-8          0.00B          0.00B           ~     (all equal)
pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64
Filter/icmp4-8            0.00B          0.00B           ~     (all equal)
Filter/tcp4_syn_in-8      0.00B          0.00B           ~     (all equal)
Filter/tcp4_syn_out-8     0.00B          0.00B           ~     (all equal)
Filter/udp4_in-8          0.00B          0.00B           ~     (all equal)
Filter/udp4_out-8         16.0B ± 0%     64.0B ± 0%  +300.00%  (p=0.008 n=5+5)
Filter/icmp6-8            0.00B          0.00B           ~     (all equal)
Filter/tcp6_syn_in-8      0.00B          0.00B           ~     (all equal)
Filter/tcp6_syn_out-8     0.00B          0.00B           ~     (all equal)
Filter/udp6_in-8          0.00B          0.00B           ~     (all equal)
Filter/udp6_out-8         48.0B ± 0%     64.0B ± 0%   +33.33%  (p=0.008 n=5+5)

name                   old allocs/op  new allocs/op  delta
pkg:tailscale.com/net/packet goos:linux goarch:amd64
Decode/tcp4-8              0.00           0.00           ~     (all equal)
Decode/tcp6-8              0.00           0.00           ~     (all equal)
Decode/udp4-8              0.00           0.00           ~     (all equal)
Decode/udp6-8              0.00           0.00           ~     (all equal)
Decode/icmp4-8             0.00           0.00           ~     (all equal)
Decode/icmp6-8             0.00           0.00           ~     (all equal)
Decode/igmp-8              0.00           0.00           ~     (all equal)
Decode/unknown-8           0.00           0.00           ~     (all equal)
pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64
Filter/icmp4-8             0.00           0.00           ~     (all equal)
Filter/tcp4_syn_in-8       0.00           0.00           ~     (all equal)
Filter/tcp4_syn_out-8      0.00           0.00           ~     (all equal)
Filter/udp4_in-8           0.00           0.00           ~     (all equal)
Filter/udp4_out-8          1.00 ± 0%      1.00 ± 0%      ~     (all equal)
Filter/icmp6-8             0.00           0.00           ~     (all equal)
Filter/tcp6_syn_in-8       0.00           0.00           ~     (all equal)
Filter/tcp6_syn_out-8      0.00           0.00           ~     (all equal)
Filter/udp6_in-8           0.00           0.00           ~     (all equal)
Filter/udp6_out-8          1.00 ± 0%      1.00 ± 0%      ~     (all equal)

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-22 14:44:36 -08:00
Brad Fitzpatrick
d0baece5fa go.mod: bump inet.af/netaddr to non-allocating version 2020-12-22 14:25:32 -08:00
Brad Fitzpatrick
ef15096a7d control/controlclient, version/distro: detect NixOS explicitly
The fallthrough happened to work in controlclient already due to the
/etc/os-release PRETTY_NAME default, but make it explicit so it
doesn't look like an accident.

Also add it to version/distro, even though nothing needs it yet.
2020-12-21 21:03:04 -08:00
David Crawshaw
2b2a16d9a2 wgengine/router/dns: reduce windows registry key open timeout
The windows key timeout is longer than the wgengine watchdog timeout,
which means we never reach the timeout, instead the process exits.
Reduce the timeout so if we do hit it, at least the process continues.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-12-21 17:24:58 -05:00
David Crawshaw
b4f70d8232 wgengine/router/dns: use the correct interface GUID
On Win10, there's a hardcoded GUID and this works.
On Win7, this GUID changes and we need to ask the tun for its
LUID and convert that from the GUID.

This commit uses the computed GUID that is placed in InterfaceName.

Diagnosed by Jason Donnenfeld. (Thanks!)
2020-12-21 16:43:24 -05:00
Brad Fitzpatrick
15c064f76f wgengine/router/dns: remove unsafe endianness detection on Linux 2020-12-21 13:11:09 -08:00
Brad Fitzpatrick
f9659323df wgengine/router/dns: fix typo in comment 2020-12-21 13:07:30 -08:00
Brad Fitzpatrick
053a1d1340 all: annotate log verbosity levels on most egregiously spammy log prints
Fixes #924
Fixes #282

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-21 12:59:33 -08:00
Brad Fitzpatrick
57dd247376 cmd/tailscaled, logpolicy, logtail: support log levels
Log levels can now be specified with "[v1] " or "[v2] " substrings
that are then stripped and filtered at the final logger. This follows
our existing "[unexpected]" etc convention and doesn't require a
wholesale reworking of our logging at the moment.

cmd/tailscaled then gets a new --verbose=N flag to take a log level
that controls what gets logged to stderr (and thus systemd, syslog,
etc). Logtail is unaffected by --verbose.

This commit doesn't add annotations to any existing log prints. That
is in the next commit.

Updates #924
Updates #282

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-21 12:59:33 -08:00
Brad Fitzpatrick
d97ee12179 logtail, logpolicy: remove an unidiomatic use of an interface 2020-12-21 09:03:39 -08:00
Brad Fitzpatrick
83f45ae2dd version: bump date 2020-12-21 08:33:46 -08:00
Brad Fitzpatrick
c348fb554f control/controlclient: clarify a comment 2020-12-21 08:33:05 -08:00
Brad Fitzpatrick
90c8519765 go.sum: update 2020-12-21 08:32:51 -08:00
David Anderson
ca676ea645 tailcfg: introduce map version 8, for clients that support v6 node config.
For now, the server will only send v6 configuration to mapversion 8 clients
as part of an early-adopter program, while we verify that the functionality
is robust.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 18:28:27 -08:00
David Anderson
03a039d48d go.mod: bump wireguard-go version.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 17:26:05 -08:00
David Anderson
f5e33ad761 go.mod: update inet.af/netaddr, go mod tidy.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 14:01:43 -08:00
David Anderson
89be4037bb control/controlclient: report broken routing for v4 and v6.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
David Anderson
baa7937998 net/interfaces: return IPv6 addresses from LocalAddresses.
In practice, we already provide IPv6 endpoint addresses via netcheck,
and that address is likely to match a local address anyway (i.e. no NAT66).
The comment at that piece of the code mentions needing to figure out a
good priority ordering, but that only applies to non-active-discovery
clients, who already don't do anything with IPv6 addresses.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
David Anderson
294ceb513c ipn, wgengine/magicsock: fix tailscale status display.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
David Anderson
891110e64c wgengine: expand lazy config to work with dual-stacked peers.
Lazy wg configuration now triggers if a peer has only endpoint
addresses (/32 for IPv4, /128 for IPv6). Subnet routers still
trigger eager configuration to avoid the need for a CIDR match
in the hot packet path.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
David Anderson
aa353b8d0f net/packet: add an IP6 constructor from a raw byte array.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
Smitty
f0b0a62873 Clarify that raw format strings are intentional
This caused some confusion in issue #460, since usually raw format
strings aren't printed directly. Hopefully by directly logging that
they are intended to be raw format strings, this will be more clear.
Rate limited format strings now look like:

  [RATE LIMITED] format string "control: sendStatus: %s: %v"

Closes #460.

Signed-off-by: Smitty <me@smitop.com>
2020-12-19 13:49:14 -08:00
David Anderson
c8c493f3d9 wgengine/magicsock: make ReceiveIPv4 a little easier to follow.
The previous code used a lot of whole-function variables and shared
behavior that only triggered based on prior action from a single codepath.
Instead of that, move the small amounts of "shared" code into each switch
case.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
0ad109f63d wgengine/magicsock: move legacy endpoint creation into legacy.go.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
f873da5b16 wgengine/magicsock: move more legacy endpoint handling.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
58fcd103c4 wgengine/magicsock: move legacy sending code to legacy.go.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
65ae66260f wgengine/magicsock: unexport AddrSet.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
c9b9afd761 wgengine/magicsock: move most legacy nat traversal bits to another file.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
Brad Fitzpatrick
5f07da4854 util/systemd: don't log warnings when not running under systemd
It caused our integration tests to fail, which prohibit logging to
os.Stderr for test cleanliness reasons.
2020-12-17 12:59:05 -08:00
Brad Fitzpatrick
741c513e51 wgengine/tsdns: fix error response marshaling, improve bad query logs
Updates #995

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-17 12:46:08 -08:00
David Anderson
554a20becb wgengine/magicsock: only log about lazy config when actually doing lazy config.
Before, tailscaled would log every 10 seconds when the periodic noteRecvActivity
call happens. This is noisy, but worse it's misleading, because the message
suggests that the disco code is starting a lazy config run for a missing peer,
whereas in fact it's just an internal piece of keepalive logic.

With this change, we still log when going from 0->1 tunnel for the peer, but
not every 10s thereafter.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-17 12:11:36 -08:00
Brad Fitzpatrick
da1bad51cd tailcfg: document new OmitPeers endpoint updating functionality 2020-12-15 12:16:15 -08:00
Brad Fitzpatrick
fa412c8760 wgengine/filter, wgengine/magicsock: use new IP.BitLen to simplify some code 2020-12-15 12:12:56 -08:00
Brad Fitzpatrick
afcf134812 wgengine/filter, tailcfg: support CIDRs+ranges in PacketFilter (mapver 7)
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-15 11:00:37 -08:00
Christine Dodrill
0681c6da49 Merge pull request #967 from Xe/report-status-systemd
ipn/ipnserver: enable systemd-notify support
2020-12-15 11:44:20 -05:00
Christine Dodrill
2485faf69a Merge branch 'main' into report-status-systemd 2020-12-15 08:40:46 -05:00
Christine Dodrill
7ea809897d ipn/ipnserver: enable systemd-notify support
Addresses #964

Still to be done:
- Figure out the correct logging lines in util/systemd
- Figure out if we need to slip the systemd.Status function anywhere
  else
- Log util/systemd errors? (most of the errors are of the "you cannot do
  anything about this, but it might be a bad idea to crash the program if
  it errors" kind)

Assistance in getting this over the finish line would help a lot.

Signed-off-by: Christine Dodrill <me@christine.website>

util/systemd: rename the nonlinux file to appease the magic

Signed-off-by: Christine Dodrill <me@christine.website>

util/systemd: fix package name

Signed-off-by: Christine Dodrill <me@christine.website>

util/systemd: fix review feedback from @mdlayher

Signed-off-by: Christine Dodrill <me@christine.website>

cmd/tailscale{,d}: update depaware manifests

Signed-off-by: Christine Dodrill <me@christine.website>

util/systemd: use sync.Once instead of func init

Signed-off-by: Christine Dodrill <me@christine.website>

control/controlclient: minor review feedback fixes

Signed-off-by: Christine Dodrill <me@christine.website>

{control,ipn,systemd}: fix review feedback

Signed-off-by: Christine Dodrill <me@christine.website>

review feedback fixes

Signed-off-by: Christine Dodrill <me@christine.website>

ipn: fix sprintf call

Signed-off-by: Christine Dodrill <me@christine.website>

ipn: make staticcheck less sad

Signed-off-by: Christine Dodrill <me@christine.website>

ipn: print IP address in connected status

Signed-off-by: Christine Dodrill <me@christine.website>

ipn: review feedback

Signed-off-by: Christine Dodrill <me@christine.website>

final fixups

Signed-off-by: Christine Dodrill <me@christine.website>
2020-12-15 08:39:06 -05:00
David Anderson
9cee0bfa8c wgengine/magicsock: sprinkle more docstrings.
Magicsock is too damn big, but this might help me page it back
in faster next time.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-14 23:59:17 -08:00
Josh Bleecher Snyder
34a0292433 depaware.txt: update
Upgrading staticcheck upgraded golang.org/x/sync
(one downside of mixing our tools in with our regular go.mod),
which introduced a new dependency via
https://go-review.googlesource.com/c/sync/+/251677

That CL could and probably should be written without runtime/debug,
but it's not clear to me that that is better at this moment
than simply accepting the additional package as a dependency.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-14 14:01:29 -08:00
Josh Bleecher Snyder
ce4d68b416 go.mod: upgrade depaware version
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-14 14:01:29 -08:00
Josh Bleecher Snyder
a6cad71fb2 go.mod: upgrade staticcheck to 0.1.0
Also run go.mod and fix some staticcheck warnings.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-14 14:01:29 -08:00
Brad Fitzpatrick
a0a8b9d76a control/controlclient: don't spin when starting up when node key is expired
Fixes #1018

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-14 11:51:04 -08:00
Smitty
b895bf853a Require at least Go 1.15
This was actually required before this commit, this just updates
go.mod with that fact.

Signed-off-by: Smitty <me@smitop.com>
2020-12-13 16:36:25 -08:00
Smitty
8a57f920ae Remove unused .gitignore lines
These ignore built files that don't exist anymore, and just serve
to clutter up the .gitignore file. (I was initially confused when
I saw those lines, since I (correctly) thought that the only
Tailscale binaries were tailscale and tailscaled):

- taillogin was removed in d052586
- relaynode was removed in a56e853

Signed-off-by: Smitty <me@smitop.com>
2020-12-12 16:11:58 -08:00
Josh Bleecher Snyder
6db9c4a173 wgenginer/router/dns: use constant from golang.org/x/sys/windows
Made available in https://golang.org/cl/277153
2020-12-10 17:23:01 -08:00
Aleksandar Pesic
0dc295a640 Isolate WireGuard code into a separate file with appropriate copyright info in header.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 01:08:41 +01:00
Aleksandar Pesic
d854fe95d2 Trivial change in function description.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 00:55:37 +01:00
Aleksandar Pesic
4749a96a5b Update depaware.txt files.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 00:45:31 +01:00
Aleksandar Pesic
338fd44657 Replace registry-access code, update wireguard-go and x/sys/windows.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 00:37:24 +01:00
Aleksandar Pesic
274d32d0aa Prepare for the new wireguard-go API.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 00:08:28 +01:00
Adrian Dewhurst
943860fde7 version: relax git detection logic (again)
This is a repeat of commit 3aa68cd397
which was lost in a rework of version.sh.

git worktrees have a .git file rather than a .git directory, so building
in a worktree caused version.sh to generate an error.

Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
2020-12-09 21:55:41 -05:00
Brad Fitzpatrick
bce865b61b logpolicy: migrate from x/crypto/ssh/terminal to x/term 2020-12-09 15:28:31 -08:00
David Anderson
57cd7738c2 tsweb: add an endpoint to manually trigger a GC.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-08 16:49:42 -08:00
Brad Fitzpatrick
9cb6ee3777 go.mod, go.sum: update 2020-12-08 15:23:56 -08:00
Brad Fitzpatrick
08f94b3b50 net/netcheck: fix offset of unspecified address in PCP request packet
Fixes #810
2020-12-08 15:22:26 -08:00
Brad Fitzpatrick
442d1873ec go.mod: bump tailscale/wireguard-go 2020-12-07 14:02:05 -08:00
Brad Fitzpatrick
19c2c6403d Update go.sum 2020-12-07 14:00:53 -08:00
Brad Fitzpatrick
b3c7b631c2 tailcfg, control/controlclient: make nil PacketFilter mean unchanged (mapver 6)
After mapver 5's incremental netmap updates & user profiles, much of
the remaining bandwidth for streamed MapResponses were redundant,
unchanged PacketFilters. So make MapRequest.Version 6 mean that nil
means unchanged from the previous value.
2020-12-07 09:17:42 -08:00
Brad Fitzpatrick
05e5233e07 net/netcheck: don't send flood of PCP unmap requests to router
Updates #810
2020-12-06 19:46:11 -08:00
Brad Fitzpatrick
9503be083d tailcfg: update comments a bit 2020-12-03 12:16:10 -08:00
Brad Fitzpatrick
88179121e3 version: bump date 2020-12-03 12:08:07 -08:00
Brad Fitzpatrick
7b92f8e718 wgengine/magicsock: add start of magicsock benchmarks (Conn.ReceiveIPv4 for now)
And only single-threaded for now. Will get fancier later.

Updates #414
2020-12-02 20:26:54 -08:00
Brad Fitzpatrick
713cbe84c1 wgengine/magicsock: use net.JoinHostPort when host might have colons (udp6)
Only affected tests. (where it just generated log spam)
2020-12-02 20:19:28 -08:00
David Anderson
be6fe393c5 wgengine: don't try pinging IPv6 addresses in legacy pinger.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-01 20:09:32 -08:00
David Anderson
dfbde3d3aa ipn: pass through the prefix length from control.
Control sets this to /32 for IPv4 and /128 for IPv6.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-01 20:09:16 -08:00
David Anderson
4c8ccd6dd6 tailcfg: document new debug flag. 2020-12-01 18:17:09 -08:00
Brad Fitzpatrick
c0af7deb86 tailcfg, cmd/tailscale: add Hostinfo.ShareeNode, hide in "tailscale status" 2020-12-01 15:29:18 -08:00
Brad Fitzpatrick
ab482118ad tailcfg: add some missing json omitempty
Noticed these in MapResponses to clients.

MachineAuthorized was set true, but once we fix the coordination server
to zero out that field, then it can be omittted.
2020-11-25 10:27:01 -08:00
Dmytro Tananayskiy
c431382720 Fix receiver in order to be consistent: syncs.WaitGroupChan
Signed-off-by: Dmytro Tananayskiy <dmitriyminer@gmail.com>
2020-11-24 17:20:34 -08:00
Josh Bleecher Snyder
3a7402aa2d logtail: help the server be more efficient
Add content length hints to headers.
The server can use these hints to more efficiently select buffers.

Stop attempting to compress tiny requests.
The bandwidth savings are negligible (and sometimes negative!),
and it makes extra work for the server.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-11-24 12:00:32 -08:00
Brad Fitzpatrick
cd6099113f ipn: add a comment about skipping files with null bytes
Updates #954
2020-11-24 11:07:49 -08:00
Alex Brainman
72e082aaf5 ipn: make LoadPrefs return os.ErrNotExist when reading corrupted files
It appears some users have corrupted pref.conf files. Have LoadPrefs
treat these files as non-existent. This way tailscale will make user
login, and not crash.

Fixes #954

Signed-off-by: Alex Brainman <alex.brainman@gmail.com>
2020-11-24 11:05:42 -08:00
David Crawshaw
2c48b4ee14 tailcfg: remove outdated comments about Clone methods
The cloner tool adds static checks that the Clone methods are up to
date, so failing to update Clone causes a compiler error.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-11-24 13:16:21 -05:00
Sonia Appasamy
0710fca0cd tailcfg: include ShieldsUp in HostInfo 2020-11-24 10:51:13 -05:00
Josh Bleecher Snyder
aa9d7f4665 tstime: add Parse3339B, for byte slices
Use go4.org/mem for memory safety.
A slight performance hit, but a huge performance win
for clients who start with a []byte.
The perf hit is due largely to the MapHash call, which adds ~25ns.
That is necessary to keep the fast path allocation-free.

name                     old time/op    new time/op    delta
GoParse3339/Z-8             281ns ± 1%     283ns ± 2%     ~     (p=0.366 n=9+9)
GoParse3339/TZ-8            509ns ± 0%     510ns ± 1%     ~     (p=0.059 n=9+9)
GoParse3339InLocation-8     330ns ± 1%     330ns ± 0%     ~     (p=0.802 n=10+6)
Parse3339/Z-8              69.3ns ± 1%    74.4ns ± 1%   +7.45%  (p=0.000 n=9+10)
Parse3339/TZ-8              110ns ± 1%     140ns ± 3%  +27.42%  (p=0.000 n=9+10)
ParseInt-8                 8.20ns ± 1%    8.17ns ± 1%     ~     (p=0.452 n=9+9)

name                     old alloc/op   new alloc/op   delta
GoParse3339/Z-8             0.00B          0.00B          ~     (all equal)
GoParse3339/TZ-8             160B ± 0%      160B ± 0%     ~     (all equal)
GoParse3339InLocation-8     0.00B          0.00B          ~     (all equal)
Parse3339/Z-8               0.00B          0.00B          ~     (all equal)
Parse3339/TZ-8              0.00B          0.00B          ~     (all equal)

name                     old allocs/op  new allocs/op  delta
GoParse3339/Z-8              0.00           0.00          ~     (all equal)
GoParse3339/TZ-8             3.00 ± 0%      3.00 ± 0%     ~     (all equal)
GoParse3339InLocation-8      0.00           0.00          ~     (all equal)
Parse3339/Z-8                0.00           0.00          ~     (all equal)
Parse3339/TZ-8               0.00           0.00          ~     (all equal)


Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-11-19 14:47:11 -08:00
Josh Bleecher Snyder
a5dd0bcb09 util/jsonutil: new package
The cornerstone API is a more memory-efficient Unmarshal.
The savings come from re-using a json.Decoder.

BenchmarkUnmarshal-8      	 4016418	       288 ns/op	       8 B/op	       1 allocs/op
BenchmarkStdUnmarshal-8   	 4189261	       283 ns/op	     184 B/op	       2 allocs/op

It also includes a Bytes type to reduce allocations
when unmarshalling a non-hex-encoded JSON string into a []byte.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-11-19 13:58:35 -08:00
Josh Bleecher Snyder
b65eee0745 util/lineread: add docs to Reader
In particular, point out how to stop reading
and detect it on the other side.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-11-19 12:14:58 -08:00
Josh Bleecher Snyder
1ebbaaaebb net/interfaces: make syscall and netstat agree when multiple gateways are present
likelyHomeRouterIPDarwinSyscall iterates through the list of routes,
looking for a private gateway, returning the first one it finds.

likelyHomeRouterIPDarwinExec does the same thing,
except that it returns the last one it finds.

As a result, when there are multiple gateways,
TestLikelyHomeRouterIPSyscallExec fails.
(At least, I think that that is what is happening;
I am going inferring from observed behavior.)

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-11-19 12:14:58 -08:00
Brad Fitzpatrick
eccc167733 wgengine/monitor: fix memory corruption in Windows implementation
I used the Windows APIs wrong previously, but it had worked just
enough.

Updates #921

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-11-18 14:51:27 -08:00
Brad Fitzpatrick
8f76548fd9 tempfork/osexec: remove old fork of os/exec
This package was a temporary fork of os/exec to fix an EINTR loop
bug that was fixed upstream for Go 1.15 in
8c1db77a92
(https://go-review.googlesource.com/c/go/+/232862), in
src/os/exec_unix.go:

8c1db77a92 (diff-72072cbd53a7240debad8aa506ff7ec795f9cfac7322e779f9bac29a4d0d0bd4)
2020-11-18 08:42:43 -08:00
Brad Fitzpatrick
5b338bf011 tempfork/registry: delete
It's unused.
2020-11-18 08:29:38 -08:00
Brad Fitzpatrick
acade77c86 ipn/ipnserver: add knob to disable babysitter 2020-11-17 15:26:39 -08:00
Brad Fitzpatrick
5d96ecd5e6 net/netstat: remove a bit more unsafe
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-11-17 13:49:24 -08:00
Brad Fitzpatrick
c8939ab7c7 util/endian: add Native variable to get the platform's native binary.ByteOrder
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-11-17 13:49:24 -08:00
Josh Bleecher Snyder
883a11f2a8 logtail: fix typo in comment
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-11-17 13:43:54 -08:00
159 changed files with 7305 additions and 5790 deletions

48
.github/workflows/coverage.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Code Coverage
on:
push:
branches:
- main
pull_request:
branches:
- '*'
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
# https://markphelps.me/2019/11/speed-up-your-go-builds-with-actions-cache/
- name: Restore Cache
uses: actions/cache@preview
id: cache
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-${{ hashFiles('**/go.sum') }}
- name: Basic build
run: go build ./cmd/...
- name: Run tests on linux with coverage data
run: go test -race -coverprofile=coverage.txt -bench=. -benchtime=1x ./...
- name: coveralls.io
uses: shogo82148/actions-goveralls@v1
env:
COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
GITHUB_TOKEN: ${{ secrets.COVERALLS_BOT_PUBLIC_REPO_TOKEN }}
with:
path-to-profile: ./coverage.txt

6
.gitignore vendored
View File

@@ -6,8 +6,6 @@
*.so
*.dylib
cmd/relaynode/relaynode
cmd/taillogin/taillogin
cmd/tailscale/tailscale
cmd/tailscaled/tailscaled
@@ -19,3 +17,7 @@ cmd/tailscaled/tailscaled
# Dependency directories (remove the comment below to include it)
# vendor/
# direnv config, this may be different for other people so it's probably safer
# to make this nonspecific.
.envrc

View File

@@ -2,6 +2,23 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
############################################################################
#
# WARNING: Tailscale is not yet officially supported in Docker,
# Kubernetes, etc.
#
# It might work, but we don't regularly test it, and it's not as polished as
# our currently supported platforms. This is provided for people who know
# how Tailscale works and what they're doing.
#
# Our tracking bug for officially support container use cases is:
# https://github.com/tailscale/tailscale/issues/504
#
# Also, see the various bugs tagged "containers":
# https://github.com/tailscale/tailscale/labels/containers
#
############################################################################
# This Dockerfile includes all the tailscale binaries.
#
# To build the Dockerfile:

View File

@@ -63,8 +63,13 @@ Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin)
## About Us
We are apenwarr, bradfitz, crawshaw, danderson, dfcarney, josharian
from Tailscale Inc.
You can learn more about us from [our website](https://tailscale.com).
[Tailscale](https://tailscale.com/) is primarily developed by the
people at https://github.com/orgs/tailscale/people. For other contributors,
see:
* https://github.com/tailscale/tailscale/graphs/contributors
* https://github.com/tailscale/tailscale-android/graphs/contributors
## Legal
WireGuard is a registered trademark of Jason A. Donenfeld.

774
api.md Normal file
View File

@@ -0,0 +1,774 @@
# Tailscale API
The Tailscale API is a (mostly) RESTful API. Typically, POST bodies should be JSON encoded and responses will be JSON encoded.
# Authentication
Currently based on {some authentication method}. Visit the [admin panel](https://api.tailscale.com/admin) and navigate to the `Keys` page. Generate an API Key and keep it safe. Provide the key as the user key in basic auth when making calls to Tailscale API endpoints.
# APIs
* **[Devices](#device)**
- [GET device](#device-get)
- [DELETE device](#device-delete)
- Routes
- [GET device routes](#device-routes-get)
- [POST device routes](#device-routes-post)
* **[Tailnets](#tailnet)**
- ACLs
- [GET tailnet ACL](#tailnet-acl-get)
- [POST tailnet ACL](#tailnet-acl-post): set ACL for a tailnet
- [POST tailnet ACL preview](#tailnet-acl-preview-post): preview rule matches on an ACL for a resource
- [Devices](#tailnet-devices)
- [GET tailnet devices](#tailnet-devices-get)
- [DNS](#tailnet-dns)
- [GET tailnet DNS nameservers](#tailnet-dns-nameservers-get)
- [POST tailnet DNS nameservers](#tailnet-dns-nameservers-post)
- [GET tailnet DNS preferences](#tailnet-dns-preferences-get)
- [POST tailnet DNS preferences](#tailnet-dns-preferences-post)
- [GET tailnet DNS searchpaths](#tailnet-dns-searchpaths-get)
- [POST tailnet DNS searchpaths](#tailnet-dns-searchpaths-post)
## Device
<!-- TODO: description about what devices are -->
Each Tailscale-connected device has a globally-unique identifier number which we refer as the "deviceID" or sometimes, just "id".
You can use the deviceID to specify operations on a specific device, like retrieving its subnet routes.
To find the deviceID of a particular device, you can use the ["GET /devices"](#getdevices) API call and generate a list of devices on your network.
Find the device you're looking for and get the "id" field.
This is your deviceID.
<a name=device-get></div>
#### `GET /api/v2/device/:deviceid` - lists the details for a device
Returns the details for the specified device.
Supply the device of interest in the path using its ID.
Use the `fields` query parameter to explicitly indicate which fields are returned.
##### Parameters
##### Query Parameters
`fields` - Controls which fields will be included in the returned response.
Currently, supported options are:
* `all`: returns all fields in the response.
* `default`: return all fields except:
* `enabledRoutes`
* `advertisedRoutes`
* `clientConnectivity` (which contains the following fields: `mappingVariesByDestIP`, `derp`, `endpoints`, `latency`, and `clientSupports`)
Use commas to separate multiple options.
If more than one option is indicated, then the union is used.
For example, for `fields=default,all`, all fields are returned.
If the `fields` parameter is not provided, then the default option is used.
##### Example
```
GET /api/v2/device/12345
curl 'https://api.tailscale.com/api/v2/device/12345?fields=all' \
-u "tskey-yourapikey123:"
```
Response
```
{
"addresses":[
"100.105.58.116"
],
"id":"12345",
"user":"user1@example.com",
"name":"user1-device.example.com",
"hostname":"User1-Device",
"clientVersion":"date.20201107",
"updateAvailable":false,
"os":"macOS",
"created":"2020-11-20T20:56:49Z",
"lastSeen":"2020-11-20T16:15:55-05:00",
"keyExpiryDisabled":false,
"expires":"2021-05-19T20:56:49Z",
"authorized":true,
"isExternal":false,
"machineKey":"mkey:user1-machine-key",
"nodeKey":"nodekey:user1-node-key",
"blocksIncomingConnections":false,
"enabledRoutes":[
],
"advertisedRoutes":[
],
"clientConnectivity": {
"endpoints":[
"209.195.87.231:59128",
"192.168.0.173:59128"
],
"derp":"",
"mappingVariesByDestIP":false,
"latency":{
"Dallas":{
"latencyMs":60.463043
},
"New York City":{
"preferred":true,
"latencyMs":31.323811
},
"San Francisco":{
"latencyMs":81.313389
}
},
"clientSupports":{
"hairPinning":false,
"ipv6":false,
"pcp":false,
"pmp":false,
"udp":true,
"upnp":false
}
}
}
```
<a name=device-delete></div>
#### `DELETE /api/v2/device/:deviceID` - deletes the device from its tailnet
Deletes the provided device from its tailnet.
The device must belong to the user's tailnet.
Deleting shared/external devices is not supported.
Supply the device of interest in the path using its ID.
##### Parameters
No parameters.
##### Example
```
DELETE /api/v2/device/12345
curl -X DELETE 'https://api.tailscale.com/api/v2/device/12345' \
-u "tskey-yourapikey123:" -v
```
Response
If successful, the response should be empty:
```
< HTTP/1.1 200 OK
...
* Connection #0 to host left intact
* Closing connection 0
```
If the device is not owned by your tailnet:
```
< HTTP/1.1 501 Not Implemented
...
{"message":"cannot delete devices outside of your tailnet"}
```
<a name=device-routes-get></div>
#### `GET /api/v2/device/:deviceID/routes` - fetch subnet routes that are advertised and enabled for a device
Retrieves the list of subnet routes that a device is advertising, as well as those that are enabled for it. Enabled routes are not necessarily advertised (e.g. for pre-enabling), and likewise, advertised routes are not necessarily enabled.
##### Parameters
No parameters.
##### Example
```
curl 'https://api.tailscale.com/api/v2/device/11055/routes' \
-u "tskey-yourapikey123:"
```
Response
```
{
"advertisedRoutes" : [
"10.0.1.0/24",
"1.2.0.0/16",
"2.0.0.0/24"
],
"enabledRoutes" : []
}
```
<a name=device-routes-post></div>
#### `POST /api/v2/device/:deviceID/routes` - set the subnet routes that are enabled for a device
Sets which subnet routes are enabled to be routed by a device by replacing the existing list of subnet routes with the supplied parameters. Routes can be enabled without a device advertising them (e.g. for preauth). Returns a list of enabled subnet routes and a list of advertised subnet routes for a device.
##### Parameters
###### POST Body
`routes` - The new list of enabled subnet routes in JSON.
```
{
"routes": ["10.0.1.0/24", "1.2.0.0/16", "2.0.0.0/24"]
}
```
##### Example
```
curl 'https://api.tailscale.com/api/v2/device/11055/routes' \
-u "tskey-yourapikey123:" \
--data-binary '{"routes": ["10.0.1.0/24", "1.2.0.0/16", "2.0.0.0/24"]}'
```
Response
```
{
"advertisedRoutes" : [
"10.0.1.0/24",
"1.2.0.0/16",
"2.0.0.0/24"
],
"enabledRoutes" : [
"10.0.1.0/24",
"1.2.0.0/16",
"2.0.0.0/24"
]
}
```
## 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.
`alice@example.com` belongs to the `example.com` tailnet and would use the following format for API calls:
```
GET /api/v2/tailnet/example.com/...
curl https://api.tailscale.com/api/v2/tailnet/example.com/...
```
For solo plans, the tailnet is the email you signed up with.
So `alice@gmail.com` has the tailnet `alice@gmail.com` since `@gmail.com` is a shared email host.
Her API calls would have the following format:
```
GET /api/v2/tailnet/alice@gmail.com/...
curl https://api.tailscale.com/api/v2/tailnet/alice@gmail.com/...
```
Tailnets are a top-level resource. ACL is an example of a resource that is tied to a top-level tailnet.
For more information on Tailscale networks/tailnets, click [here](https://tailscale.com/kb/1064/invite-team-members).
### ACL
<a name=tailnet-acl-get></a>
#### `GET /api/v2/tailnet/:tailnet/acl` - fetch ACL for a tailnet
Retrieves the ACL that is currently set for the given tailnet. Supply the tailnet of interest in the path. This endpoint can send back either the HuJSON of the ACL or a parsed JSON, depending on the `Accept` header.
##### Parameters
###### Headers
`Accept` - Response is parsed `JSON` if `application/json` is explicitly named, otherwise HuJSON will be returned.
##### Returns
Returns the ACL HuJSON by default. Returns a parsed JSON of the ACL (sans comments) if the `Accept` type is explicitly set to `application/json`. An `ETag` header is also sent in the response, which can be optionally used in POST requests to avoid missed updates.
<!-- TODO (chungdaniel): define error types and a set of docs for them -->
##### Example
###### Requesting a HuJSON response:
```
GET /api/v2/tailnet/example.com/acl
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl' \
-u "tskey-yourapikey123:" \
-H "Accept: application/hujson" \
-v
```
Response
```
...
Content-Type: application/hujson
Etag: "e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c"
...
// Example/default ACLs for unrestricted connections.
{
"Tests": [],
// Declare static groups of users beyond those in the identity service.
"Groups": {
"group:example": [
"user1@example.com",
"user2@example.com"
],
},
// Declare convenient hostname aliases to use in place of IP addresses.
"Hosts": {
"example-host-1": "100.100.100.100",
},
// Access control lists.
"ACLs": [
// Match absolutely everything. Comment out this section if you want
// to define specific ACL restrictions.
{
"Action": "accept",
"Users": [
"*"
],
"Ports": [
"*:*"
]
},
]
}
```
###### Requesting a JSON response:
```
GET /api/v2/tailnet/example.com/acl
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl' \
-u "tskey-yourapikey123:" \
-H "Accept: application/json" \
-v
```
Response
```
...
Content-Type: application/json
Etag: "e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c"
...
{
"acls" : [
{
"action" : "accept",
"ports" : [
"*:*"
],
"users" : [
"*"
]
}
],
"groups" : {
"group:example" : [
"user1@example.com",
"user2@example.com"
]
},
"hosts" : {
"example-host-1" : "100.100.100.100"
}
}
```
<a name=tailnet-acl-post></a>
#### `POST /api/v2/tailnet/:tailnet/acl` - set ACL for a tailnet
Sets the ACL for the given tailnet. HuJSON and JSON are both accepted inputs. An `If-Match` header can be set to avoid missed updates.
Returns error for invalid ACLs.
Returns error if using an `If-Match` header and the ETag does not match.
##### Parameters
###### Headers
`If-Match` - A request header. Set this value to the ETag header provided in an `ACL GET` request to avoid missed updates.
`Accept` - Sets the return type of the updated ACL. Response is parsed `JSON` if `application/json` is explicitly named, otherwise HuJSON will be returned.
###### POST Body
ACL JSON or HuJSON (see https://tailscale.com/kb/1018/acls)
##### Example
```
POST /api/v2/tailnet/example.com/acl
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl' \
-u "tskey-yourapikey123:" \
-H "If-Match: \"e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c\""
--data-binary '// Example/default ACLs for unrestricted connections.
{
// Declare tests to check functionality of ACL rules. User must be a valid user with registered machines.
"Tests": [
// {"User": "user1@example.com", "Allow": ["example-host-1:22"], "Deny": ["example-host-2:100"]},
],
// Declare static groups of users beyond those in the identity service.
"Groups": {
"group:example": [ "user1@example.com", "user2@example.com" ],
},
// Declare convenient hostname aliases to use in place of IP addresses.
"Hosts": {
"example-host-1": "100.100.100.100",
},
// Access control lists.
"ACLs": [
// Match absolutely everything. Comment out this section if you want
// to define specific ACL restrictions.
{ "Action": "accept", "Users": ["*"], "Ports": ["*:*"] },
]
}'
```
Response
```
// Example/default ACLs for unrestricted connections.
{
// Declare tests to check functionality of ACL rules. User must be a valid user with registered machines.
"Tests": [
// {"User": "user1@example.com", "Allow": ["example-host-1:22"], "Deny": ["example-host-2:100"]},
],
// Declare static groups of users beyond those in the identity service.
"Groups": {
"group:example": [ "user1@example.com", "user2@example.com" ],
},
// Declare convenient hostname aliases to use in place of IP addresses.
"Hosts": {
"example-host-1": "100.100.100.100",
},
// Access control lists.
"ACLs": [
// Match absolutely everything. Comment out this section if you want
// to define specific ACL restrictions.
{ "Action": "accept", "Users": ["*"], "Ports": ["*:*"] },
]
}
```
<a name=tailnet-acl-preview-post></a>
#### `POST /api/v2/tailnet/:tailnet/acl/preview` - preview rule matches on an ACL for a resource
Determines what rules match for a user on an ACL without saving the ACL to the server.
##### Parameters
###### Query Parameters
`user` - A user's email. The provided ACL is queried with this user to determine which rules match.
###### POST Body
ACL JSON or HuJSON (see https://tailscale.com/kb/1018/acls)
##### Example
```
POST /api/v2/tailnet/example.com/acl/preiew
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl?user=user1@example.com' \
-u "tskey-yourapikey123:" \
--data-binary '// Example/default ACLs for unrestricted connections.
{
// Declare tests to check functionality of ACL rules. User must be a valid user with registered machines.
"Tests": [
// {"User": "user1@example.com", "Allow": ["example-host-1:22"], "Deny": ["example-host-2:100"]},
],
// Declare static groups of users beyond those in the identity service.
"Groups": {
"group:example": [ "user1@example.com", "user2@example.com" ],
},
// Declare convenient hostname aliases to use in place of IP addresses.
"Hosts": {
"example-host-1": "100.100.100.100",
},
// Access control lists.
"ACLs": [
// Match absolutely everything. Comment out this section if you want
// to define specific ACL restrictions.
{ "Action": "accept", "Users": ["*"], "Ports": ["*:*"] },
]
}'
```
Response
```
{"matches":[{"users":["*"],"ports":["*:*"],"lineNumber":19}],"user":"user1@example.com"}
```
<a name=tailnet-devices></a>
### Devices
<a name=tailnet-devices-get></a>
#### <a name="getdevices"></a> `GET /api/v2/tailnet/:tailnet/devices` - list the devices for a tailnet
Lists the devices in a tailnet.
Supply the tailnet of interest in the path.
Use the `fields` query parameter to explicitly indicate which fields are returned.
##### Parameters
###### Query Parameters
`fields` - Controls which fields will be included in the returned response.
Currently, supported options are:
* `all`: Returns all fields in the response.
* `default`: return all fields except:
* `enabledRoutes`
* `advertisedRoutes`
* `clientConnectivity` (which contains the following fields: `mappingVariesByDestIP`, `derp`, `endpoints`, `latency`, and `clientSupports`)
Use commas to separate multiple options.
If more than one option is indicated, then the union is used.
For example, for `fields=default,all`, all fields are returned.
If the `fields` parameter is not provided, then the default option is used.
##### Example
```
GET /api/v2/tailnet/example.com/devices
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/devices' \
-u "tskey-yourapikey123:"
```
Response
```
{
"devices":[
{
"addresses":[
"100.68.203.125"
],
"clientVersion":"date.20201107",
"os":"macOS",
"name":"user1-device.example.com",
"created":"2020-11-30T22:20:04Z",
"lastSeen":"2020-11-30T17:20:04-05:00",
"hostname":"User1-Device",
"machineKey":"mkey:user1-node-key",
"nodeKey":"nodekey:user1-node-key",
"id":"12345",
"user":"user1@example.com",
"expires":"2021-05-29T22:20:04Z",
"keyExpiryDisabled":false,
"authorized":false,
"isExternal":false,
"updateAvailable":false,
"blocksIncomingConnections":false,
},
{
"addresses":[
"100.111.63.90"
],
"clientVersion":"date.20201107",
"os":"macOS",
"name":"user2-device.example.com",
"created":"2020-11-30T22:21:03Z",
"lastSeen":"2020-11-30T17:21:03-05:00",
"hostname":"User2-Device",
"machineKey":"mkey:user2-machine-key",
"nodeKey":"nodekey:user2-node-key",
"id":"48810",
"user":"user2@example.com",
"expires":"2021-05-29T22:21:03Z",
"keyExpiryDisabled":false,
"authorized":false,
"isExternal":false,
"updateAvailable":false,
"blocksIncomingConnections":false,
}
]
}
```
<a name=tailnet-dns></a>
### DNS
<a name=tailnet-dns-nameservers-get></a>
#### `GET /api/v2/tailnet/:tailnet/dns/nameservers` - list the DNS nameservers for a tailnet
Lists the DNS nameservers for a tailnet.
Supply the tailnet of interest in the path.
##### Parameters
No parameters.
##### Example
```
GET /api/v2/tailnet/example.com/dns/nameservers
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/nameservers' \
-u "tskey-yourapikey123:"
```
Response
```
{
"dns": ["8.8.8.8"],
}
```
<a name=tailnet-dns-nameservers-post></a>
#### `POST /api/v2/tailnet/:tailnet/dns/nameservers` - replaces the list of DNS nameservers for a tailnet
Replaces the list of DNS nameservers for the given tailnet with the list supplied by the user.
Supply the tailnet of interest in the path.
Note that changing the list of DNS nameservers may also affect the status of MagicDNS (if MagicDNS is on).
##### Parameters
###### POST Body
`dns` - The new list of DNS nameservers in JSON.
```
{
"dns":["8.8.8.8"]
}
```
##### Returns
Returns the new list of nameservers and the status of MagicDNS.
If all nameservers have been removed, MagicDNS will be automatically disabled (until explicitly turned back on by the user).
##### Example
###### Adding DNS nameservers with the MagicDNS on:
```
POST /api/v2/tailnet/example.com/dns/nameservers
curl -X POST 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/nameservers' \
-u "tskey-yourapikey123:" \
--data-binary '{"dns": ["8.8.8.8"]}'
```
Response:
```
{
"dns":["8.8.8.8"],
"magicDNS":true,
}
```
###### Removing all DNS nameservers with the MagicDNS on:
```
POST /api/v2/tailnet/example.com/dns/nameservers
curl -X POST 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/nameservers' \
-u "tskey-yourapikey123:" \
--data-binary '{"dns": []}'
```
Response:
```
{
"dns":[],
"magicDNS": false,
}
```
<a name=tailnet-dns-preferences-get></a>
#### `GET /api/v2/tailnet/:tailnet/dns/preferences` - retrieves the DNS preferences for a tailnet
Retrieves the DNS preferences that are currently set for the given tailnet.
Supply the tailnet of interest in the path.
##### Parameters
No parameters.
##### Example
```
GET /api/v2/tailnet/example.com/dns/preferences
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/preferences' \
-u "tskey-yourapikey123:"
```
Response:
```
{
"magicDNS":false,
}
```
<a name=tailnet-dns-preferences-post></a>
#### `POST /api/v2/tailnet/:tailnet/dns/preferences` - replaces the DNS preferences for a tailnet
Replaces the DNS preferences for a tailnet, specifically, the MagicDNS setting.
Note that MagicDNS is dependent on DNS servers.
If there is at least one DNS server, then MagicDNS can be enabled.
Otherwise, it returns an error.
Note that removing all nameservers will turn off MagicDNS.
To reenable it, nameservers must be added back, and MagicDNS must be explicitly turned on.
##### Parameters
###### POST Body
The DNS preferences in JSON. Currently, MagicDNS is the only setting available.
`magicDNS` - Automatically registers DNS names for devices in your tailnet.
```
{
"magicDNS": true
}
```
##### Example
```
POST /api/v2/tailnet/example.com/dns/preferences
curl -X POST 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/preferences' \
-u "tskey-yourapikey123:" \
--data-binary '{"magicDNS": true}'
```
Response:
If there are no DNS servers, it returns an error message:
```
{
"message":"need at least one nameserver to enable MagicDNS"
}
```
If there are DNS servers:
```
{
"magicDNS":true,
}
```
<a name=tailnet-dns-searchpaths-get></a>
#### `GET /api/v2/tailnet/:tailnet/dns/searchpaths` - retrieves the search paths for a tailnet
Retrieves the list of search paths that is currently set for the given tailnet.
Supply the tailnet of interest in the path.
##### Parameters
No parameters.
##### Example
```
GET /api/v2/tailnet/example.com/dns/searchpaths
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/searchpaths' \
-u "tskey-yourapikey123:"
```
Response:
```
{
"searchPaths": ["user1.example.com"],
}
```
<a name=tailnet-dns-searchpaths-post></a>
#### `POST /api/v2/tailnet/:tailnet/dns/searchpaths` - replaces the search paths for a tailnet
Replaces the list of searchpaths with the list supplied by the user and returns an error otherwise.
##### Parameters
###### POST Body
`searchPaths` - A list of searchpaths in JSON.
```
{
"searchPaths: ["user1.example.com", "user2.example.com"]
}
```
##### Example
```
POST /api/v2/tailnet/example.com/dns/searchpaths
curl -X POST 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/searchpaths' \
-u "tskey-yourapikey123:" \
--data-binary '{"searchPaths": ["user1.example.com", "user2.example.com"]}'
```
Response:
```
{
"searchPaths": ["user1.example.com", "user2.example.com"],
}
```

View File

@@ -140,7 +140,7 @@ func main() {
flag.Usage()
os.Exit(2)
}
if err := ioutil.WriteFile(output, out, 0666); err != nil {
if err := ioutil.WriteFile(output, out, 0644); err != nil {
log.Fatal(err)
}
}

View File

@@ -25,7 +25,6 @@ import (
"strings"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/crypto/acme/autocert"
"tailscale.com/atomicfile"
"tailscale.com/derp"
@@ -35,6 +34,7 @@ import (
"tailscale.com/net/stun"
"tailscale.com/tsweb"
"tailscale.com/types/key"
"tailscale.com/types/wgkey"
"tailscale.com/version"
)
@@ -51,7 +51,7 @@ var (
)
type config struct {
PrivateKey wgcfg.PrivateKey
PrivateKey wgkey.Private
}
func loadConfig() config {
@@ -77,8 +77,8 @@ func loadConfig() config {
}
}
func mustNewKey() wgcfg.PrivateKey {
key, err := wgcfg.NewPrivateKey()
func mustNewKey() wgkey.Private {
key, err := wgkey.NewPrivate()
if err != nil {
log.Fatal(err)
}
@@ -97,7 +97,7 @@ func writeNewConfig() config {
if err != nil {
log.Fatal(err)
}
if err := atomicfile.WriteFile(*configPath, b, 0666); err != nil {
if err := atomicfile.WriteFile(*configPath, b, 0600); err != nil {
log.Fatal(err)
}
return cfg

View File

@@ -99,7 +99,7 @@ func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context
if runtime.GOOS != "windows" && rootArgs.socket == "" {
fatalf("--socket cannot be empty")
}
fatalf("Failed to connect to connect to tailscaled. (safesocket.Connect: %v)\n", err)
fatalf("Failed to connect to tailscaled. (safesocket.Connect: %v)\n", err)
}
clientToServer := func(b []byte) {
ipn.WriteMsg(c, b)

View File

@@ -14,6 +14,8 @@ import (
"net"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
@@ -21,6 +23,7 @@ import (
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/interfaces"
"tailscale.com/util/dnsname"
)
var statusCmd = &ffcli.Command{
@@ -34,6 +37,7 @@ var statusCmd = &ffcli.Command{
fs.BoolVar(&statusArgs.web, "web", false, "run webserver with HTML showing status")
fs.BoolVar(&statusArgs.active, "active", false, "filter output to only peers with active sessions (not applicable to web mode)")
fs.BoolVar(&statusArgs.self, "self", true, "show status of local machine")
fs.BoolVar(&statusArgs.peers, "peers", true, "show status of peers")
fs.StringVar(&statusArgs.listen, "listen", "127.0.0.1:8384", "listen address; use port 0 for automatic")
fs.BoolVar(&statusArgs.browser, "browser", true, "Open a browser in web mode")
return fs
@@ -47,6 +51,7 @@ var statusArgs struct {
browser bool // in web mode, whether to open browser
active bool // in CLI mode, filter output to only peers with active sessions
self bool // in CLI mode, show status of local machine
peers bool // in CLI mode, show status of peer machines
}
func runStatus(ctx context.Context, args []string) error {
@@ -136,30 +141,30 @@ func runStatus(ctx context.Context, args []string) error {
f := func(format string, a ...interface{}) { fmt.Fprintf(&buf, format, a...) }
printPS := func(ps *ipnstate.PeerStatus) {
active := peerActive(ps)
f("%s %-7s %-15s %-18s tx=%8d rx=%8d ",
ps.PublicKey.ShortString(),
ps.OS,
f("%-15s %-20s %-12s %-7s ",
ps.TailAddr,
ps.SimpleHostName(),
ps.TxBytes,
ps.RxBytes,
dnsOrQuoteHostname(st, ps),
ownerLogin(st, ps),
ps.OS,
)
relay := ps.Relay
if active && relay != "" && ps.CurAddr == "" {
relay = "*" + relay + "*"
} else {
relay = " " + relay
}
f("%-6s", relay)
for i, addr := range ps.Addrs {
if i != 0 {
f(", ")
}
if addr == ps.CurAddr {
f("*%s*", addr)
anyTraffic := ps.TxBytes != 0 || ps.RxBytes != 0
if !active {
if anyTraffic {
f("idle")
} else {
f("%s", addr)
f("-")
}
} else {
f("active; ")
if relay != "" && ps.CurAddr == "" {
f("relay %q", relay)
} else if ps.CurAddr != "" {
f("direct %s", ps.CurAddr)
}
}
if anyTraffic {
f(", tx %d rx %d", ps.TxBytes, ps.RxBytes)
}
f("\n")
}
@@ -167,13 +172,23 @@ func runStatus(ctx context.Context, args []string) error {
if statusArgs.self && st.Self != nil {
printPS(st.Self)
}
for _, peer := range st.Peers() {
ps := st.Peer[peer]
active := peerActive(ps)
if statusArgs.active && !active {
continue
if statusArgs.peers {
var peers []*ipnstate.PeerStatus
for _, peer := range st.Peers() {
ps := st.Peer[peer]
if ps.ShareeNode {
continue
}
peers = append(peers, ps)
}
sort.Slice(peers, func(i, j int) bool { return sortKey(peers[i]) < sortKey(peers[j]) })
for _, ps := range peers {
active := peerActive(ps)
if statusArgs.active && !active {
continue
}
printPS(ps)
}
printPS(ps)
}
os.Stdout.Write(buf.Bytes())
return nil
@@ -185,3 +200,37 @@ func runStatus(ctx context.Context, args []string) error {
func peerActive(ps *ipnstate.PeerStatus) bool {
return !ps.LastWrite.IsZero() && time.Since(ps.LastWrite) < 2*time.Minute
}
func dnsOrQuoteHostname(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
if i := strings.Index(ps.DNSName, "."); i != -1 && dnsname.HasSuffix(ps.DNSName, st.MagicDNSSuffix) {
return ps.DNSName[:i]
}
if ps.DNSName != "" {
return strings.TrimRight(ps.DNSName, ".")
}
return fmt.Sprintf("(%q)", strings.ReplaceAll(ps.SimpleHostName(), " ", "_"))
}
func sortKey(ps *ipnstate.PeerStatus) string {
if ps.DNSName != "" {
return ps.DNSName
}
if ps.HostName != "" {
return ps.HostName
}
return ps.TailAddr
}
func ownerLogin(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
if ps.UserID.IsZero() {
return "-"
}
u, ok := st.User[ps.UserID]
if !ok {
return fmt.Sprint(ps.UserID)
}
if i := strings.Index(u.LoginName, "@"); i != -1 {
return u.LoginName[:i+1]
}
return u.LoginName
}

View File

@@ -19,7 +19,6 @@ import (
"sync"
"github.com/peterbourgon/ff/v2/ffcli"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
@@ -85,29 +84,6 @@ var upArgs struct {
hostname string
}
// parseIPOrCIDR parses an IP address or a CIDR prefix. If the input
// is an IP address, it is returned in CIDR form with a /32 mask for
// IPv4 or a /128 mask for IPv6.
func parseIPOrCIDR(s string) (wgcfg.CIDR, bool) {
if strings.Contains(s, "/") {
ret, err := wgcfg.ParseCIDR(s)
if err != nil {
return wgcfg.CIDR{}, false
}
return ret, true
}
ip, ok := wgcfg.ParseIP(s)
if !ok {
return wgcfg.CIDR{}, false
}
if ip.Is4() {
return wgcfg.CIDR{IP: ip, Mask: 32}, true
} else {
return wgcfg.CIDR{IP: ip, Mask: 128}, true
}
}
func isBSD(s string) bool {
return s == "dragonfly" || s == "freebsd" || s == "netbsd" || s == "openbsd"
}
@@ -162,19 +138,18 @@ func runUp(ctx context.Context, args []string) error {
}
}
var routes []wgcfg.CIDR
var routes []netaddr.IPPrefix
if upArgs.advertiseRoutes != "" {
advroutes := strings.Split(upArgs.advertiseRoutes, ",")
for _, s := range advroutes {
cidr, ok := parseIPOrCIDR(s)
ipp, err := netaddr.ParseIPPrefix(s) // parse it with other pawith both packages
if !ok || err != nil {
ipp, err := netaddr.ParseIPPrefix(s)
if err != nil {
fatalf("%q is not a valid IP address or CIDR prefix", s)
}
if ipp != ipp.Masked() {
fatalf("%s has non-address bits set; expected %s", ipp, ipp.Masked())
}
routes = append(routes, cidr)
routes = append(routes, ipp)
}
checkIPForwarding()
}

View File

@@ -9,11 +9,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
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/wgengine/router/dns
github.com/golang/groupcache/lru from tailscale.com/wgengine/filter+
L github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
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
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
@@ -24,18 +24,15 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/iphlpapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/namespaceapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/nci from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/registry from github.com/tailscale/wireguard-go/tun/wintun+
W 💣 github.com/tailscale/wireguard-go/tun/wintun/setupapi from github.com/tailscale/wireguard-go/tun/wintun
github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+
W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun+
github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/device+
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
💣 go4.org/intern from inet.af/netaddr
💣 go4.org/mem from tailscale.com/control/controlclient+
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
inet.af/netaddr from tailscale.com/cmd/tailscale/cli+
rsc.io/goversion/version from tailscale.com/version
@@ -54,6 +51,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/logtail/backoff from tailscale.com/control/controlclient+
tailscale.com/metrics from tailscale.com/derp
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
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/netns from tailscale.com/control/controlclient+
@@ -67,7 +65,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
DW tailscale.com/tempfork/osexec from tailscale.com/portlist
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/key from tailscale.com/cmd/tailscale/cli+
@@ -76,8 +73,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/opt from tailscale.com/control/controlclient+
tailscale.com/types/strbuilder from tailscale.com/net/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
W tailscale.com/util/endian from tailscale.com/net/netns
tailscale.com/types/wgkey from tailscale.com/control/controlclient+
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
LW tailscale.com/util/endian from tailscale.com/net/netns+
tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/util/systemd from tailscale.com/control/controlclient+
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine from tailscale.com/ipn
@@ -85,7 +85,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/wgengine/magicsock from tailscale.com/wgengine
💣 tailscale.com/wgengine/monitor from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscale/cli+
💣 tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/tsdns from tailscale.com/ipn+
tailscale.com/wgengine/tstun from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
@@ -119,11 +119,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+
W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+
W golang.org/x/sys/windows/registry from github.com/tailscale/wireguard-go/tun/wintun+
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
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 github.com/tailscale/wireguard-go/tun/wintun+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from tailscale.com/types/logger+
bufio from compress/flate+
bytes from bufio+
@@ -171,6 +171,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+
hash/fnv from tailscale.com/wgengine/magicsock
hash/maphash from go4.org/mem
html from tailscale.com/ipn/ipnstate
io from bufio+
io/ioutil from crypto/tls+
@@ -197,6 +198,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
reflect from crypto/x509+
regexp from github.com/coreos/go-iptables/iptables+
regexp/syntax from regexp
runtime/debug from golang.org/x/sync/singleflight
runtime/pprof from tailscale.com/log/logheap+
sort from compress/flate+
strconv from compress/flate+

View File

@@ -9,8 +9,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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/wgengine/router/dns
github.com/golang/groupcache/lru from tailscale.com/wgengine/filter+
L github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
@@ -19,6 +19,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd
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
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+
github.com/tailscale/wireguard-go/device/tokenbucket from github.com/tailscale/wireguard-go/device
@@ -27,18 +28,44 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/iphlpapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/namespaceapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/nci from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/registry from github.com/tailscale/wireguard-go/tun/wintun+
W 💣 github.com/tailscale/wireguard-go/tun/wintun/setupapi from github.com/tailscale/wireguard-go/tun/wintun
github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+
W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun+
github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/device+
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
💣 go4.org/intern from inet.af/netaddr
💣 go4.org/mem from tailscale.com/control/controlclient+
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
💣 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/tcpip/stack+
gvisor.dev/gvisor/pkg/rand from gvisor.dev/gvisor/pkg/tcpip/network/hash+
💣 gvisor.dev/gvisor/pkg/sleep from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/tcpip+
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/link/channel+
gvisor.dev/gvisor/pkg/tcpip/header/parse from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/link/channel from tailscale.com/wgengine/netstack
gvisor.dev/gvisor/pkg/tcpip/network/fragmentation from gvisor.dev/gvisor/pkg/tcpip/network/ipv4
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4
gvisor.dev/gvisor/pkg/tcpip/network/ip from gvisor.dev/gvisor/pkg/tcpip/network/ipv4
gvisor.dev/gvisor/pkg/tcpip/network/ipv4 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/icmp from tailscale.com/wgengine/netstack
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/tcpip+
inet.af/netaddr from tailscale.com/control/controlclient+
rsc.io/goversion/version from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn+
@@ -59,6 +86,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/logtail/filch from tailscale.com/logpolicy
tailscale.com/metrics from tailscale.com/derp
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+
💣 tailscale.com/net/interfaces from tailscale.com/ipn+
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
tailscale.com/net/netns from tailscale.com/control/controlclient+
@@ -74,7 +102,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/control/controlclient+
DW tailscale.com/tempfork/osexec from tailscale.com/portlist
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
@@ -84,20 +111,24 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/opt from tailscale.com/control/controlclient+
tailscale.com/types/strbuilder from tailscale.com/net/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
W tailscale.com/util/endian from tailscale.com/net/netns+
tailscale.com/types/wgkey from tailscale.com/control/controlclient+
tailscale.com/util/dnsname from tailscale.com/control/controlclient+
LW tailscale.com/util/endian from tailscale.com/net/netns+
tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
tailscale.com/util/racebuild from tailscale.com/logpolicy
tailscale.com/util/systemd from tailscale.com/control/controlclient+
tailscale.com/version from tailscale.com/cmd/tailscaled+
tailscale.com/version/distro from tailscale.com/control/controlclient+
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/magicsock from tailscale.com/cmd/tailscaled+
💣 tailscale.com/wgengine/monitor from tailscale.com/wgengine
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
💣 tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/tsdns from tailscale.com/ipn+
tailscale.com/wgengine/tstun from tailscale.com/wgengine
tailscale.com/wgengine/tstun from tailscale.com/wgengine+
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+
@@ -111,7 +142,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/crypto/ssh/terminal from tailscale.com/logpolicy
golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal
golang.org/x/net/dns/dnsmessage from net+
@@ -130,17 +160,19 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+
W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+
W golang.org/x/sys/windows/registry from github.com/tailscale/wireguard-go/tun/wintun+
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
golang.org/x/term from tailscale.com/logpolicy
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
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 github.com/tailscale/wireguard-go/tun/wintun+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from tailscale.com/types/logger+
bufio from compress/flate+
bytes from bufio+
compress/flate from compress/gzip+
compress/gzip from internal/profile+
compress/zlib from debug/elf+
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
container/list from crypto/tls+
context from crypto/tls+
crypto from crypto/ecdsa+
@@ -182,6 +214,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+
hash/fnv from tailscale.com/wgengine/magicsock
hash/maphash from go4.org/mem
html from html/template+
html/template from net/http/pprof
io from bufio+

View File

@@ -33,6 +33,7 @@ import (
"tailscale.com/version"
"tailscale.com/wgengine"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/netstack"
"tailscale.com/wgengine/router"
)
@@ -64,6 +65,7 @@ var args struct {
port uint16
statepath string
socketpath string
verbose int
}
func main() {
@@ -76,6 +78,7 @@ func main() {
}
printVersion := false
flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose")
flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit")
flag.BoolVar(&args.fake, "fake", false, "use userspace fake tunnel+routing instead of kernel TUN interface")
flag.StringVar(&args.debug, "debug", "", "listen address ([ip]:port) of optional debug server")
@@ -118,6 +121,7 @@ func run() error {
var err error
pol := logpolicy.New("tailnode.log.tailscale.io")
pol.SetVerbosityLevel(args.verbose)
defer func() {
// Finish uploading logs after closing everything else.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
@@ -144,7 +148,11 @@ func run() error {
var e wgengine.Engine
if args.fake {
e, err = wgengine.NewFakeUserspaceEngine(logf, args.port)
var impl wgengine.FakeImplFunc
if args.tunname == "userspace-networking" {
impl = netstack.Impl
}
e, err = wgengine.NewFakeUserspaceEngine(logf, 0, impl)
} else {
e, err = wgengine.NewUserspaceEngine(logf, args.tunname, args.port)
}

View File

@@ -18,6 +18,24 @@ StateDirectory=tailscale
StateDirectoryMode=0750
CacheDirectory=tailscale
CacheDirectoryMode=0750
Type=notify
DeviceAllow=/dev/net/tun
DeviceAllow=/dev/null
DeviceAllow=/dev/random
DeviceAllow=/dev/urandom
DevicePolicy=strict
LockPersonality=true
MemoryDenyWriteExecute=true
PrivateTmp=true
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectKernelTunables=true
ProtectSystem=strict
ReadWritePaths=/etc/
RestrictSUIDSGID=true
SystemCallArchitectures=native
[Install]
WantedBy=multi-user.target

View File

@@ -17,13 +17,13 @@ import (
"sync"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/oauth2"
"tailscale.com/logtail/backoff"
"tailscale.com/tailcfg"
"tailscale.com/types/empty"
"tailscale.com/types/logger"
"tailscale.com/types/structs"
"tailscale.com/types/wgkey"
)
// State is the high-level state of the client. It is used only in
@@ -117,15 +117,16 @@ type Client struct {
mu sync.Mutex // mutex guards the following fields
statusFunc func(Status) // called to update Client status
paused bool // whether we should stop making HTTP requests
unpauseWaiters []chan struct{}
loggedIn bool // true if currently logged in
loginGoal *LoginGoal // non-nil if some login activity is desired
synced bool // true if our netmap is up-to-date
hostinfo *tailcfg.Hostinfo
inPollNetMap bool // true if currently running a PollNetMap
inSendStatus int // number of sendStatus calls currently in progress
state State
paused bool // whether we should stop making HTTP requests
unpauseWaiters []chan struct{}
loggedIn bool // true if currently logged in
loginGoal *LoginGoal // non-nil if some login activity is desired
synced bool // true if our netmap is up-to-date
hostinfo *tailcfg.Hostinfo
inPollNetMap bool // true if currently running a PollNetMap
inLiteMapUpdate bool // true if a lite (non-streaming) map request is outstanding
inSendStatus int // number of sendStatus calls currently in progress
state State
authCtx context.Context // context used for auth requests
mapCtx context.Context // context used for netmap requests
@@ -182,7 +183,8 @@ func (c *Client) SetPaused(paused bool) {
}
c.paused = paused
if paused {
// Just cancel the map routine. The auth routine isn't expensive.
// Only cancel the map routine. (The auth routine isn't expensive
// so it's fine to keep it running.)
c.cancelMapLocked()
} else {
for _, ch := range c.unpauseWaiters {
@@ -200,6 +202,50 @@ func (c *Client) Start() {
go c.mapRoutine()
}
// sendNewMapRequest either sends a new OmitPeers, non-streaming map request
// (to just send Hostinfo/Netinfo/Endpoints info, while keeping an existing
// streaming response open), or start a new streaming one if necessary.
//
// It should be called whenever there's something new to tell the server.
func (c *Client) sendNewMapRequest() {
c.mu.Lock()
// If we're not already streaming a netmap, or if we're already stuck
// in a lite update, then tear down everything and start a new stream
// (which starts by sending a new map request)
if !c.inPollNetMap || c.inLiteMapUpdate {
c.mu.Unlock()
c.cancelMapSafely()
return
}
// Otherwise, send a lite update that doesn't keep a
// long-running stream response.
defer c.mu.Unlock()
c.inLiteMapUpdate = true
ctx, cancel := context.WithTimeout(c.mapCtx, 10*time.Second)
go func() {
defer cancel()
t0 := time.Now()
err := c.direct.SendLiteMapUpdate(ctx)
d := time.Since(t0).Round(time.Millisecond)
c.mu.Lock()
c.inLiteMapUpdate = false
c.mu.Unlock()
if err == nil {
c.logf("[v1] successful lite map update in %v", d)
return
}
if ctx.Err() == nil {
c.logf("lite map update after %v: %v", d, err)
}
// Fall back to restarting the long-polling map
// request (the old heavy way) if the lite update
// failed for any reason.
c.cancelMapSafely()
}()
}
func (c *Client) cancelAuth() {
c.mu.Lock()
if c.authCancel != nil {
@@ -230,7 +276,7 @@ func (c *Client) cancelMapSafely() {
c.mu.Lock()
defer c.mu.Unlock()
c.logf("cancelMapSafely: synced=%v", c.synced)
c.logf("[v1] cancelMapSafely: synced=%v", c.synced)
if c.inPollNetMap {
// received at least one netmap since the last
@@ -252,12 +298,12 @@ func (c *Client) cancelMapSafely() {
// request.
select {
case c.newMapCh <- struct{}{}:
c.logf("cancelMapSafely: wrote to channel")
c.logf("[v1] cancelMapSafely: wrote to channel")
default:
// if channel write failed, then there was already
// an outstanding newMapCh request. One is enough,
// since it'll always use the latest endpoints.
c.logf("cancelMapSafely: channel was full")
c.logf("[v1] cancelMapSafely: channel was full")
}
}
}
@@ -268,22 +314,24 @@ func (c *Client) authRoutine() {
for {
c.mu.Lock()
c.logf("authRoutine: %s", c.state)
expiry := c.expiry
goal := c.loginGoal
ctx := c.authCtx
synced := c.synced
if goal != nil {
c.logf("authRoutine: %s; wantLoggedIn=%v", c.state, goal.wantLoggedIn)
} else {
c.logf("authRoutine: %s; goal=nil", c.state)
}
c.mu.Unlock()
select {
case <-c.quit:
c.logf("authRoutine: quit")
c.logf("[v1] authRoutine: quit")
return
default:
}
report := func(err error, msg string) {
c.logf("%s: %v", msg, err)
c.logf("[v1] %s: %v", msg, err)
err = fmt.Errorf("%s: %v", msg, err)
// don't send status updates for context errors,
// since context cancelation is always on purpose.
@@ -293,51 +341,13 @@ func (c *Client) authRoutine() {
}
if goal == nil {
// Wait for something interesting to happen
var exp <-chan time.Time
var expTimer *time.Timer
if expiry != nil && !expiry.IsZero() {
// if expiry is in the future, don't delay
// past that time.
// If it's in the past, then it's already
// being handled by someone, so no need to
// wake ourselves up again.
now := c.timeNow()
if expiry.Before(now) {
delay := expiry.Sub(now)
if delay > 5*time.Second {
delay = time.Second
}
expTimer = time.NewTimer(delay)
exp = expTimer.C
}
}
select {
case <-ctx.Done():
if expTimer != nil {
expTimer.Stop()
}
c.logf("authRoutine: context done.")
case <-exp:
// Unfortunately the key expiry isn't provided
// by the control server until mapRequest.
// So we have to do some hackery with c.expiry
// in here.
// TODO(apenwarr): add a key expiry field in RegisterResponse.
c.logf("authRoutine: key expiration check.")
if synced && expiry != nil && !expiry.IsZero() && expiry.Before(c.timeNow()) {
c.logf("Key expired; setting loggedIn=false.")
// Wait for user to Login or Logout.
<-ctx.Done()
c.logf("[v1] authRoutine: context done.")
continue
}
c.mu.Lock()
c.loginGoal = &LoginGoal{
wantLoggedIn: c.loggedIn,
}
c.loggedIn = false
c.expiry = nil
c.mu.Unlock()
}
}
} else if !goal.wantLoggedIn {
if !goal.wantLoggedIn {
err := c.direct.TryLogout(ctx)
if err != nil {
report(err, "TryLogout")
@@ -469,7 +479,7 @@ func (c *Client) mapRoutine() {
}
report := func(err error, msg string) {
c.logf("%s: %v", msg, err)
c.logf("[v1] %s: %v", msg, err)
err = fmt.Errorf("%s: %v", msg, err)
// don't send status updates for context errors,
// since context cancelation is always on purpose.
@@ -504,7 +514,7 @@ func (c *Client) mapRoutine() {
select {
case <-c.newMapCh:
c.logf("mapRoutine: new map request during PollNetMap. canceling.")
c.logf("[v1] mapRoutine: new map request during PollNetMap. canceling.")
c.cancelMapLocked()
// Don't emit this netmap; we're
@@ -526,7 +536,7 @@ func (c *Client) mapRoutine() {
c.mu.Unlock()
c.logf("mapRoutine: netmap received: %s", state)
c.logf("[v1] mapRoutine: netmap received: %s", state)
if stillAuthed {
c.sendStatus("mapRoutine-got-netmap", nil, "", nm)
}
@@ -580,7 +590,7 @@ func (c *Client) SetHostinfo(hi *tailcfg.Hostinfo) {
c.logf("Hostinfo: %v", hi)
// Send new Hostinfo to server
c.cancelMapSafely()
c.sendNewMapRequest()
}
func (c *Client) SetNetInfo(ni *tailcfg.NetInfo) {
@@ -588,13 +598,12 @@ func (c *Client) SetNetInfo(ni *tailcfg.NetInfo) {
panic("nil NetInfo")
}
if !c.direct.SetNetInfo(ni) {
c.logf("[unexpected] duplicate NetInfo: %v", ni)
return
}
c.logf("NetInfo: %v", ni)
// Send new Hostinfo (which includes NetInfo) to server
c.cancelMapSafely()
c.sendNewMapRequest()
}
func (c *Client) sendStatus(who string, err error, url string, nm *NetworkMap) {
@@ -607,7 +616,7 @@ func (c *Client) sendStatus(who string, err error, url string, nm *NetworkMap) {
c.inSendStatus++
c.mu.Unlock()
c.logf("sendStatus: %s: %v", who, state)
c.logf("[v1] sendStatus: %s: %v", who, state)
var p *Persist
var fin *empty.Message
@@ -671,7 +680,7 @@ func (c *Client) Logout() {
func (c *Client) UpdateEndpoints(localPort uint16, endpoints []string) {
changed := c.direct.SetEndpoints(localPort, endpoints)
if changed {
c.cancelMapSafely()
c.sendNewMapRequest()
}
}
@@ -700,7 +709,7 @@ func (c *Client) Shutdown() {
// NodePublicKey returns the node public key currently in use. This is
// used exclusively in tests.
func (c *Client) TestOnlyNodePublicKey() wgcfg.Key {
func (c *Client) TestOnlyNodePublicKey() wgkey.Key {
priv := c.direct.GetPersist()
return priv.PrivateNodeKey.Public()
}

View File

@@ -42,6 +42,11 @@ func TestStatusEqual(t *testing.T) {
&Status{},
false,
},
{
nil,
nil,
true,
},
{
&Status{},
&Status{},

View File

@@ -31,7 +31,6 @@ import (
"sync/atomic"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/crypto/nacl/box"
"golang.org/x/oauth2"
"inet.af/netaddr"
@@ -44,7 +43,10 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
"tailscale.com/types/wgkey"
"tailscale.com/util/systemd"
"tailscale.com/version"
"tailscale.com/wgengine/filter"
)
type Persist struct {
@@ -59,10 +61,10 @@ type Persist struct {
// needed. This field should be considered read-only from GUI
// frontends. The real value should not be written back in
// this field, lest the frontend persist it to disk.
LegacyFrontendPrivateMachineKey wgcfg.PrivateKey `json:"PrivateMachineKey"`
LegacyFrontendPrivateMachineKey wgkey.Private `json:"PrivateMachineKey"`
PrivateNodeKey wgcfg.PrivateKey
OldPrivateNodeKey wgcfg.PrivateKey // needed to request key rotation
PrivateNodeKey wgkey.Private
OldPrivateNodeKey wgkey.Private // needed to request key rotation
Provider string
LoginName string
}
@@ -83,7 +85,7 @@ func (p *Persist) Equals(p2 *Persist) bool {
}
func (p *Persist) Pretty() string {
var mk, ok, nk wgcfg.Key
var mk, ok, nk wgkey.Key
if !p.LegacyFrontendPrivateMachineKey.IsZero() {
mk = p.LegacyFrontendPrivateMachineKey.Public()
}
@@ -93,7 +95,7 @@ func (p *Persist) Pretty() string {
if !p.PrivateNodeKey.IsZero() {
nk = p.PrivateNodeKey.Public()
}
ss := func(k wgcfg.Key) string {
ss := func(k wgkey.Key) string {
if k.IsZero() {
return ""
}
@@ -105,22 +107,23 @@ func (p *Persist) Pretty() string {
// Direct is the client that connects to a tailcontrol server for a node.
type Direct struct {
httpc *http.Client // HTTP client used to talk to tailcontrol
serverURL string // URL of the tailcontrol server
timeNow func() time.Time
lastPrintMap time.Time
newDecompressor func() (Decompressor, error)
keepAlive bool
logf logger.Logf
discoPubKey tailcfg.DiscoKey
machinePrivKey wgcfg.PrivateKey
debugFlags []string
httpc *http.Client // HTTP client used to talk to tailcontrol
serverURL string // URL of the tailcontrol server
timeNow func() time.Time
lastPrintMap time.Time
newDecompressor func() (Decompressor, error)
keepAlive bool
logf logger.Logf
discoPubKey tailcfg.DiscoKey
machinePrivKey wgkey.Private
debugFlags []string
keepSharerAndUserSplit bool
mu sync.Mutex // mutex guards the following fields
serverKey wgcfg.Key
serverKey wgkey.Key
persist Persist
authKey string
tryingNewKey wgcfg.PrivateKey
tryingNewKey wgkey.Private
expiry *time.Time
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo // always non-nil
@@ -131,7 +134,7 @@ type Direct struct {
type Options struct {
Persist Persist // initial persistent data
MachinePrivateKey wgcfg.PrivateKey // the machine key to use
MachinePrivateKey wgkey.Private // the machine key to use
ServerURL string // URL of the tailcontrol server
AuthKey string // optional node auth key for auto registration
TimeNow func() time.Time // time.Now implementation used by Client
@@ -142,6 +145,10 @@ type Options struct {
Logf logger.Logf
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
DebugFlags []string // debug settings to send to control
// KeepSharerAndUserSplit controls whether the client
// understands Node.Sharer. If false, the Sharer is mapped to the User.
KeepSharerAndUserSplit bool
}
type Decompressor interface {
@@ -188,17 +195,18 @@ func NewDirect(opts Options) (*Direct, error) {
}
c := &Direct{
httpc: httpc,
machinePrivKey: opts.MachinePrivateKey,
serverURL: opts.ServerURL,
timeNow: opts.TimeNow,
logf: opts.Logf,
newDecompressor: opts.NewDecompressor,
keepAlive: opts.KeepAlive,
persist: opts.Persist,
authKey: opts.AuthKey,
discoPubKey: opts.DiscoPublicKey,
debugFlags: opts.DebugFlags,
httpc: httpc,
machinePrivKey: opts.MachinePrivateKey,
serverURL: opts.ServerURL,
timeNow: opts.TimeNow,
logf: opts.Logf,
newDecompressor: opts.NewDecompressor,
keepAlive: opts.KeepAlive,
persist: opts.Persist,
authKey: opts.AuthKey,
discoPubKey: opts.DiscoPublicKey,
debugFlags: opts.DebugFlags,
keepSharerAndUserSplit: opts.KeepSharerAndUserSplit,
}
if opts.Hostinfo == nil {
c.SetHostinfo(NewHostinfo())
@@ -308,6 +316,7 @@ func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags Logi
if mustregen {
_, url, err = c.doLogin(ctx, t, flags, true, url)
}
return url, err
}
@@ -328,6 +337,7 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
if expired {
c.logf("Old key expired -> regen=true")
systemd.Status("key expired; run 'tailscale up' to authenticate")
regen = true
}
if (flags & LoginInteractive) != 0 {
@@ -336,7 +346,7 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
}
c.logf("doLogin(regen=%v, hasUrl=%v)", regen, url != "")
if serverKey == (wgcfg.Key{}) {
if serverKey.IsZero() {
var err error
serverKey, err = loadServerKey(ctx, c.httpc, c.serverURL)
if err != nil {
@@ -348,12 +358,12 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
c.mu.Unlock()
}
var oldNodeKey wgcfg.Key
var oldNodeKey wgkey.Key
if url != "" {
} else if regen || persist.PrivateNodeKey.IsZero() {
c.logf("Generating a new nodekey.")
persist.OldPrivateNodeKey = persist.PrivateNodeKey
key, err := wgcfg.NewPrivateKey()
key, err := wgkey.NewPrivate()
if err != nil {
c.logf("login keygen: %v", err)
return regen, url, err
@@ -517,6 +527,18 @@ func inTest() bool { return flag.Lookup("test.v") != nil }
// maxPolls is how many network maps to download; common values are 1
// or -1 (to keep a long-poll query open to the server).
func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkMap)) error {
return c.sendMapRequest(ctx, maxPolls, cb)
}
// SendLiteMapUpdate makes a /map request to update the server of our latest state,
// but does not fetch anything. It returns an error if the server did not return a
// successful 200 OK response.
func (c *Direct) SendLiteMapUpdate(ctx context.Context) error {
return c.sendMapRequest(ctx, 1, nil)
}
// cb nil means to omit peers.
func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*NetworkMap)) error {
c.mu.Lock()
persist := c.persist
serverURL := c.serverURL
@@ -533,15 +555,17 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
}
allowStream := maxPolls != 1
c.logf("PollNetMap: stream=%v :%v ep=%v", allowStream, localPort, ep)
c.logf("[v1] PollNetMap: stream=%v :%v ep=%v", allowStream, localPort, ep)
vlogf := logger.Discard
if Debug.NetMap {
// TODO(bradfitz): update this to use "[v2]" prefix perhaps? but we don't
// want to upload it always.
vlogf = c.logf
}
request := tailcfg.MapRequest{
Version: 5,
Version: tailcfg.CurrentMapRequestVersion,
KeepAlive: c.keepAlive,
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
DiscoKey: c.discoPubKey,
@@ -549,6 +573,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
Stream: allowStream,
Hostinfo: hostinfo,
DebugFlags: c.debugFlags,
OmitPeers: cb == nil,
}
if hostinfo != nil && ipForwardingBroken(hostinfo.RoutableIPs) {
old := request.DebugFlags
@@ -601,6 +626,11 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
}
defer res.Body.Close()
if cb == nil {
io.Copy(ioutil.Discard, res.Body)
return nil
}
// If we go more than pollTimeout without hearing from the server,
// end the long poll. We should be receiving a keep alive ping
// every minute.
@@ -636,6 +666,8 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
var lastDERPMap *tailcfg.DERPMap
var lastUserProfile = map[tailcfg.UserID]tailcfg.UserProfile{}
var lastParsedPacketFilter []filter.Match
var collectServices bool
// If allowStream, then the server will use an HTTP long poll to
// return incremental results. There is always one response right
@@ -676,7 +708,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
case timeoutReset <- struct{}{}:
vlogf("netmap: sent timer reset")
case <-ctx.Done():
c.logf("netmap: not resetting timer; context done: %v", ctx.Err())
c.logf("[v1] netmap: not resetting timer; context done: %v", ctx.Err())
return ctx.Err()
}
if resp.KeepAlive {
@@ -713,23 +745,40 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
resp.Peers = filtered
}
if pf := resp.PacketFilter; pf != nil {
lastParsedPacketFilter = c.parsePacketFilter(pf)
}
if v, ok := resp.CollectServices.Get(); ok {
collectServices = v
}
// Get latest localPort. This might've changed if
// a lite map update occured meanwhile. This only affects
// the end-to-end test.
// TODO(bradfitz): remove the NetworkMap.LocalPort field entirely.
c.mu.Lock()
localPort = c.localPort
c.mu.Unlock()
nm := &NetworkMap{
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
PrivateKey: persist.PrivateNodeKey,
MachineKey: machinePubKey,
Expiry: resp.Node.KeyExpiry,
Name: resp.Node.Name,
Addresses: resp.Node.Addresses,
Peers: resp.Peers,
LocalPort: localPort,
User: resp.Node.User,
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile),
Domain: resp.Domain,
DNS: resp.DNSConfig,
Hostinfo: resp.Node.Hostinfo,
PacketFilter: c.parsePacketFilter(resp.PacketFilter),
DERPMap: lastDERPMap,
Debug: resp.Debug,
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
PrivateKey: persist.PrivateNodeKey,
MachineKey: machinePubKey,
Expiry: resp.Node.KeyExpiry,
Name: resp.Node.Name,
Addresses: resp.Node.Addresses,
Peers: resp.Peers,
LocalPort: localPort,
User: resp.Node.User,
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile),
Domain: resp.Domain,
DNS: resp.DNSConfig,
Hostinfo: resp.Node.Hostinfo,
PacketFilter: lastParsedPacketFilter,
CollectServices: collectServices,
DERPMap: lastDERPMap,
Debug: resp.Debug,
}
addUserProfile := func(userID tailcfg.UserID) {
if _, dup := nm.UserProfiles[userID]; dup {
@@ -742,6 +791,13 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
}
addUserProfile(nm.User)
for _, peer := range resp.Peers {
if !peer.Sharer.IsZero() {
if c.keepSharerAndUserSplit {
addUserProfile(peer.Sharer)
} else {
peer.User = peer.Sharer
}
}
addUserProfile(peer.User)
}
if resp.Node.MachineAuthorized {
@@ -750,7 +806,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
nm.MachineStatus = tailcfg.MachineUnauthorized
}
if len(resp.DNS) > 0 {
nm.DNS.Nameservers = wgIPToNetaddr(resp.DNS)
nm.DNS.Nameservers = resp.DNS
}
if len(resp.SearchPaths) > 0 {
nm.DNS.Domains = resp.SearchPaths
@@ -767,7 +823,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
now := c.timeNow()
if now.Sub(c.lastPrintMap) >= 5*time.Minute {
c.lastPrintMap = now
c.logf("new network map[%d]:\n%s", i, nm.Concise())
c.logf("[v1] new network map[%d]:\n%s", i, nm.Concise())
}
c.mu.Lock()
@@ -782,7 +838,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
return nil
}
func decode(res *http.Response, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) error {
func decode(res *http.Response, v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) error {
defer res.Body.Close()
msg, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
if err != nil {
@@ -837,7 +893,7 @@ func (c *Direct) decodeMsg(msg []byte, v interface{}) error {
}
func decodeMsg(msg []byte, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) error {
func decodeMsg(msg []byte, v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) error {
decrypted, err := decryptMsg(msg, serverKey, mkey)
if err != nil {
return err
@@ -851,7 +907,7 @@ func decodeMsg(msg []byte, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.Priv
return nil
}
func decryptMsg(msg []byte, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byte, error) {
func decryptMsg(msg []byte, serverKey *wgkey.Key, mkey *wgkey.Private) ([]byte, error) {
var nonce [24]byte
if len(msg) < len(nonce)+1 {
return nil, fmt.Errorf("response missing nonce, len=%d", len(msg))
@@ -867,7 +923,7 @@ func decryptMsg(msg []byte, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byt
return decrypted, nil
}
func encode(v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byte, error) {
func encode(v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
@@ -886,42 +942,31 @@ func encode(v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byte
return msg, nil
}
func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (wgcfg.Key, error) {
func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (wgkey.Key, error) {
req, err := http.NewRequest("GET", serverURL+"/key", nil)
if err != nil {
return wgcfg.Key{}, fmt.Errorf("create control key request: %v", err)
return wgkey.Key{}, fmt.Errorf("create control key request: %v", err)
}
req = req.WithContext(ctx)
res, err := httpc.Do(req)
if err != nil {
return wgcfg.Key{}, fmt.Errorf("fetch control key: %v", err)
return wgkey.Key{}, fmt.Errorf("fetch control key: %v", err)
}
defer res.Body.Close()
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<16))
if err != nil {
return wgcfg.Key{}, fmt.Errorf("fetch control key response: %v", err)
return wgkey.Key{}, fmt.Errorf("fetch control key response: %v", err)
}
if res.StatusCode != 200 {
return wgcfg.Key{}, fmt.Errorf("fetch control key: %d: %s", res.StatusCode, string(b))
return wgkey.Key{}, fmt.Errorf("fetch control key: %d: %s", res.StatusCode, string(b))
}
key, err := wgcfg.ParseHexKey(string(b))
key, err := wgkey.ParseHex(string(b))
if err != nil {
return wgcfg.Key{}, fmt.Errorf("fetch control key: %v", err)
return wgkey.Key{}, fmt.Errorf("fetch control key: %v", err)
}
return key, nil
}
func wgIPToNetaddr(ips []wgcfg.IP) (ret []netaddr.IP) {
for _, ip := range ips {
nip, ok := netaddr.FromStdIP(ip.IP())
if !ok {
panic(fmt.Sprintf("conversion of %s from wgcfg to netaddr IP failed", ip))
}
ret = append(ret, nip.Unmap())
}
return ret
}
// Debug contains temporary internal-only debug knobs.
// They're unexported to not draw attention to them.
var Debug = initDebug()
@@ -1080,11 +1125,12 @@ func TrimWGConfig() opt.Bool {
// and will definitely not work for the routes provided.
//
// It should not return false positives.
func ipForwardingBroken(routes []wgcfg.CIDR) bool {
func ipForwardingBroken(routes []netaddr.IPPrefix) bool {
if len(routes) == 0 {
// Nothing to route, so no need to warn.
return false
}
if runtime.GOOS != "linux" {
// We only do subnet routing on Linux for now.
// It might work on darwin/macOS when building from source, so
@@ -1092,17 +1138,57 @@ func ipForwardingBroken(routes []wgcfg.CIDR) bool {
// already in the admin panel.
return false
}
out, err := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward")
if err != nil {
// Try another way.
out, err = exec.Command("sysctl", "-n", "net.ipv4.ip_forward").Output()
v4Routes, v6Routes := false, false
for _, r := range routes {
if r.IP.Is4() {
v4Routes = true
} else {
v6Routes = true
}
}
if err != nil {
// Oh well, we tried. This is just for debugging.
// We don't want false positives.
// TODO: maybe we want a different warning for inability to check?
return false
if v4Routes {
out, err := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward")
if err != nil {
// Try another way.
out, err = exec.Command("sysctl", "-n", "net.ipv4.ip_forward").Output()
}
if err != nil {
// Oh well, we tried. This is just for debugging.
// We don't want false positives.
// TODO: maybe we want a different warning for inability to check?
return false
}
if strings.TrimSpace(string(out)) == "0" {
return true
}
}
return strings.TrimSpace(string(out)) == "0"
// TODO: also check IPv6 if 'routes' contains any IPv6 routes
if v6Routes {
// Note: you might be wondering why we check only the state of
// conf.all.forwarding, rather than per-interface forwarding
// configuration. According to kernel documentation, it seems
// that to actually forward packets, you need to enable
// forwarding globally, and the per-interface forwarding
// setting only alters other things such as how router
// advertisements are handled. The kernel itself warns that
// enabling forwarding per-interface and not globally will
// probably not work, so I feel okay calling those configs
// broken until we have proof otherwise.
out, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/forwarding")
if err != nil {
out, err = exec.Command("sysctl", "-n", "net.ipv6.conf.all.forwarding").Output()
}
if err != nil {
// Oh well, we tried. This is just for debugging.
// We don't want false positives.
// TODO: maybe we want a different warning for inability to check?
return false
}
if strings.TrimSpace(string(out)) == "0" {
return true
}
}
return false
}

View File

@@ -11,6 +11,7 @@ import (
"testing"
"tailscale.com/tailcfg"
"tailscale.com/types/wgkey"
)
func TestUndeltaPeers(t *testing.T) {
@@ -91,3 +92,67 @@ func formatNodes(nodes []*tailcfg.Node) string {
}
return sb.String()
}
func TestNewDirect(t *testing.T) {
hi := NewHostinfo()
ni := tailcfg.NetInfo{LinkType: "wired"}
hi.NetInfo = &ni
key, err := wgkey.NewPrivate()
if err != nil {
t.Error(err)
}
opts := Options{ServerURL: "https://example.com", MachinePrivateKey: key, Hostinfo: hi}
c, err := NewDirect(opts)
if err != nil {
t.Fatal(err)
}
if c.serverURL != opts.ServerURL {
t.Errorf("c.serverURL got %v want %v", c.serverURL, opts.ServerURL)
}
if !hi.Equal(c.hostinfo) {
t.Errorf("c.hostinfo got %v want %v", c.hostinfo, hi)
}
changed := c.SetNetInfo(&ni)
if changed {
t.Errorf("c.SetNetInfo(ni) want false got %v", changed)
}
ni = tailcfg.NetInfo{LinkType: "wifi"}
changed = c.SetNetInfo(&ni)
if !changed {
t.Errorf("c.SetNetInfo(ni) want true got %v", changed)
}
changed = c.SetHostinfo(hi)
if changed {
t.Errorf("c.SetHostinfo(hi) want false got %v", changed)
}
hi = NewHostinfo()
hi.Hostname = "different host name"
changed = c.SetHostinfo(hi)
if !changed {
t.Errorf("c.SetHostinfo(hi) want true got %v", changed)
}
endpoints := []string{"1", "2", "3"}
changed = c.newEndpoints(12, endpoints)
if !changed {
t.Errorf("c.newEndpoints(12) want true got %v", changed)
}
changed = c.newEndpoints(12, endpoints)
if changed {
t.Errorf("c.newEndpoints(12) want false got %v", changed)
}
changed = c.newEndpoints(13, endpoints)
if !changed {
t.Errorf("c.newEndpoints(13) want true got %v", changed)
}
endpoints = []string{"4", "5", "6"}
changed = c.newEndpoints(13, endpoints)
if !changed {
t.Errorf("c.newEndpoints(13) want true got %v", changed)
}
}

View File

@@ -73,7 +73,7 @@ func osVersionLinux() string {
return fmt.Sprintf("%s%s", bytes.TrimSpace(cr), attr)
}
fallthrough
case "fedora", "rhel", "alpine":
case "fedora", "rhel", "alpine", "nixos":
// Their PRETTY_NAME is fine as-is for all versions I tested.
fallthrough
default:

View File

@@ -14,8 +14,11 @@ import (
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/wgkey"
"tailscale.com/util/dnsname"
"tailscale.com/wgengine/filter"
)
@@ -23,11 +26,11 @@ type NetworkMap struct {
// Core networking
NodeKey tailcfg.NodeKey
PrivateKey wgcfg.PrivateKey
PrivateKey wgkey.Private
Expiry time.Time
// Name is the DNS name assigned to this node.
Name string
Addresses []wgcfg.CIDR
Addresses []netaddr.IPPrefix
LocalPort uint16 // used for debugging
MachineStatus tailcfg.MachineStatus
MachineKey tailcfg.MachineKey
@@ -36,6 +39,12 @@ type NetworkMap struct {
Hostinfo tailcfg.Hostinfo
PacketFilter []filter.Match
// CollectServices reports whether this node's Tailnet has
// requested that info about services be included in HostInfo.
// If set, Hostinfo.ShieldsUp blocks services collection; that
// takes precedence over this field.
CollectServices bool
// DERPMap is the last DERP server map received. It's reused
// between updates and should not be modified.
DERPMap *tailcfg.DERPMap
@@ -54,7 +63,30 @@ type NetworkMap struct {
// TODO(crawshaw): Capabilities []tailcfg.Capability
}
func (nm NetworkMap) String() string {
// MagicDNSSuffix returns the domain's MagicDNS suffix, or empty if none.
// If non-empty, it will neither start nor end with a period.
func (nm *NetworkMap) MagicDNSSuffix() string {
searchPathUsedAsDNSSuffix := func(suffix string) bool {
if dnsname.HasSuffix(nm.Name, suffix) {
return true
}
for _, p := range nm.Peers {
if dnsname.HasSuffix(p.Name, suffix) {
return true
}
}
return false
}
for _, d := range nm.DNS.Domains {
if searchPathUsedAsDNSSuffix(d) {
return strings.Trim(d, ".")
}
}
return ""
}
func (nm *NetworkMap) String() string {
return nm.Concise()
}
@@ -240,7 +272,7 @@ const EndpointDiscoSuffix = ".disco.tailscale:12345"
func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Config, error) {
cfg := &wgcfg.Config{
Name: "tailscale",
PrivateKey: nm.PrivateKey,
PrivateKey: wgcfg.PrivateKey(nm.PrivateKey),
Addresses: nm.Addresses,
ListenPort: nm.LocalPort,
Peers: make([]wgcfg.Peer, 0, len(nm.Peers)),
@@ -266,7 +298,7 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
if err := appendEndpoint(cpeer, fmt.Sprintf("%x%s", peer.DiscoKey[:], EndpointDiscoSuffix)); err != nil {
return nil, err
}
cpeer.Endpoints = []wgcfg.Endpoint{{Host: fmt.Sprintf("%x.disco.tailscale", peer.DiscoKey[:]), Port: 12345}}
cpeer.Endpoints = fmt.Sprintf("%x.disco.tailscale:12345", peer.DiscoKey[:])
} else {
if err := appendEndpoint(cpeer, peer.DERP); err != nil {
return nil, err
@@ -278,14 +310,14 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
}
}
for _, allowedIP := range peer.AllowedIPs {
if allowedIP.Mask == 0 {
if allowedIP.Bits == 0 {
if (flags & AllowDefaultRoute) == 0 {
logf("wgcfg: %v skipping default route", peer.Key.ShortString())
logf("[v1] wgcfg: %v skipping default route", peer.Key.ShortString())
continue
}
} else if cidrIsSubnet(peer, allowedIP) {
if (flags & AllowSubnetRoutes) == 0 {
logf("wgcfg: %v skipping subnet route", peer.Key.ShortString())
logf("[v1] wgcfg: %v skipping subnet route", peer.Key.ShortString())
continue
}
}
@@ -298,17 +330,11 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
// cidrIsSubnet reports whether cidr is a non-default-route subnet
// exported by node that is not one of its own self addresses.
func cidrIsSubnet(node *tailcfg.Node, cidr wgcfg.CIDR) bool {
if cidr.Mask == 0 {
func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool {
if cidr.Bits == 0 {
return false
}
if cidr.Mask < 32 {
// Fast path for IPv4, to avoid loop below.
//
// TODO: if cidr.IP is an IPv6 address, we could do "< 128"
// to avoid the range over node.Addresses. Or we could
// just remove this fast path and unconditionally do the range
// loop.
if !cidr.IsSingleIP() {
return true
}
for _, selfCIDR := range node.Addresses {
@@ -323,15 +349,18 @@ func appendEndpoint(peer *wgcfg.Peer, epStr string) error {
if epStr == "" {
return nil
}
host, port, err := net.SplitHostPort(epStr)
_, port, err := net.SplitHostPort(epStr)
if err != nil {
return fmt.Errorf("malformed endpoint %q for peer %v", epStr, peer.PublicKey.ShortString())
}
port16, err := strconv.ParseUint(port, 10, 16)
_, err = strconv.ParseUint(port, 10, 16)
if err != nil {
return fmt.Errorf("invalid port in endpoint %q for peer %v", epStr, peer.PublicKey.ShortString())
}
peer.Endpoints = append(peer.Endpoints, wgcfg.Endpoint{Host: host, Port: uint16(port16)})
if peer.Endpoints != "" {
peer.Endpoints += ","
}
peer.Endpoints += epStr
return nil
}
@@ -351,7 +380,7 @@ func eqStringsIgnoreNil(a, b []string) bool {
// eqCIDRsIgnoreNil reports whether a and b have the same length and
// contents, but ignore whether a or b are nil.
func eqCIDRsIgnoreNil(a, b []wgcfg.CIDR) bool {
func eqCIDRsIgnoreNil(a, b []netaddr.IPPrefix) bool {
if len(a) != len(b) {
return false
}

View File

@@ -6,9 +6,10 @@ package controlclient
import (
"encoding/hex"
"encoding/json"
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/tailcfg"
)
@@ -250,7 +251,7 @@ func TestConciseDiffFrom(t *testing.T) {
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:41641", "1.1.1.1:41641"},
DiscoKey: testDiscoKey("f00f00f00f"),
AllowedIPs: []wgcfg.CIDR{{IP: wgcfg.IPv4(100, 102, 103, 104), Mask: 32}},
AllowedIPs: []netaddr.IPPrefix{{IP: netaddr.IPv4(100, 102, 103, 104), Bits: 32}},
},
},
},
@@ -263,7 +264,7 @@ func TestConciseDiffFrom(t *testing.T) {
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:41641", "1.1.1.1:41641"},
DiscoKey: testDiscoKey("ba4ba4ba4b"),
AllowedIPs: []wgcfg.CIDR{{IP: wgcfg.IPv4(100, 102, 103, 104), Mask: 32}},
AllowedIPs: []netaddr.IPPrefix{{IP: netaddr.IPv4(100, 102, 103, 104), Bits: 32}},
},
},
},
@@ -282,3 +283,15 @@ func TestConciseDiffFrom(t *testing.T) {
})
}
}
func TestNewHostinfo(t *testing.T) {
hi := NewHostinfo()
if hi == nil {
t.Fatal("no Hostinfo")
}
j, err := json.MarshalIndent(hi, " ", "")
if err != nil {
t.Fatal(err)
}
t.Logf("Got: %s", j)
}

View File

@@ -8,7 +8,7 @@ import (
"reflect"
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/types/wgkey"
)
func TestPersistEqual(t *testing.T) {
@@ -18,8 +18,8 @@ func TestPersistEqual(t *testing.T) {
have, persistHandles)
}
newPrivate := func() wgcfg.PrivateKey {
k, err := wgcfg.NewPrivateKey()
newPrivate := func() wgkey.Private {
k, err := wgkey.NewPrivate()
if err != nil {
panic(err)
}

View File

@@ -358,7 +358,7 @@ func (c *Client) dialURL(ctx context.Context) (net.Conn, error) {
dialer := netns.NewDialer()
if c.DNSCache != nil {
ip, err := c.DNSCache.LookupIP(ctx, host)
ip, _, err := c.DNSCache.LookupIP(ctx, host)
if err == nil {
hostOrIP = ip.String()
}

35
go.mod
View File

@@ -1,6 +1,6 @@
module tailscale.com
go 1.14
go 1.15
require (
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5
@@ -12,30 +12,33 @@ require (
github.com/go-multierror/multierror v1.0.2
github.com/go-ole/go-ole v1.2.4
github.com/godbus/dbus/v5 v5.0.3
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/google/go-cmp v0.4.0
github.com/golang/protobuf v1.4.2 // indirect
github.com/google/go-cmp v0.5.4
github.com/goreleaser/nfpm v1.1.10
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391
github.com/klauspost/compress v1.10.10
github.com/kr/pty v1.1.1
github.com/mdlayher/netlink v1.1.0
github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1
github.com/mdlayher/netlink v1.2.0
github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a
github.com/miekg/dns v1.1.30
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
github.com/peterbourgon/ff/v2 v2.0.0
github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be
github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
github.com/tailscale/wireguard-go v0.0.0-20210116013233-4cd297ed5a7d
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/mem v0.0.0-20200706164138-185c595c3ecc
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
golang.org/x/net v0.0.0-20201216054612-986b41b23924
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8
honnef.co/go/tools v0.0.1-2020.1.4
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98
gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6
honnef.co/go/tools v0.1.0
inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d
rsc.io/goversion v1.2.0
)

463
go.sum
View File

@@ -1,12 +1,34 @@
cloud.google.com/go v0.0.0-20170206221025-ce650573d812/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.52.1-0.20200122224058-0482b626c726/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes=
github.com/aclements/go-moremath v0.0.0-20161014184102-0ff62e0875ff/go.mod h1:idZL3yvz4kzx1dsBOAC+oYv6L92P1oFEhUXUB1A/lwQ=
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
@@ -22,217 +44,530 @@ github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
github.com/cenkalti/backoff v1.1.1-0.20190506075156-2146c9339422/go.mod h1:b6Nc7NRH5C4aCISLry0tLnTjcuTEvoiqcWDdsU0sOGM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
github.com/containerd/containerd v1.3.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a/go.mod h1:W0qIOTD7mp2He++YVq+kgfXezRYqzP1uDuMVH1bITDY=
github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
github.com/containerd/ttrpc v0.0.0-20200121165050-0be804eadb15/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/typeurl v0.0.0-20200205145503-b45ef1f1f737/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20191028175130-9e7d5ac5ea55/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/dpjacques/clockwork v0.1.1-0.20200827220843-c1f524b839be/go.mod h1:D8mP2A8vVT2GkXqPorSBmhnshhkFBYgzhA90KmJt25Y=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-multierror/multierror v1.0.2 h1:AwsKbEXkmf49ajdFJgcFXqSG0aLo0HEyAE9zk9JguJo=
github.com/go-multierror/multierror v1.0.2/go.mod h1:U7SZR/D9jHgt2nkSj8XcbCWdmVM2igraCHQ3HC1HiKY=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/gofrs/flock v0.6.1-0.20180915234121-886344bea079/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc=
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg=
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks=
github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A=
github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3-0.20201020212313-ab46b8bd0abd/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v28 v28.1.2-0.20191108005307-e555eab49ce8/go.mod h1:g82e6OHbJ0WYrYeOrid1MMfHAtqjxBz+N74tfAt9KrQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0 h1:BW6OvS3kpT5UEPbCZ+KyX/OB4Ks9/MNMhWjqPPkZxsE=
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/google/subcommands v1.0.2-0.20190508160503-636abe8753b8/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/goreleaser/nfpm v1.1.10 h1:0nwzKUJTcygNxTzVKq2Dh9wpVP1W2biUH6SNKmoxR3w=
github.com/goreleaser/nfpm v1.1.10/go.mod h1:oOcoGRVwvKIODz57NUfiRwFWGfn00NXdgnn6MrYtO5k=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391 h1:Dqu/4JhMV1vpXHDjzQCuDCEsjNi0xfuSmQlMOyqayKA=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1 h1:zc0R6cOw98cMengLA0fvU55mqbnN7sd/tBMLzSejp+M=
github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/walk v0.0.0-20201110160827-18ea5e372cdb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mattn/go-sqlite3 v0.0.0-20161215041557-2d44decb4941/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.2.0 h1:zPolhRjfuabdf8ofZsl56eoU+92cvSlAn13lw4veCZ0=
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a h1:wMv2mvcHRH4jqIxaVL5t6gSq1hjPiaWH7TOcA0Z+uNo=
github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4=
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mohae/deepcopy v0.0.0-20170308212314-bb9b5e7adda9/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2-0.20181111125026-1722abf79c2f/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 h1:YtFkrqsMEj7YqpIhRteVxJxCeC3jJBieuLr0d4C4rSA=
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/peterbourgon/ff/v2 v2.0.0 h1:lx0oYI5qr/FU1xnpNhQ+EZM04gKgn46jyYvGEEqBBbY=
github.com/peterbourgon/ff/v2 v2.0.0/go.mod h1:xjwr+t+SjWm4L46fcj/D+Ap+6ME7+HqFzaP22pP5Ggk=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b h1:+gCnWOZV8Z/8jehJ2CdqB47Z3S+SREmQcuXkRFLNsiI=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be h1:ZKe3kVGbu/goUVxXcaCPbQ4b0STQ5NsCpG90CG6mw/c=
github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI=
github.com/tailscale/wireguard-go v0.0.0-20200921221757-11a958a67bdd h1:yEWpro9EdxGgkt24NInVnONIJxRLURH5c37Ki5+06EE=
github.com/tailscale/wireguard-go v0.0.0-20200921221757-11a958a67bdd/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tailscale/wireguard-go v0.0.0-20201008164108-2c83f43a9859 h1:Z7bXXCYRg/8sjSyKTk0V8Yso/gQjNvPb10DBemKuz+A=
github.com/tailscale/wireguard-go v0.0.0-20201008164108-2c83f43a9859/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f h1:KMx58dbn2YCutzOvjNHgmvbwQH7nGE8H+J42Nenjl/M=
github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/tailscale/wireguard-go v0.0.0-20210109012254-dc30a1b9415e h1:ZXbXfVJOhSq4/Gt7TnqwXBPCctzYXkWXo3oQS7LZ40I=
github.com/tailscale/wireguard-go v0.0.0-20210109012254-dc30a1b9415e/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8=
github.com/tailscale/wireguard-go v0.0.0-20210113223737-a6213b5eaf98 h1:khwYPK1eT+4pmEFyCjpf6Br/0JWjdVT3uQ+ILFJPTRo=
github.com/tailscale/wireguard-go v0.0.0-20210113223737-a6213b5eaf98/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8=
github.com/tailscale/wireguard-go v0.0.0-20210114205708-a1377e83f551 h1:hjBVxvVa145kVflAFkVcTr/zwUzBO4SqfSS6xhbcMv8=
github.com/tailscale/wireguard-go v0.0.0-20210114205708-a1377e83f551/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8=
github.com/tailscale/wireguard-go v0.0.0-20210116013233-4cd297ed5a7d h1:8GcGtZ4Ui+lzHm6gOq7s2Oe4ksxkbUYtS/JuoJ2Nce8=
github.com/tailscale/wireguard-go v0.0.0-20210116013233-4cd297ed5a7d/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go4.org/mem v0.0.0-20200706164138-185c595c3ecc h1:paujszgN6SpsO/UsXC7xax3gQAKz/XQKCYZLQdU34Tw=
go4.org/mem v0.0.0-20200706164138-185c595c3ecc/go.mod h1:NEYvpHWemiG/E5UWfaN5QAIGZeT1sa0Z2UNk6oeMb/k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7 h1:yeDrXaQ3VRXbTN7lHj70DxW4LdPow83MVwPPRjpP70U=
go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/intern v0.0.0-20201223061701-969c7e87e7cb h1:yuqO0E4bHRsTPUocDpRKXfLE40lwWplVxENQ2WOV7Gc=
go4.org/intern v0.0.0-20201223061701-969c7e87e7cb/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/intern v0.0.0-20210101010959-7cab76ca296a h1:28p852HIWWaOS019DYK/A3yTmpm1HJaUce63pvll4C8=
go4.org/intern v0.0.0-20210101010959-7cab76ca296a/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 h1:vSug/WNOi2+4jrKdivxayTN/zd8EA1UrStjpWvvo1jk=
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e h1:ExUmGi0ZsQmiVo9giDQqXkr7vreeXPMkOGIusfsfbzI=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20170207211851-4464e7848382/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20200918155509-d949658356f9 h1:yVBHF5pcQLKR9B+y+dOJ6y68nqJBDWaZ9DhB1Ohg0qE=
golang.org/x/perf v0.0.0-20200918155509-d949658356f9/go.mod h1:FrqOtQDO3iMDVUtw5nNTDFpR1HUCGh00M3kj2wiSzLQ=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo=
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 h1:+CBz4km/0KPU3RGTwARGh/noP3bEwtHcq+0YcBQM2JQ=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q=
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d h1:vWQvJ/Z0Lu+9/8oQ/pAYXNzbc7CMnBl+tULGVHOy3oE=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201021000207-d49c4edd7d96/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.20200321-0.20200715051853-507f148e1c42 h1:SrR1hmxGKKarHEEDvaHxatwnqE3uT+7jvMcin6SHOkw=
golang.zx2c4.com/wireguard v0.0.20200321-0.20200715051853-507f148e1c42/go.mod h1:GJvYs5O24/ASlwPiRklVnjMx2xQzrOic0DuU6GvYJL4=
golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9 h1:qowcZ56hhpeoESmWzI4Exhx4Y78TpCyXUJur4/c0CoE=
golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9/go.mod h1:LMeNfjlcPZTrBC1juwgbQyA4Zy2XVcsrdO/fIJxwyuA=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201004085714-dd60d0447f81 h1:cT2oWlz8v9g7bjFZclT362akxJJfGv9d7ccKu6GQUbA=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201004085714-dd60d0447f81/go.mod h1:GaK5zcgr5XE98WaRzIDilumDBp5/yP8j2kG/LCDnvAM=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8 h1:nlXPqGA98n+qcq1pwZ28KjM5EsFQvamKS00A+VUeVjs=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8/go.mod h1:psva4yDnAHLuh7lUzOK7J7bLYxNFfo0iKWz+mi9gzkA=
google.golang.org/api v0.0.0-20170206182103-3d017632ea10/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/grpc v0.0.0-20170208002647-2a6bf6142e96/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b h1:jEdfCm+8YTWSYgU4L7Nq0jjU+q9RxIhi0cXLTY+Ih3A=
google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98 h1:bWyWDZP0l6VnQ1TDKf6yNwuiEDV6Q3q1Mv34m+lzT1I=
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6 h1:H5EvGkFG+pgAAbZMV8Me3Gy+HUYdaDcGXKWWixZ0EE8=
gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6/go.mod h1:5DEMKRjYDiM24fvDUWPjBpABm9ROMcv/kEcox3fHtm0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c=
honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM=
inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf h1:0eHZ8v6j5wIiOVyoYPd70ueZ/RPEQtRlzi60uneDbRU=
inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o=
inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d h1:6f0242aW/6x2enQBOSKgDS8KQNw6Tp7IVR8eG3x0Jc8=
inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d/go.mod h1:jPZo7Jy4nke2cCgISa4fKJKa5T7+EO8k5fWwWghzneg=
k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs=
k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
k8s.io/client-go v0.16.13/go.mod h1:UKvVT4cajC2iN7DCjLgT0KVY/cbY6DGdUCyRiIfws5M=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20200410163147-594e756bea31/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -37,16 +37,11 @@ func getVal() []interface{} {
return []interface{}{
&wgcfg.Config{
Name: "foo",
Addresses: []wgcfg.CIDR{{Mask: 5, IP: wgcfg.IP{Addr: [16]byte{3: 3}}}},
Addresses: []netaddr.IPPrefix{{Bits: 5, IP: netaddr.IPFrom16([16]byte{3: 3})}},
ListenPort: 5,
Peers: []wgcfg.Peer{
{
Endpoints: []wgcfg.Endpoint{
{
Host: "foo",
Port: 5,
},
},
Endpoints: "foo:5",
},
},
},

View File

@@ -8,8 +8,8 @@ import (
"sync"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/types/logger"
)
@@ -118,7 +118,7 @@ func (h *Handle) EngineStatus() EngineStatus {
return h.engineStatusCache
}
func (h *Handle) LocalAddrs() []wgcfg.CIDR {
func (h *Handle) LocalAddrs() []netaddr.IPPrefix {
h.mu.Lock()
defer h.mu.Unlock()
@@ -126,7 +126,7 @@ func (h *Handle) LocalAddrs() []wgcfg.CIDR {
if nm != nil {
return nm.Addresses
}
return []wgcfg.CIDR{}
return []netaddr.IPPrefix{}
}
func (h *Handle) NetMap() *controlclient.NetworkMap {

View File

@@ -0,0 +1,49 @@
// 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.
// +build linux
package ipnserver
import (
"net"
"golang.org/x/sys/unix"
"tailscale.com/types/logger"
)
func isReadonlyConn(c net.Conn, logf logger.Logf) (ro bool) {
ro = true // conservative default for naked returns below
uc, ok := c.(*net.UnixConn)
if !ok {
logf("unexpected connection type %T", c)
return
}
raw, err := uc.SyscallConn()
if err != nil {
logf("SyscallConn: %v", err)
return
}
var cred *unix.Ucred
cerr := raw.Control(func(fd uintptr) {
cred, err = unix.GetsockoptUcred(int(fd),
unix.SOL_SOCKET,
unix.SO_PEERCRED)
})
if cerr != nil {
logf("raw.Control: %v", err)
return
}
if err != nil {
logf("raw.Control: %v", err)
return
}
if cred.Uid == 0 {
// root is not read-only.
return false
}
logf("non-root connection from %v (read-only)", cred.Uid)
return true
}

View File

@@ -0,0 +1,27 @@
// 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.
// +build !linux
package ipnserver
import (
"net"
"tailscale.com/types/logger"
)
func isReadonlyConn(c net.Conn, logf logger.Logf) bool {
// Windows doesn't need/use this mechanism, at least yet. It
// has a different last-user-wins auth model.
// And on Darwin, we're not using it yet, as the Darwin
// tailscaled port isn't yet done, and unix.Ucred and
// unix.GetsockoptUcred aren't in x/sys/unix.
// TODO(bradfitz): OpenBSD and FreeBSD should implement this too.
// But their x/sys/unix package is different than Linux, so
// I didn't include it for now.
return false
}

View File

@@ -34,6 +34,7 @@ import (
"tailscale.com/smallzstd"
"tailscale.com/types/logger"
"tailscale.com/util/pidowner"
"tailscale.com/util/systemd"
"tailscale.com/version"
"tailscale.com/wgengine"
)
@@ -265,18 +266,24 @@ func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
}
defer s.removeAndCloseConn(c)
logf("incoming control connection")
logf("[v1] incoming control connection")
if isReadonlyConn(c, logf) {
ctx = ipn.ReadonlyContextOf(ctx)
}
for ctx.Err() == nil {
msg, err := ipn.ReadMsg(br)
if err != nil {
if ctx.Err() == nil {
if errors.Is(err, io.EOF) {
logf("[v1] ReadMsg: %v", err)
} else if ctx.Err() == nil {
logf("ReadMsg: %v", err)
}
return
}
s.bsMu.Lock()
if err := s.bs.GotCommandMsg(msg); err != nil {
if err := s.bs.GotCommandMsg(ctx, msg); err != nil {
logf("GotCommandMsg: %v", err)
}
gotQuit := s.bs.GotQuit
@@ -352,7 +359,7 @@ func (s *server) addConn(c net.Conn, isHTTP bool) (ci connIdentity, err error) {
if doReset {
s.logf("identity changed; resetting server")
s.bsMu.Lock()
s.bs.Reset()
s.bs.Reset(context.TODO())
s.bsMu.Unlock()
}
}()
@@ -404,7 +411,7 @@ func (s *server) removeAndCloseConn(c net.Conn) {
} else {
s.logf("client disconnected; stopping server")
s.bsMu.Lock()
s.bs.Reset()
s.bs.Reset(context.TODO())
s.bsMu.Unlock()
}
}
@@ -578,7 +585,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
server.bs = ipn.NewBackendServer(logf, b, server.writeToClients)
if opts.AutostartStateKey != "" {
server.bs.GotCommand(&ipn.Command{
server.bs.GotCommand(context.TODO(), &ipn.Command{
Version: version.Long,
Start: &ipn.StartArgs{
Opts: ipn.Options{
@@ -589,6 +596,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
})
}
systemd.Ready()
for i := 1; ctx.Err() == nil; i++ {
var c net.Conn
var err error
@@ -724,6 +732,10 @@ func BabysitProc(ctx context.Context, args []string, logf logger.Logf) {
// pipe. We'll make a new one when we restart the subproc.
wStdin.Close()
if os.Getenv("TS_DEBUG_RESTART_CRASHED") == "0" {
log.Fatalf("Process ended.")
}
if time.Since(startTime) < 60*time.Second {
bo.BackOff(ctx, fmt.Errorf("subproc early exit: %v", err))
} else {

View File

@@ -56,7 +56,7 @@ func TestRunMultipleAccepts(t *testing.T) {
}
}
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0, nil)
if err != nil {
t.Fatal(err)
}

View File

@@ -25,9 +25,10 @@ import (
// Status represents the entire state of the IPN network.
type Status struct {
BackendState string
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Self *PeerStatus
BackendState string
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Self *PeerStatus
MagicDNSSuffix string // e.g. "userfoo.tailscale.net" (no surrounding dots)
Peer map[key.Public]*PeerStatus
User map[tailcfg.UserID]tailcfg.UserProfile
@@ -64,6 +65,12 @@ type PeerStatus struct {
LastHandshake time.Time // with local wireguard
KeepAlive bool
// ShareeNode indicates this node exists in the netmap because
// it's owned by a shared-to user and that node might connect
// to us. These nodes should be hidden by "tailscale status"
// etc by default.
ShareeNode bool `json:",omitempty"`
// InNetworkMap means that this peer was seen in our latest network map.
// In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.
InNetworkMap bool
@@ -97,6 +104,12 @@ func (sb *StatusBuilder) SetBackendState(v string) {
sb.st.BackendState = v
}
func (sb *StatusBuilder) SetMagicDNSSuffix(v string) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.MagicDNSSuffix = v
}
func (sb *StatusBuilder) Status() *Status {
sb.mu.Lock()
defer sb.mu.Unlock()
@@ -218,6 +231,9 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
if st.KeepAlive {
e.KeepAlive = true
}
if st.ShareeNode {
e.ShareeNode = true
}
}
type StatusUpdater interface {

View File

@@ -29,6 +29,8 @@ import (
"tailscale.com/types/empty"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/wgkey"
"tailscale.com/util/systemd"
"tailscale.com/version"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
@@ -82,7 +84,7 @@ type LocalBackend struct {
userID string // current controlling user ID (for Windows, primarily)
prefs *Prefs
inServerMode bool
machinePrivKey wgcfg.PrivateKey
machinePrivKey wgkey.Private
state State
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo
@@ -199,6 +201,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
// TODO: hostinfo, and its networkinfo
// TODO: EngineStatus copy (and deprecate it?)
if b.netMap != nil {
sb.SetMagicDNSSuffix(b.netMap.MagicDNSSuffix())
for id, up := range b.netMap.UserProfiles {
sb.AddUser(id, up)
}
@@ -208,8 +211,14 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
lastSeen = *p.LastSeen
}
var tailAddr string
if len(p.Addresses) > 0 {
tailAddr = strings.TrimSuffix(p.Addresses[0].String(), "/32")
for _, addr := range p.Addresses {
// The peer struct currently only allows a single
// Tailscale IP address. For compatibility with the
// old display, make sure it's the IPv4 address.
if addr.IP.Is4() && addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) {
tailAddr = addr.IP.String()
break
}
}
sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
InNetworkMap: true,
@@ -221,6 +230,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
KeepAlive: p.KeepAlive,
Created: p.Created,
LastSeen: lastSeen,
ShareeNode: p.Hostinfo.ShareeNode,
})
}
}
@@ -243,7 +253,11 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// The following do not depend on any data for which we need to lock b.
if st.Err != "" {
// TODO(crawshaw): display in the UI.
b.logf("Received error: %v", st.Err)
if st.Err == "EOF" {
b.logf("[v1] Received error: EOF")
} else {
b.logf("Received error: %v", st.Err)
}
return
}
if st.LoginFinished != nil {
@@ -307,7 +321,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
if netMap != nil {
diff := st.NetMap.ConciseDiffFrom(netMap)
if strings.TrimSpace(diff) == "" {
b.logf("netmap diff: (none)")
b.logf("[v1] netmap diff: (none)")
} else {
b.logf("netmap diff:\n%v", diff)
}
@@ -338,7 +352,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// This updates the endpoints both in the backend and in the control client.
func (b *LocalBackend) setWgengineStatus(s *wgengine.Status, err error) {
if err != nil {
b.logf("wgengine status error: %#v", err)
b.logf("wgengine status error: %v", err)
return
}
if s == nil {
@@ -522,9 +536,9 @@ func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Pre
// quite hard to debug, so save yourself the trouble.
var (
haveNetmap = netMap != nil
addrs []wgcfg.CIDR
addrs []netaddr.IPPrefix
packetFilter []filter.Match
advRoutes []wgcfg.CIDR
advRoutes []netaddr.IPPrefix
shieldsUp = prefs == nil || prefs.ShieldsUp // Be conservative when not ready
)
if haveNetmap {
@@ -546,12 +560,11 @@ func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Pre
return
}
localNets := wgCIDRsToNetaddr(netMap.Addresses, advRoutes)
localNets := unmapIPPrefixes(netMap.Addresses, advRoutes)
if shieldsUp {
b.logf("netmap packet filter: (shields up)")
var prevFilter *filter.Filter // don't reuse old filter state
b.e.SetFilter(filter.New(nil, localNets, prevFilter, b.logf))
b.e.SetFilter(filter.NewShieldsUpFilter(b.logf))
} else {
b.logf("netmap packet filter: %v", packetFilter)
b.e.SetFilter(filter.New(packetFilter, localNets, b.e.GetFilter(), b.logf))
@@ -560,7 +573,7 @@ func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Pre
// dnsCIDRsEqual determines whether two CIDR lists are equal
// for DNS map construction purposes (that is, only the first entry counts).
func dnsCIDRsEqual(newAddr, oldAddr []wgcfg.CIDR) bool {
func dnsCIDRsEqual(newAddr, oldAddr []netaddr.IPPrefix) bool {
if len(newAddr) != len(oldAddr) {
return false
}
@@ -614,11 +627,11 @@ func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
}
nameToIP := make(map[string]netaddr.IP)
set := func(name string, addrs []wgcfg.CIDR) {
set := func(name string, addrs []netaddr.IPPrefix) {
if len(addrs) == 0 || name == "" {
return
}
nameToIP[name] = netaddr.IPFrom16(addrs[0].IP.Addr)
nameToIP[name] = addrs[0].IP
}
for _, peer := range netMap.Peers {
@@ -626,7 +639,7 @@ func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
}
set(netMap.Name, netMap.Addresses)
dnsMap := tsdns.NewMap(nameToIP, domainsForProxying(netMap))
dnsMap := tsdns.NewMap(nameToIP, magicDNSRootDomains(netMap))
// map diff will be logged in tsdns.Resolver.SetMap.
b.e.SetDNSMap(dnsMap)
}
@@ -725,7 +738,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
return nil
}
var legacyMachineKey wgcfg.PrivateKey
var legacyMachineKey wgkey.Private
if b.prefs.Persist != nil {
legacyMachineKey = b.prefs.Persist.LegacyFrontendPrivateMachineKey
}
@@ -760,7 +773,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
} else {
b.logf("generating new machine key")
var err error
b.machinePrivKey, err = wgcfg.NewPrivateKey()
b.machinePrivKey, err = wgkey.NewPrivate()
if err != nil {
return fmt.Errorf("initializing new machine key: %w", err)
}
@@ -995,22 +1008,24 @@ func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret EngineStatus
// [GRINDER STATS LINES] - please don't remove (used for log parsing)
if peerStats.Len() > 0 {
b.keyLogf("peer keys: %s", strings.TrimSpace(peerKeys.String()))
b.statsLogf("v%v peers: %v", version.Long, strings.TrimSpace(peerStats.String()))
b.keyLogf("[v1] peer keys: %s", strings.TrimSpace(peerKeys.String()))
b.statsLogf("[v1] v%v peers: %v", version.Long, strings.TrimSpace(peerStats.String()))
}
return ret
}
// shieldsAreUp returns whether user preferences currently request
// "shields up" mode, which disallows all inbound connections.
func (b *LocalBackend) shieldsAreUp() bool {
// shouldUploadServices reports whether this node should include services
// in Hostinfo. When the user preferences currently request "shields up"
// mode, all inbound connections are refused, so services are not reported.
// Otherwise, shouldUploadServices respects NetMap.CollectServices.
func (b *LocalBackend) shouldUploadServices() bool {
b.mu.Lock()
defer b.mu.Unlock()
if b.prefs == nil {
return true // default to safest setting
if b.prefs == nil || b.netMap == nil {
return false // default to safest setting
}
return b.prefs.ShieldsUp
return !b.prefs.ShieldsUp && b.netMap.CollectServices
}
func (b *LocalBackend) SetCurrentUserID(uid string) {
@@ -1052,7 +1067,7 @@ func (b *LocalBackend) SetPrefs(newp *Prefs) {
oldHi := b.hostinfo
newHi := oldHi.Clone()
newHi.RoutableIPs = append([]wgcfg.CIDR(nil), b.prefs.AdvertiseRoutes...)
newHi.RoutableIPs = append([]netaddr.IPPrefix(nil), b.prefs.AdvertiseRoutes...)
applyPrefsToHostinfo(newHi, newp)
b.hostinfo = newHi
hostInfoChanged := !oldHi.Equal(newHi)
@@ -1110,9 +1125,7 @@ func (b *LocalBackend) SetPrefs(newp *Prefs) {
// painstakingly constructing it in twelvety other places.
func (b *LocalBackend) doSetHostinfoFilterServices(hi *tailcfg.Hostinfo) {
hi2 := *hi
if b.shieldsAreUp() {
// No local services are available, since ShieldsUp will block
// them all.
if !b.shouldUploadServices() {
hi2.Services = []tailcfg.Service{}
}
@@ -1196,20 +1209,14 @@ func (b *LocalBackend) authReconfig() {
// If CorpDNS is false, rcfg.DNS remains the zero value.
if uc.CorpDNS {
domains := nm.DNS.Domains
proxied := nm.DNS.Proxied
if proxied {
if len(nm.DNS.Nameservers) == 0 {
b.logf("[unexpected] dns proxied but no nameservers")
proxied = false
} else {
// Domains for proxying should come first to avoid leaking queries.
domains = append(domainsForProxying(nm), domains...)
}
if proxied && len(nm.DNS.Nameservers) == 0 {
b.logf("[unexpected] dns proxied but no nameservers")
proxied = false
}
rcfg.DNS = dns.Config{
Nameservers: nm.DNS.Nameservers,
Domains: domains,
Domains: nm.DNS.Domains,
PerDomain: nm.DNS.PerDomain,
Proxied: proxied,
}
@@ -1219,56 +1226,29 @@ func (b *LocalBackend) authReconfig() {
if err == wgengine.ErrNoChanges {
return
}
b.logf("authReconfig: ra=%v dns=%v 0x%02x: %v", uc.RouteAll, uc.CorpDNS, flags, err)
b.logf("[v1] authReconfig: ra=%v dns=%v 0x%02x: %v", uc.RouteAll, uc.CorpDNS, flags, err)
}
// domainsForProxying produces a list of search domains for proxied DNS.
func domainsForProxying(nm *controlclient.NetworkMap) []string {
var domains []string
if idx := strings.IndexByte(nm.Name, '.'); idx != -1 {
domains = append(domains, nm.Name[idx+1:])
// magicDNSRootDomains returns the subset of nm.DNS.Domains that are the search domains for MagicDNS.
// Each entry has a trailing period.
func magicDNSRootDomains(nm *controlclient.NetworkMap) []string {
if v := nm.MagicDNSSuffix(); v != "" {
return []string{strings.Trim(v, ".") + "."}
}
for _, peer := range nm.Peers {
idx := strings.IndexByte(peer.Name, '.')
if idx == -1 {
continue
}
domain := peer.Name[idx+1:]
seen := false
// In theory this makes the function O(n^2) worst case,
// but in practice we expect domains to contain very few elements
// (only one until invitations are introduced).
for _, seenDomain := range domains {
if domain == seenDomain {
seen = true
}
}
if !seen {
domains = append(domains, domain)
}
}
return domains
return nil
}
// routerConfig produces a router.Config from a wireguard config and IPN prefs.
func routerConfig(cfg *wgcfg.Config, prefs *Prefs) *router.Config {
var addrs []wgcfg.CIDR
for _, addr := range cfg.Addresses {
addrs = append(addrs, wgcfg.CIDR{
IP: addr.IP,
Mask: 32,
})
}
rs := &router.Config{
LocalAddrs: wgCIDRsToNetaddr(addrs),
SubnetRoutes: wgCIDRsToNetaddr(prefs.AdvertiseRoutes),
LocalAddrs: unmapIPPrefixes(cfg.Addresses),
SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes),
SNATSubnetRoutes: !prefs.NoSNAT,
NetfilterMode: prefs.NetfilterMode,
}
for _, peer := range cfg.Peers {
rs.Routes = append(rs.Routes, wgCIDRsToNetaddr(peer.AllowedIPs)...)
rs.Routes = append(rs.Routes, unmapIPPrefixes(peer.AllowedIPs)...)
}
rs.Routes = append(rs.Routes, netaddr.IPPrefix{
@@ -1279,15 +1259,10 @@ func routerConfig(cfg *wgcfg.Config, prefs *Prefs) *router.Config {
return rs
}
func wgCIDRsToNetaddr(cidrLists ...[]wgcfg.CIDR) (ret []netaddr.IPPrefix) {
for _, cidrs := range cidrLists {
for _, cidr := range cidrs {
ncidr, ok := netaddr.FromStdIPNet(cidr.IPNet())
if !ok {
panic(fmt.Sprintf("conversion of %s from wgcfg to netaddr IPNet failed", cidr))
}
ncidr.IP = ncidr.IP.Unmap()
ret = append(ret, ncidr)
func unmapIPPrefixes(ippsList ...[]netaddr.IPPrefix) (ret []netaddr.IPPrefix) {
for _, ipps := range ippsList {
for _, ipp := range ipps {
ret = append(ret, netaddr.IPPrefix{IP: ipp.IP.Unmap(), Bits: ipp.Bits})
}
}
return ret
@@ -1303,6 +1278,7 @@ func applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *Prefs) {
if m := prefs.DeviceModel; m != "" {
hi.DeviceModel = m
}
hi.ShieldsUp = prefs.ShieldsUp
}
// enterState transitions the backend into newState, updating internal
@@ -1320,6 +1296,8 @@ func (b *LocalBackend) enterState(newState State) {
notify := b.notify
bc := b.c
networkUp := b.prevIfState.AnyInterfaceUp()
activeLogin := b.activeLogin
authURL := b.authURL
b.mu.Unlock()
if state == newState {
@@ -1337,6 +1315,7 @@ func (b *LocalBackend) enterState(newState State) {
switch newState {
case NeedsLogin:
systemd.Status("Needs login: %s", authURL)
b.blockEngineUpdates(true)
fallthrough
case Stopped:
@@ -1344,12 +1323,20 @@ func (b *LocalBackend) enterState(newState State) {
if err != nil {
b.logf("Reconfig(down): %v", err)
}
if authURL == "" {
systemd.Status("Stopped; run 'tailscale up' to log in")
}
case Starting, NeedsMachineAuth:
b.authReconfig()
// Needed so that UpdateEndpoints can run
b.e.RequestStatus()
case Running:
break
var addrs []string
for _, addr := range b.netMap.Addresses {
addrs = append(addrs, addr.IP.String())
}
systemd.Status("Connected; %s; %s", activeLogin, strings.Join(addrs, " "))
default:
b.logf("[unexpected] unknown newState %#v", newState)
}
@@ -1548,7 +1535,6 @@ func (b *LocalBackend) TestOnlyPublicKeys() (machineKey tailcfg.MachineKey, node
// 1.0.x. But eventually we want to stop sending the machine key to
// clients. We can't do that until 1.0.x is no longer supported.
func temporarilySetMachineKeyInPersist() bool {
//lint:ignore S1008 for comments
switch runtime.GOOS {
case "darwin", "ios", "android":
// iOS, macOS, Android users can't downgrade anyway.

119
ipn/local_test.go Normal file
View File

@@ -0,0 +1,119 @@
// 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 ipn
import (
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/tailcfg"
"testing"
)
func TestNetworkMapCompare(t *testing.T) {
prefix1, err := netaddr.ParseIPPrefix("192.168.0.0/24")
if err != nil {
t.Fatal(err)
}
node1 := &tailcfg.Node{Addresses: []netaddr.IPPrefix{prefix1}}
prefix2, err := netaddr.ParseIPPrefix("10.0.0.0/8")
if err != nil {
t.Fatal(err)
}
node2 := &tailcfg.Node{Addresses: []netaddr.IPPrefix{prefix2}}
tests := []struct {
name string
a, b *controlclient.NetworkMap
want bool
}{
{
"both nil",
nil,
nil,
true,
},
{
"b nil",
&controlclient.NetworkMap{},
nil,
false,
},
{
"a nil",
nil,
&controlclient.NetworkMap{},
false,
},
{
"both default",
&controlclient.NetworkMap{},
&controlclient.NetworkMap{},
true,
},
{
"names identical",
&controlclient.NetworkMap{Name: "map1"},
&controlclient.NetworkMap{Name: "map1"},
true,
},
{
"names differ",
&controlclient.NetworkMap{Name: "map1"},
&controlclient.NetworkMap{Name: "map2"},
false,
},
{
"Peers identical",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{}},
true,
},
{
"Peer list length",
// length of Peers list differs
&controlclient.NetworkMap{Peers: []*tailcfg.Node{{}}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{}},
false,
},
{
"Node names identical",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}},
true,
},
{
"Node names differ",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "B"}}},
false,
},
{
"Node lists identical",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
true,
},
{
"Node lists differ",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node2}},
false,
},
{
"Node Users differ",
// User field is not checked.
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{User: 0}}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{User: 1}}},
true,
},
}
for _, tt := range tests {
got := dnsMapsEqual(tt.a, tt.b)
if got != tt.want {
t.Errorf("%s: Equal = %v; want %v", tt.name, got, tt.want)
}
}
}

View File

@@ -23,9 +23,10 @@ import (
func TestLocalLogLines(t *testing.T) {
logListen := tstest.NewLogLineTracker(t.Logf, []string{
"SetPrefs: %v",
"peer keys: %s",
"v%v peers: %v",
"[v1] peer keys: %s",
"[v1] v%v peers: %v",
})
defer logListen.Close()
logid := func(hex byte) logtail.PublicID {
var ret logtail.PublicID
@@ -40,7 +41,7 @@ func TestLocalLogLines(t *testing.T) {
store := &MemoryStore{
cache: make(map[StateKey][]byte),
}
e, err := wgengine.NewFakeUserspaceEngine(logListen.Logf, 0)
e, err := wgengine.NewFakeUserspaceEngine(logListen.Logf, 0, nil)
if err != nil {
t.Fatal(err)
}
@@ -71,7 +72,7 @@ func TestLocalLogLines(t *testing.T) {
prefs.Persist = persist
lb.SetPrefs(prefs)
t.Run("after_prefs", testWantRemain("peer keys: %s", "v%v peers: %v"))
t.Run("after_prefs", testWantRemain("[v1] peer keys: %s", "[v1] v%v peers: %v"))
// log peers, peer keys
status := &wgengine.Status{

View File

@@ -6,6 +6,7 @@ package ipn
import (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"errors"
@@ -20,6 +21,24 @@ import (
"tailscale.com/version"
)
type readOnlyContextKey struct{}
// IsReadonlyContext reports whether ctx is a read-only context, as currently used
// by Unix non-root users running the "tailscale" CLI command. They can run "status",
// but not much else.
func IsReadonlyContext(ctx context.Context) bool {
return ctx.Value(readOnlyContextKey{}) != nil
}
// ReadonlyContextOf returns ctx wrapped with a context value that
// will make IsReadonlyContext reports true.
func ReadonlyContextOf(ctx context.Context) context.Context {
if IsReadonlyContext(ctx) {
return ctx
}
return context.WithValue(ctx, readOnlyContextKey{}, readOnlyContextKey{})
}
var jsonEscapedZero = []byte(`\u0000`)
type NoArgs struct{}
@@ -111,7 +130,7 @@ func (bs *BackendServer) SendInUseOtherUserErrorMessage(msg string) {
// GotCommandMsg parses the incoming message b as a JSON Command and
// calls GotCommand with it.
func (bs *BackendServer) GotCommandMsg(b []byte) error {
func (bs *BackendServer) GotCommandMsg(ctx context.Context, b []byte) error {
cmd := &Command{}
if len(b) == 0 {
return nil
@@ -119,15 +138,15 @@ func (bs *BackendServer) GotCommandMsg(b []byte) error {
if err := json.Unmarshal(b, cmd); err != nil {
return err
}
return bs.GotCommand(cmd)
return bs.GotCommand(ctx, cmd)
}
func (bs *BackendServer) GotFakeCommand(cmd *Command) error {
func (bs *BackendServer) GotFakeCommand(ctx context.Context, cmd *Command) error {
cmd.Version = version.Long
return bs.GotCommand(cmd)
return bs.GotCommand(ctx, cmd)
}
func (bs *BackendServer) GotCommand(cmd *Command) error {
func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
if cmd.Version != version.Long && !cmd.AllowVersionSkew {
vs := fmt.Sprintf("GotCommand: Version mismatch! frontend=%#v backend=%#v",
cmd.Version, version.Long)
@@ -141,12 +160,33 @@ func (bs *BackendServer) GotCommand(cmd *Command) error {
})
return nil
}
// TODO(bradfitz): finish plumbing context down to all the methods below;
// currently we just check for read-only contexts in this method and
// then never use contexts again.
// Actions permitted with a read-only context:
if c := cmd.RequestEngineStatus; c != nil {
bs.b.RequestEngineStatus()
return nil
} else if c := cmd.RequestStatus; c != nil {
bs.b.RequestStatus()
return nil
} else if c := cmd.Ping; c != nil {
bs.b.Ping(c.IP)
return nil
}
if IsReadonlyContext(ctx) {
msg := "permission denied"
bs.send(Notify{ErrMessage: &msg})
return nil
}
if cmd.Quit != nil {
bs.GotQuit = true
return errors.New("Quit command received")
}
if c := cmd.Start; c != nil {
} else if c := cmd.Start; c != nil {
opts := c.Opts
opts.Notify = bs.send
return bs.b.Start(opts)
@@ -165,27 +205,17 @@ func (bs *BackendServer) GotCommand(cmd *Command) error {
} else if c := cmd.SetWantRunning; c != nil {
bs.b.SetWantRunning(*c)
return nil
} else if c := cmd.RequestEngineStatus; c != nil {
bs.b.RequestEngineStatus()
return nil
} else if c := cmd.RequestStatus; c != nil {
bs.b.RequestStatus()
return nil
} else if c := cmd.FakeExpireAfter; c != nil {
bs.b.FakeExpireAfter(c.Duration)
return nil
} else if c := cmd.Ping; c != nil {
bs.b.Ping(c.IP)
return nil
} else {
return fmt.Errorf("BackendServer.Do: no command specified")
}
return fmt.Errorf("BackendServer.Do: no command specified")
}
func (bs *BackendServer) Reset() error {
func (bs *BackendServer) Reset(ctx context.Context) error {
// Tell the backend we got a Logout command, which will cause it
// to forget all its authentication information.
return bs.GotFakeCommand(&Command{Logout: &NoArgs{}})
return bs.GotFakeCommand(ctx, &Command{Logout: &NoArgs{}})
}
type BackendClient struct {

View File

@@ -6,6 +6,7 @@ package ipn
import (
"bytes"
"context"
"testing"
"time"
@@ -81,7 +82,7 @@ func TestClientServer(t *testing.T) {
serverToClientCh <- append([]byte{}, b...)
}
clientToServer := func(b []byte) {
bs.GotCommandMsg(b)
bs.GotCommandMsg(context.TODO(), b)
}
slogf := func(fmt string, args ...interface{}) {
t.Logf("s: "+fmt, args...)

View File

@@ -5,6 +5,7 @@
package ipn
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
@@ -14,7 +15,7 @@ import (
"runtime"
"strings"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/atomicfile"
"tailscale.com/control/controlclient"
"tailscale.com/wgengine/router"
@@ -54,7 +55,7 @@ type Prefs struct {
// ShieldsUp indicates whether to block all incoming connections,
// regardless of the control-provided packet filter. If false, we
// use the packet filter as provided. If true, we block incoming
// connections.
// connections. This overrides tailcfg.Hostinfo's ShieldsUp.
ShieldsUp bool
// AdvertiseTags specifies groups that this node wants to join, for
@@ -99,7 +100,7 @@ type Prefs struct {
// AdvertiseRoutes specifies CIDR prefixes to advertise into the
// Tailscale network as reachable through the current
// node.
AdvertiseRoutes []wgcfg.CIDR
AdvertiseRoutes []netaddr.IPPrefix
// NoSNAT specifies whether to source NAT traffic going to
// destinations in AdvertiseRoutes. The default is to apply source
@@ -205,12 +206,12 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.Persist.Equals(p2.Persist)
}
func compareIPNets(a, b []wgcfg.CIDR) bool {
func compareIPNets(a, b []netaddr.IPPrefix) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !a[i].IP.Equal(b[i].IP) || a[i].Mask != b[i].Mask {
if a[i] != b[i] {
return false
}
}
@@ -276,6 +277,14 @@ func LoadPrefs(filename string) (*Prefs, error) {
if err != nil {
return nil, fmt.Errorf("LoadPrefs open: %w", err) // err includes path
}
if bytes.Contains(data, jsonEscapedZero) {
// Tailscale 1.2.0 - 1.2.8 on Windows had a memory corruption bug
// in the backend process that ended up sending NULL bytes over JSON
// to the frontend which wrote them out to JSON files on disk.
// So if we see one, treat is as corrupt and the user will need
// to log in again. (better than crashing)
return nil, os.ErrNotExist
}
p, err := PrefsFromBytes(data, false)
if err != nil {
return nil, fmt.Errorf("LoadPrefs(%q) decode: %w", filename, err)
@@ -287,7 +296,7 @@ func SavePrefs(filename string, p *Prefs) {
log.Printf("Saving prefs %v %v\n", filename, p.Pretty())
data := p.ToBytes()
os.MkdirAll(filepath.Dir(filename), 0700)
if err := atomicfile.WriteFile(filename, data, 0666); err != nil {
if err := atomicfile.WriteFile(filename, data, 0600); err != nil {
log.Printf("SavePrefs: %v\n", err)
}
}

View File

@@ -7,7 +7,7 @@
package ipn
import (
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/wgengine/router"
)
@@ -44,7 +44,7 @@ var _PrefsNeedsRegeneration = Prefs(struct {
DeviceModel string
NotepadURLs bool
ForceDaemon bool
AdvertiseRoutes []wgcfg.CIDR
AdvertiseRoutes []netaddr.IPPrefix
NoSNAT bool
NetfilterMode router.NetfilterMode
Persist *controlclient.Persist

View File

@@ -7,14 +7,16 @@ package ipn
import (
"errors"
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/tstest"
"tailscale.com/types/wgkey"
"tailscale.com/wgengine/router"
)
@@ -34,9 +36,9 @@ func TestPrefsEqual(t *testing.T) {
have, prefsHandles)
}
nets := func(strs ...string) (ns []wgcfg.CIDR) {
nets := func(strs ...string) (ns []netaddr.IPPrefix) {
for _, s := range strs {
n, err := wgcfg.ParseCIDR(s)
n, err := netaddr.ParseIPPrefix(s)
if err != nil {
panic(err)
}
@@ -165,12 +167,12 @@ func TestPrefsEqual(t *testing.T) {
{
&Prefs{AdvertiseRoutes: nil},
&Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
&Prefs{AdvertiseRoutes: []netaddr.IPPrefix{}},
true,
},
{
&Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
&Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
&Prefs{AdvertiseRoutes: []netaddr.IPPrefix{}},
&Prefs{AdvertiseRoutes: []netaddr.IPPrefix{}},
true,
},
{
@@ -346,7 +348,7 @@ func TestPrefsPretty(t *testing.T) {
{
Prefs{
Persist: &controlclient.Persist{
PrivateNodeKey: wgcfg.PrivateKey{1: 1},
PrivateNodeKey: wgkey.Private{1: 1},
},
},
"linux",
@@ -371,3 +373,25 @@ func TestLoadPrefsNotExist(t *testing.T) {
}
t.Fatalf("unexpected prefs=%#v, err=%v", p, err)
}
// TestLoadPrefsFileWithZeroInIt verifies that LoadPrefs hanldes corrupted input files.
// See issue #954 for details.
func TestLoadPrefsFileWithZeroInIt(t *testing.T) {
f, err := ioutil.TempFile("", "TestLoadPrefsFileWithZeroInIt")
if err != nil {
t.Fatal(err)
}
path := f.Name()
if _, err := f.Write(jsonEscapedZero); err != nil {
t.Fatal(err)
}
f.Close()
defer os.Remove(path)
p, err := LoadPrefs(path)
if errors.Is(err, os.ErrNotExist) {
// expected.
return
}
t.Fatalf("unexpected prefs=%#v, err=%v", p, err)
}

View File

@@ -24,7 +24,7 @@ var ErrStateNotExist = errors.New("no state with given ID")
const (
// MachineKeyStateKey is the key under which we store the machine key,
// in its wgcfg.PrivateKey.MarshalText representation.
// in its wgkey.Private.MarshalText representation.
MachineKeyStateKey = StateKey("_machinekey")
// GlobalDaemonStateKey is the ipn.StateKey that tailscaled

View File

@@ -25,7 +25,7 @@ import (
"strings"
"time"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/term"
"tailscale.com/atomicfile"
"tailscale.com/logtail"
"tailscale.com/logtail/filch"
@@ -49,7 +49,7 @@ type Config struct {
// Policy is a logger and its public ID.
type Policy struct {
// Logtail is the logger.
Logtail logtail.Logger
Logtail *logtail.Logger
// PublicID is the logger's instance identifier.
PublicID logtail.PublicID
}
@@ -311,7 +311,7 @@ func tryFixLogStateLocation(dir, cmdname string) {
// given collection name.
func New(collection string) *Policy {
var lflags int
if terminal.IsTerminal(2) || runtime.GOOS == "windows" {
if term.IsTerminal(2) || runtime.GOOS == "windows" {
lflags = 0
} else {
lflags = log.LstdFlags
@@ -391,7 +391,7 @@ func New(collection string) *Policy {
if filchBuf != nil {
c.Buffer = filchBuf
}
lw := logtail.Log(c, log.Printf)
lw := logtail.NewLogger(c, log.Printf)
log.SetFlags(0) // other logflags are set on console, not here
log.SetOutput(lw)
@@ -413,6 +413,15 @@ func New(collection string) *Policy {
}
}
// SetVerbosityLevel controls the verbosity level that should be
// written to stderr. 0 is the default (not verbose). Levels 1 or higher
// are increasingly verbose.
//
// It should not be changed concurrently with log writes.
func (p *Policy) SetVerbosityLevel(level int) {
p.Logtail.SetVerbosityLevel(level)
}
// Close immediately shuts down the logger.
func (p *Policy) Close() {
ctx, cancel := context.WithCancel(context.Background())

View File

@@ -31,7 +31,7 @@ func main() {
log.Fatalf("logtail: bad -privateid: %v", err)
}
logger := logtail.Log(logtail.Config{
logger := logtail.NewLogger(logtail.Config{
Collection: *collection,
PrivateID: id,
}, log.Printf)

View File

@@ -131,11 +131,11 @@ func New(filePrefix string, opts Options) (f *Filch, err error) {
path1 := filePrefix + ".log1.txt"
path2 := filePrefix + ".log2.txt"
f1, err = os.OpenFile(path1, os.O_CREATE|os.O_RDWR, 0666)
f1, err = os.OpenFile(path1, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return nil, err
}
f2, err = os.OpenFile(path2, os.O_CREATE|os.O_RDWR, 0666)
f2, err = os.OpenFile(path2, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return nil, err
}

View File

@@ -14,6 +14,7 @@ import (
"io/ioutil"
"net/http"
"os"
"strconv"
"time"
"tailscale.com/logtail/backoff"
@@ -24,34 +25,6 @@ import (
// Config.BaseURL isn't provided.
const DefaultHost = "log.tailscale.io"
type Logger interface {
// Write logs an encoded JSON blob.
//
// If the []byte passed to Write is not an encoded JSON blob,
// then contents is fit into a JSON blob and written.
//
// This is intended as an interface for the stdlib "log" package.
Write([]byte) (int, error)
// Flush uploads all logs to the server.
// It blocks until complete or there is an unrecoverable error.
Flush() error
// Shutdown gracefully shuts down the logger while completing any
// remaining uploads.
//
// It will block, continuing to try and upload unless the passed
// context object interrupts it by being done.
// If the shutdown is interrupted, an error is returned.
Shutdown(context.Context) error
// Close shuts down this logger object, the background log uploader
// process, and any associated goroutines.
//
// DEPRECATED: use Shutdown
Close()
}
type Encoder interface {
EncodeAll(src, dst []byte) []byte
Close() error
@@ -66,15 +39,16 @@ type Config struct {
LowMemory bool // if true, logtail minimizes memory use
TimeNow func() time.Time // if set, subsitutes uses of time.Now
Stderr io.Writer // if set, logs are sent here instead of os.Stderr
StderrLevel int // max verbosity level to write to stderr; 0 means the non-verbose messages only
Buffer Buffer // temp storage, if nil a MemoryBuffer
NewZstdEncoder func() Encoder // if set, used to compress logs for transmission
// DrainLogs, if non-nil, disables autmatic uploading of new logs,
// DrainLogs, if non-nil, disables automatic uploading of new logs,
// so that logs are only uploaded when a token is sent to DrainLogs.
DrainLogs <-chan struct{}
}
func Log(cfg Config, logf tslogger.Logf) Logger {
func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
if cfg.BaseURL == "" {
cfg.BaseURL = "https://" + DefaultHost
}
@@ -94,8 +68,9 @@ func Log(cfg Config, logf tslogger.Logf) Logger {
}
cfg.Buffer = NewMemoryBuffer(pendingSize)
}
l := &logger{
l := &Logger{
stderr: cfg.Stderr,
stderrLevel: cfg.StderrLevel,
httpc: cfg.HTTPC,
url: cfg.BaseURL + "/c/" + cfg.Collection + "/" + cfg.PrivateID.String(),
lowMem: cfg.LowMemory,
@@ -122,8 +97,11 @@ func Log(cfg Config, logf tslogger.Logf) Logger {
return l
}
type logger struct {
// Logger writes logs, splitting them as configured between local
// logging facilities and uploading to a log server.
type Logger struct {
stderr io.Writer
stderrLevel int
httpc *http.Client
url string
lowMem bool
@@ -141,7 +119,22 @@ type logger struct {
shutdownDone chan struct{} // closd when shutdown complete
}
func (l *logger) Shutdown(ctx context.Context) error {
// SetVerbosityLevel controls the verbosity level that should be
// written to stderr. 0 is the default (not verbose). Levels 1 or higher
// are increasingly verbose.
//
// It should not be changed concurrently with log writes.
func (l *Logger) SetVerbosityLevel(level int) {
l.stderrLevel = level
}
// Shutdown gracefully shuts down the logger while completing any
// remaining uploads.
//
// It will block, continuing to try and upload unless the passed
// context object interrupts it by being done.
// If the shutdown is interrupted, an error is returned.
func (l *Logger) Shutdown(ctx context.Context) error {
done := make(chan struct{})
go func() {
select {
@@ -163,7 +156,11 @@ func (l *logger) Shutdown(ctx context.Context) error {
return nil
}
func (l *logger) Close() {
// Close shuts down this logger object, the background log uploader
// process, and any associated goroutines.
//
// Deprecated: use Shutdown
func (l *Logger) Close() {
l.Shutdown(context.Background())
}
@@ -174,7 +171,7 @@ func (l *logger) Close() {
//
// If the caller provides a DrainLogs channel, then unblock-drain-on-Write
// is disabled, and it is up to the caller to trigger unblock the drain.
func (l *logger) drainBlock() (shuttingDown bool) {
func (l *Logger) drainBlock() (shuttingDown bool) {
if l.drainLogs == nil {
select {
case <-l.shutdownStart:
@@ -193,7 +190,7 @@ func (l *logger) drainBlock() (shuttingDown bool) {
// drainPending drains and encodes a batch of logs from the buffer for upload.
// If no logs are available, drainPending blocks until logs are available.
func (l *logger) drainPending() (res []byte) {
func (l *Logger) drainPending() (res []byte) {
buf := new(bytes.Buffer)
entries := 0
@@ -254,13 +251,22 @@ func (l *logger) drainPending() (res []byte) {
}
// This is the goroutine that repeatedly uploads logs in the background.
func (l *logger) uploading(ctx context.Context) {
func (l *Logger) uploading(ctx context.Context) {
defer close(l.shutdownDone)
for {
body := l.drainPending()
if l.zstdEncoder != nil {
body = l.zstdEncoder.EncodeAll(body, nil)
origlen := -1 // sentinel value: uncompressed
// Don't attempt to compress tiny bodies; not worth the CPU cycles.
if l.zstdEncoder != nil && len(body) > 256 {
zbody := l.zstdEncoder.EncodeAll(body, nil)
// Only send it compressed if the bandwidth savings are sufficient.
// Just the extra headers associated with enabling compression
// are 50 bytes by themselves.
if len(body)-len(zbody) > 64 {
origlen = len(body)
body = zbody
}
}
for len(body) > 0 {
@@ -269,7 +275,7 @@ func (l *logger) uploading(ctx context.Context) {
return
default:
}
uploaded, err := l.upload(ctx, body)
uploaded, err := l.upload(ctx, body, origlen)
if err != nil {
fmt.Fprintf(l.stderr, "logtail: upload: %v\n", err)
}
@@ -287,7 +293,10 @@ func (l *logger) uploading(ctx context.Context) {
}
}
func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err error) {
// upload uploads body to the log server.
// origlen indicates the pre-compression body length.
// origlen of -1 indicates that the body is not compressed.
func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded bool, err error) {
req, err := http.NewRequest("POST", l.url, bytes.NewReader(body))
if err != nil {
// I know of no conditions under which this could fail.
@@ -295,8 +304,9 @@ func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err er
// TODO record logs to disk
panic("logtail: cannot build http request: " + err.Error())
}
if l.zstdEncoder != nil {
if origlen != -1 {
req.Header.Add("Content-Encoding", "zstd")
req.Header.Add("Orig-Content-Length", strconv.Itoa(origlen))
}
req.Header["User-Agent"] = nil // not worth writing one; save some bytes
@@ -306,7 +316,7 @@ func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err er
req = req.WithContext(ctx)
compressedNote := "not-compressed"
if l.zstdEncoder != nil {
if origlen != -1 {
compressedNote = "compressed"
}
@@ -333,11 +343,13 @@ func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err er
return true, nil
}
func (l *logger) Flush() error {
// Flush uploads all logs to the server.
// It blocks until complete or there is an unrecoverable error.
func (l *Logger) Flush() error {
return nil
}
func (l *logger) send(jsonBlob []byte) (int, error) {
func (l *Logger) send(jsonBlob []byte) (int, error) {
n, err := l.buffer.Write(jsonBlob)
if l.drainLogs == nil {
select {
@@ -350,7 +362,7 @@ func (l *logger) send(jsonBlob []byte) (int, error) {
// TODO: instead of allocating, this should probably just append
// directly into the output log buffer.
func (l *logger) encodeText(buf []byte, skipClientTime bool) []byte {
func (l *Logger) encodeText(buf []byte, skipClientTime bool) []byte {
now := l.timeNow()
// Factor in JSON encoding overhead to try to only do one alloc
@@ -406,7 +418,7 @@ func (l *logger) encodeText(buf []byte, skipClientTime bool) []byte {
return b
}
func (l *logger) encode(buf []byte) []byte {
func (l *Logger) encode(buf []byte) []byte {
if buf[0] != '{' {
return l.encodeText(buf, l.skipClientTime) // text fast-path
}
@@ -447,11 +459,18 @@ func (l *logger) encode(buf []byte) []byte {
return b
}
func (l *logger) Write(buf []byte) (int, error) {
// Write logs an encoded JSON blob.
//
// If the []byte passed to Write is not an encoded JSON blob,
// then contents is fit into a JSON blob and written.
//
// This is intended as an interface for the stdlib "log" package.
func (l *Logger) Write(buf []byte) (int, error) {
if len(buf) == 0 {
return 0, nil
}
if l.stderr != nil && l.stderr != ioutil.Discard {
level, buf := parseAndRemoveLogLevel(buf)
if l.stderr != nil && l.stderr != ioutil.Discard && level <= l.stderrLevel {
if buf[len(buf)-1] == '\n' {
l.stderr.Write(buf)
} else {
@@ -465,3 +484,23 @@ func (l *logger) Write(buf []byte) (int, error) {
_, err := l.send(b)
return len(buf), err
}
var (
openBracketV = []byte("[v")
v1 = []byte("[v1] ")
v2 = []byte("[v2] ")
)
// level 0 is normal (or unknown) level; 1+ are increasingly verbose
func parseAndRemoveLogLevel(buf []byte) (level int, cleanBuf []byte) {
if len(buf) == 0 || buf[0] == '{' || !bytes.Contains(buf, openBracketV) {
return 0, buf
}
if bytes.Contains(buf, v1) {
return 1, bytes.ReplaceAll(buf, v1, nil)
}
if bytes.Contains(buf, v2) {
return 2, bytes.ReplaceAll(buf, v2, nil)
}
return 0, buf
}

View File

@@ -6,6 +6,12 @@ package logtail
import (
"context"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
@@ -14,16 +20,215 @@ func TestFastShutdown(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
l := Log(Config{
BaseURL: "http://localhost:1234",
testServ := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {}))
defer testServ.Close()
l := NewLogger(Config{
BaseURL: testServ.URL,
}, t.Logf)
l.Shutdown(ctx)
err := l.Shutdown(ctx)
if err != nil {
t.Error(err)
}
}
// maximum number of times a test will call l.Write()
const logLines = 3
type LogtailTestServer struct {
srv *httptest.Server // Log server
uploaded chan []byte
}
func NewLogtailTestHarness(t *testing.T) (*LogtailTestServer, *Logger) {
ts := LogtailTestServer{}
// max channel backlog = 1 "started" + #logLines x "log line" + 1 "closed"
ts.uploaded = make(chan []byte, 2+logLines)
ts.srv = httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Error("failed to read HTTP request")
}
ts.uploaded <- body
}))
t.Cleanup(ts.srv.Close)
l := NewLogger(Config{BaseURL: ts.srv.URL}, t.Logf)
// There is always an initial "logtail started" message
body := <-ts.uploaded
if !strings.Contains(string(body), "started") {
t.Errorf("unknown start logging statement: %q", string(body))
}
return &ts, l
}
func TestDrainPendingMessages(t *testing.T) {
ts, l := NewLogtailTestHarness(t)
for i := 0; i < logLines; i++ {
l.Write([]byte("log line"))
}
// all of the "log line" messages usually arrive at once, but poll if needed.
body := ""
for i := 0; i <= logLines; i++ {
body += string(<-ts.uploaded)
count := strings.Count(body, "log line")
if count == logLines {
break
}
// if we never find count == logLines, the test will eventually time out.
}
err := l.Shutdown(context.Background())
if err != nil {
t.Error(err)
}
}
func TestEncodeAndUploadMessages(t *testing.T) {
ts, l := NewLogtailTestHarness(t)
tests := []struct {
name string
log string
want string
}{
{
"plain text",
"log line",
"log line",
},
{
"simple JSON",
`{"text": "log line"}`,
"log line",
},
}
for _, tt := range tests {
io.WriteString(l, tt.log)
body := <-ts.uploaded
data := make(map[string]interface{})
err := json.Unmarshal(body, &data)
if err != nil {
t.Error(err)
}
got := data["text"]
if got != tt.want {
t.Errorf("%s: got %q; want %q", tt.name, got.(string), tt.want)
}
ltail, ok := data["logtail"]
if ok {
logtailmap := ltail.(map[string]interface{})
_, ok = logtailmap["client_time"]
if !ok {
t.Errorf("%s: no client_time present", tt.name)
}
} else {
t.Errorf("%s: no logtail map present", tt.name)
}
}
err := l.Shutdown(context.Background())
if err != nil {
t.Error(err)
}
}
func TestEncodeSpecialCases(t *testing.T) {
ts, l := NewLogtailTestHarness(t)
// -------------------------------------------------------------------------
// JSON log message already contains a logtail field.
io.WriteString(l, `{"logtail": "LOGTAIL", "text": "text"}`)
body := <-ts.uploaded
data := make(map[string]interface{})
err := json.Unmarshal(body, &data)
if err != nil {
t.Error(err)
}
errorHasLogtail, ok := data["error_has_logtail"]
if ok {
if errorHasLogtail != "LOGTAIL" {
t.Errorf("error_has_logtail: got:%q; want:%q",
errorHasLogtail, "LOGTAIL")
}
} else {
t.Errorf("no error_has_logtail field: %v", data)
}
// -------------------------------------------------------------------------
// special characters
io.WriteString(l, "\b\f\n\r\t"+`"\`)
bodytext := string(<-ts.uploaded)
// json.Unmarshal would unescape the characters, we have to look at the encoded text
escaped := strings.Contains(bodytext, `\b\f\n\r\t\"\`)
if !escaped {
t.Errorf("special characters got %s", bodytext)
}
// -------------------------------------------------------------------------
// skipClientTime to omit the logtail metadata
l.skipClientTime = true
io.WriteString(l, "text")
body = <-ts.uploaded
data = make(map[string]interface{})
err = json.Unmarshal(body, &data)
if err != nil {
t.Error(err)
}
_, ok = data["logtail"]
if ok {
t.Errorf("skipClientTime: unexpected logtail map present: %v", data)
}
// -------------------------------------------------------------------------
// lowMem + long string
l.skipClientTime = false
l.lowMem = true
longStr := strings.Repeat("0", 512)
io.WriteString(l, longStr)
body = <-ts.uploaded
data = make(map[string]interface{})
err = json.Unmarshal(body, &data)
if err != nil {
t.Error(err)
}
text, ok := data["text"]
if !ok {
t.Errorf("lowMem: no text %v", data)
}
if n := len(text.(string)); n > 300 {
t.Errorf("lowMem: got %d chars; want <300 chars", n)
}
// -------------------------------------------------------------------------
err = l.Shutdown(context.Background())
if err != nil {
t.Error(err)
}
}
var sink []byte
func TestLoggerEncodeTextAllocs(t *testing.T) {
lg := &logger{timeNow: time.Now}
lg := &Logger{timeNow: time.Now}
inBuf := []byte("some text to encode")
n := testing.AllocsPerRun(1000, func() {
sink = lg.encodeText(inBuf, false)
@@ -34,7 +239,7 @@ func TestLoggerEncodeTextAllocs(t *testing.T) {
}
func TestLoggerWriteLength(t *testing.T) {
lg := &logger{
lg := &Logger{
timeNow: time.Now,
buffer: NewMemoryBuffer(1024),
}
@@ -47,3 +252,54 @@ func TestLoggerWriteLength(t *testing.T) {
t.Errorf("logger.Write wrote %d bytes, expected %d", n, len(inBuf))
}
}
func TestParseAndRemoveLogLevel(t *testing.T) {
tests := []struct {
log string
wantLevel int
wantLog string
}{
{
"no level",
0,
"no level",
},
{
"[v1] level 1",
1,
"level 1",
},
{
"level 1 [v1] ",
1,
"level 1 ",
},
{
"[v2] level 2",
2,
"level 2",
},
{
"level [v2] 2",
2,
"level 2",
},
{
"[v3] no level 3",
0,
"[v3] no level 3",
},
}
for _, tt := range tests {
gotLevel, gotLog := parseAndRemoveLogLevel([]byte(tt.log))
if gotLevel != tt.wantLevel {
t.Errorf("parseAndRemoveLogLevel(%q): got:%d; want %d",
tt.log, gotLevel, tt.wantLevel)
}
if string(gotLog) != tt.wantLog {
t.Errorf("parseAndRemoveLogLevel(%q): got:%q; want %q",
tt.log, gotLog, tt.wantLog)
}
}
}

View File

@@ -71,7 +71,8 @@ type Resolver struct {
}
type ipCacheEntry struct {
ip net.IP
ip net.IP // either v4 or v6
ip6 net.IP // nil if no v4 or no v6
expires time.Time
}
@@ -91,78 +92,87 @@ func (r *Resolver) ttl() time.Duration {
var debug, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_DNS_CACHE"))
// LookupIP returns the first IPv4 address found, otherwise the first IPv6 address.
func (r *Resolver) LookupIP(ctx context.Context, host string) (net.IP, error) {
// LookupIP returns the host's primary IP address (either IPv4 or
// IPv6, but preferring IPv4) and optionally its IPv6 address, if
// there is both IPv4 and IPv6.
//
// If err is nil, ip will be non-nil. The v6 address may be nil even
// with a nil error.
func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 net.IP, err error) {
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
return ip4, nil
return ip4, nil, nil
}
if debug {
log.Printf("dnscache: %q is an IP", host)
}
return ip, nil
return ip, nil, nil
}
if ip, ok := r.lookupIPCache(host); ok {
if ip, ip6, ok := r.lookupIPCache(host); ok {
if debug {
log.Printf("dnscache: %q = %v (cached)", host, ip)
}
return ip, nil
return ip, ip6, nil
}
type ipPair struct {
ip, ip6 net.IP
}
ch := r.sf.DoChan(host, func() (interface{}, error) {
ip, err := r.lookupIP(host)
ip, ip6, err := r.lookupIP(host)
if err != nil {
return nil, err
}
return ip, nil
return ipPair{ip, ip6}, nil
})
select {
case res := <-ch:
if res.Err != nil {
if r.UseLastGood {
if ip, ok := r.lookupIPCacheExpired(host); ok {
if ip, ip6, ok := r.lookupIPCacheExpired(host); ok {
if debug {
log.Printf("dnscache: %q using %v after error", host, ip)
}
return ip, nil
return ip, ip6, nil
}
}
if debug {
log.Printf("dnscache: error resolving %q: %v", host, res.Err)
}
return nil, res.Err
return nil, nil, res.Err
}
return res.Val.(net.IP), nil
pair := res.Val.(ipPair)
return pair.ip, pair.ip6, nil
case <-ctx.Done():
if debug {
log.Printf("dnscache: context done while resolving %q: %v", host, ctx.Err())
}
return nil, ctx.Err()
return nil, nil, ctx.Err()
}
}
func (r *Resolver) lookupIPCache(host string) (ip net.IP, ok bool) {
func (r *Resolver) lookupIPCache(host string) (ip, ip6 net.IP, ok bool) {
r.mu.Lock()
defer r.mu.Unlock()
if ent, ok := r.ipCache[host]; ok && ent.expires.After(time.Now()) {
return ent.ip, true
return ent.ip, ent.ip6, true
}
return nil, false
return nil, nil, false
}
func (r *Resolver) lookupIPCacheExpired(host string) (ip net.IP, ok bool) {
func (r *Resolver) lookupIPCacheExpired(host string) (ip, ip6 net.IP, ok bool) {
r.mu.Lock()
defer r.mu.Unlock()
if ent, ok := r.ipCache[host]; ok {
return ent.ip, true
return ent.ip, ent.ip6, true
}
return nil, false
return nil, nil, false
}
func (r *Resolver) lookupTimeoutForHost(host string) time.Duration {
if r.UseLastGood {
if _, ok := r.lookupIPCacheExpired(host); ok {
if _, _, ok := r.lookupIPCacheExpired(host); ok {
// If we have some previous good value for this host,
// don't give this DNS lookup much time. If we're in a
// situation where the user's DNS server is unreachable
@@ -177,40 +187,52 @@ func (r *Resolver) lookupTimeoutForHost(host string) time.Duration {
return 10 * time.Second
}
func (r *Resolver) lookupIP(host string) (net.IP, error) {
if ip, ok := r.lookupIPCache(host); ok {
func (r *Resolver) lookupIP(host string) (ip, ip6 net.IP, err error) {
if ip, ip6, ok := r.lookupIPCache(host); ok {
if debug {
log.Printf("dnscache: %q found in cache as %v", host, ip)
}
return ip, nil
return ip, ip6, nil
}
ctx, cancel := context.WithTimeout(context.Background(), r.lookupTimeoutForHost(host))
defer cancel()
ips, err := r.fwd().LookupIPAddr(ctx, host)
if err != nil {
return nil, err
return nil, nil, err
}
if len(ips) == 0 {
return nil, fmt.Errorf("no IPs for %q found", host)
return nil, nil, fmt.Errorf("no IPs for %q found", host)
}
have4 := false
for _, ipa := range ips {
if ip4 := ipa.IP.To4(); ip4 != nil {
return r.addIPCache(host, ip4, r.ttl()), nil
if !have4 {
ip6 = ip
ip = ip4
have4 = true
}
} else {
if have4 {
ip6 = ipa.IP
} else {
ip = ipa.IP
}
}
}
return r.addIPCache(host, ips[0].IP, r.ttl()), nil
r.addIPCache(host, ip, ip6, r.ttl())
return ip, ip6, nil
}
func (r *Resolver) addIPCache(host string, ip net.IP, d time.Duration) net.IP {
func (r *Resolver) addIPCache(host string, ip, ip6 net.IP, d time.Duration) {
if isPrivateIP(ip) {
// Don't cache obviously wrong entries from captive portals.
// TODO: use DoH or DoT for the forwarding resolver?
if debug {
log.Printf("dnscache: %q resolved to private IP %v; using but not caching", host, ip)
}
return ip
return
}
if debug {
@@ -222,8 +244,7 @@ func (r *Resolver) addIPCache(host string, ip net.IP, d time.Duration) net.IP {
if r.ipCache == nil {
r.ipCache = make(map[string]ipCacheEntry)
}
r.ipCache[host] = ipCacheEntry{ip: ip, expires: time.Now().Add(d)}
return ip
r.ipCache[host] = ipCacheEntry{ip: ip, ip6: ip6, expires: time.Now().Add(d)}
}
func mustCIDR(s string) *net.IPNet {
@@ -255,7 +276,7 @@ func Dialer(fwd DialContextFunc, dnsCache *Resolver) DialContextFunc {
// inventing a similar one.
return fwd(ctx, network, address)
}
ip, err := dnsCache.LookupIP(ctx, host)
ip, ip6, err := dnsCache.LookupIP(ctx, host)
if err != nil {
return nil, fmt.Errorf("failed to resolve %q: %w", host, err)
}
@@ -263,6 +284,19 @@ func Dialer(fwd DialContextFunc, dnsCache *Resolver) DialContextFunc {
if debug {
log.Printf("dnscache: dialing %s, %s for %s", network, dst, address)
}
c, err := fwd(ctx, network, dst)
if err == nil || ctx.Err() != nil || ip6 == nil {
return c, err
}
// Fall back to trying IPv6.
// TODO(bradfitz): this is a primarily for IPv6-only
// hosts; it's not supposed to be a real Happy
// Eyeballs implementation. We should use the net
// package's implementation of that by plumbing this
// dnscache impl into net.Dialer.Resolver.Dial and
// unmarshal/marshal DNS queries/responses to the net
// package. This works for v6-only hosts for now.
dst = net.JoinHostPort(ip6.String(), port)
return fwd(ctx, network, dst)
}
}

104
net/flowtrack/flowtrack.go Normal file
View File

@@ -0,0 +1,104 @@
// 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.
//
// Original implementation (from same author) from which this was derived was:
// https://github.com/golang/groupcache/blob/5b532d6fd5efaf7fa130d4e859a2fde0fc3a9e1b/lru/lru.go
// ... which was Apache licensed:
// https://github.com/golang/groupcache/blob/master/LICENSE
// Package flowtrack contains types for tracking TCP/UDP flows by 4-tuples.
package flowtrack
import (
"container/list"
"fmt"
"inet.af/netaddr"
)
// Tuple is a 4-tuple of source and destination IP and port.
type Tuple struct {
Src netaddr.IPPort
Dst netaddr.IPPort
}
func (t Tuple) String() string {
return fmt.Sprintf("(%v => %v)", t.Src, t.Dst)
}
// Cache is an LRU cache keyed by Tuple.
//
// The zero value is valid to use.
//
// It is not safe for concurrent access.
type Cache struct {
// MaxEntries is the maximum number of cache entries before
// an item is evicted. Zero means no limit.
MaxEntries int
ll *list.List
m map[Tuple]*list.Element // of *entry
}
// entry is the container/list element type.
type entry struct {
key Tuple
value interface{}
}
// Add adds a value to the cache, set or updating its assoicated
// value.
//
// If MaxEntries is non-zero and the length of the cache is greater
// after any addition, the least recently used value is evicted.
func (c *Cache) Add(key Tuple, value interface{}) {
if c.m == nil {
c.m = make(map[Tuple]*list.Element)
c.ll = list.New()
}
if ee, ok := c.m[key]; ok {
c.ll.MoveToFront(ee)
ee.Value.(*entry).value = value
return
}
ele := c.ll.PushFront(&entry{key, value})
c.m[key] = ele
if c.MaxEntries != 0 && c.Len() > c.MaxEntries {
c.RemoveOldest()
}
}
// Get looks up a key's value from the cache, also reporting
// whether it was present.
func (c *Cache) Get(key Tuple) (value interface{}, ok bool) {
if ele, hit := c.m[key]; hit {
c.ll.MoveToFront(ele)
return ele.Value.(*entry).value, true
}
return nil, false
}
// Remove removes the provided key from the cache if it was present.
func (c *Cache) Remove(key Tuple) {
if ele, hit := c.m[key]; hit {
c.removeElement(ele)
}
}
// RemoveOldest removes the oldest item from the cache, if any.
func (c *Cache) RemoveOldest() {
if c.ll != nil {
if ele := c.ll.Back(); ele != nil {
c.removeElement(ele)
}
}
}
func (c *Cache) removeElement(e *list.Element) {
c.ll.Remove(e)
delete(c.m, e.Value.(*entry).key)
}
// Len returns the number of items in the cache.
func (c *Cache) Len() int { return len(c.m) }

View File

@@ -0,0 +1,82 @@
// 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 flowtrack
import (
"testing"
"inet.af/netaddr"
)
func TestCache(t *testing.T) {
c := &Cache{MaxEntries: 2}
k1 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("1.1.1.1:1")}
k2 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("2.2.2.2:2")}
k3 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("3.3.3.3:3")}
k4 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("4.4.4.4:4")}
wantLen := func(want int) {
t.Helper()
if got := c.Len(); got != want {
t.Fatalf("Len = %d; want %d", got, want)
}
}
wantVal := func(key Tuple, want interface{}) {
t.Helper()
got, ok := c.Get(key)
if !ok {
t.Fatalf("Get(%q) failed; want value %v", key, want)
}
if got != want {
t.Fatalf("Get(%q) = %v; want %v", key, got, want)
}
}
wantMissing := func(key Tuple) {
t.Helper()
if got, ok := c.Get(key); ok {
t.Fatalf("Get(%q) = %v; want absent from cache", key, got)
}
}
wantLen(0)
c.RemoveOldest() // shouldn't panic
c.Remove(k4) // shouldn't panic
c.Add(k1, 1)
wantLen(1)
c.Add(k2, 2)
wantLen(2)
c.Add(k3, 3)
wantLen(2) // hit the max
wantMissing(k1)
c.Remove(k1)
wantLen(2) // no change; k1 should've been the deleted one per LRU
wantVal(k3, 3)
wantVal(k2, 2)
c.Remove(k2)
wantLen(1)
wantMissing(k2)
c.Add(k3, 30)
wantVal(k3, 30)
wantLen(1)
allocs := int(testing.AllocsPerRun(1000, func() {
got, ok := c.Get(k3)
if !ok {
t.Fatal("missing k3")
}
if got != 30 {
t.Fatalf("got = %d; want 30", got)
}
}))
if allocs != 0 {
t.Errorf("allocs = %v; want 0", allocs)
}
}

View File

@@ -94,11 +94,6 @@ func LocalAddresses() (regular, loopback []string, err error) {
if !ok {
continue
}
if ip.Is6() {
// TODO(crawshaw): IPv6 support.
// Easy to do here, but we need good endpoint ordering logic.
continue
}
// TODO(apenwarr): don't special case cgNAT.
// In the general wireguard case, it might
// very well be something we can route to

View File

@@ -5,6 +5,7 @@
package interfaces
import (
"errors"
"os/exec"
"go4.org/mem"
@@ -62,8 +63,12 @@ func likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) {
ip, err := netaddr.ParseIP(string(mem.Append(nil, ipm)))
if err == nil && isPrivateIP(ip) {
ret = ip
// We've found what we're looking for.
return errStopReadingNetstatTable
}
return nil
})
return ret, !ret.IsZero()
}
var errStopReadingNetstatTable = errors.New("found private gateway")

View File

@@ -692,7 +692,7 @@ func (rs *reportState) probePortMapServices() {
port1900 := netaddr.IPPort{IP: gw, Port: 1900}.UDPAddr()
port5351 := netaddr.IPPort{IP: gw, Port: 5351}.UDPAddr()
rs.c.logf("probePortMapServices: me %v -> gw %v", myIP, gw)
rs.c.logf("[v1] probePortMapServices: me %v -> gw %v", myIP, gw)
// Create a UDP4 socket used just for querying for UPnP, NAT-PMP, and PCP.
uc, err := netns.Listener().ListenPacket(context.Background(), "udp4", ":0")
@@ -710,6 +710,7 @@ func (rs *reportState) probePortMapServices() {
uc.WriteTo(pcpPacket(myIP, tempPort, false), port5351)
res := make([]byte, 1500)
sentPCPDelete := false
for {
n, addr, err := uc.ReadFrom(res)
if err != nil {
@@ -727,11 +728,14 @@ func (rs *reportState) probePortMapServices() {
if n == 60 && res[0] == 0x02 { // right length and version 2
rs.setOptBool(&rs.report.PCP, true)
// And now delete the mapping.
// (PCP is the only protocol of the three that requires
// we cause a side effect to detect whether it's present,
// so we need to redo that side effect now.)
uc.WriteTo(pcpPacket(myIP, tempPort, true), port5351)
if !sentPCPDelete {
sentPCPDelete = true
// And now delete the mapping.
// (PCP is the only protocol of the three that requires
// we cause a side effect to detect whether it's present,
// so we need to redo that side effect now.)
uc.WriteTo(pcpPacket(myIP, tempPort, true), port5351)
}
}
}
}
@@ -747,6 +751,7 @@ var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" +
var v4unspec, _ = netaddr.ParseIP("0.0.0.0")
// pcpPacket generates a PCP packet with a MAP opcode.
func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte {
const udpProtoNumber = 17
lifetimeSeconds := uint32(1)
@@ -754,17 +759,24 @@ func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte {
lifetimeSeconds = 0
}
const opMap = 1
// 24 byte header + 36 byte map opcode
pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8)
// The header (https://tools.ietf.org/html/rfc6887#section-7.1)
pkt[0] = 2 // version
pkt[1] = opMap
binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds)
myIP16 := myIP.As16()
copy(pkt[8:], myIP16[:])
rand.Read(pkt[24 : 24+12])
pkt[36] = udpProtoNumber
binary.BigEndian.PutUint16(pkt[40:], uint16(mapToLocalPort))
// The map opcode body (https://tools.ietf.org/html/rfc6887#section-11.1)
mapOp := pkt[24:]
rand.Read(mapOp[:12]) // 96 bit mappping nonce
mapOp[12] = udpProtoNumber
binary.BigEndian.PutUint16(mapOp[16:], uint16(mapToLocalPort))
v4unspec16 := v4unspec.As16()
copy(pkt[40:], v4unspec16[:])
copy(mapOp[20:], v4unspec16[:])
return pkt
}
@@ -830,7 +842,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
ifState, err := interfaces.GetState()
if err != nil {
c.logf("interfaces: %v", err)
c.logf("[v1] interfaces: %v", err)
return nil, err
}
@@ -939,7 +951,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
go func(reg *tailcfg.DERPRegion) {
defer wg.Done()
if d, ip, err := c.measureHTTPSLatency(ctx, reg); err != nil {
c.logf("netcheck: measuring HTTPS latency of %v (%d): %v", reg.RegionCode, reg.RegionID, err)
c.logf("[v1] netcheck: measuring HTTPS latency of %v (%d): %v", reg.RegionCode, reg.RegionID, err)
} else {
rs.mu.Lock()
rs.report.RegionLatency[reg.RegionID] = d
@@ -1033,7 +1045,7 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
}
func (c *Client) logConciseReport(r *Report, dm *tailcfg.DERPMap) {
c.logf("%v", logger.ArgWriter(func(w *bufio.Writer) {
c.logf("[v1] report: %v", logger.ArgWriter(func(w *bufio.Writer) {
fmt.Fprintf(w, "udp=%v", r.UDP)
if !r.IPv4 {
fmt.Fprintf(w, " v4=%v", r.IPv4)
@@ -1090,6 +1102,10 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) {
c.mu.Lock()
defer c.mu.Unlock()
var prevDERP int
if c.last != nil {
prevDERP = c.last.PreferredDERP
}
if c.prev == nil {
c.prev = map[time.Time]*Report{}
}
@@ -1107,9 +1123,9 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) {
delete(c.prev, t)
continue
}
for hp, d := range pr.RegionLatency {
if bd, ok := bestRecent[hp]; !ok || d < bd {
bestRecent[hp] = d
for regionID, d := range pr.RegionLatency {
if bd, ok := bestRecent[regionID]; !ok || d < bd {
bestRecent[regionID] = d
}
}
}
@@ -1117,13 +1133,27 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) {
// Then, pick which currently-alive DERP server from the
// current report has the best latency over the past maxAge.
var bestAny time.Duration
for hp := range r.RegionLatency {
best := bestRecent[hp]
var oldRegionCurLatency time.Duration
for regionID, d := range r.RegionLatency {
if regionID == prevDERP {
oldRegionCurLatency = d
}
best := bestRecent[regionID]
if r.PreferredDERP == 0 || best < bestAny {
bestAny = best
r.PreferredDERP = hp
r.PreferredDERP = regionID
}
}
// If we're changing our preferred DERP but the old one's still
// accessible and the new one's not much better, just stick with
// where we are.
if prevDERP != 0 &&
r.PreferredDERP != prevDERP &&
oldRegionCurLatency != 0 &&
bestAny > oldRegionCurLatency/3*2 {
r.PreferredDERP = prevDERP
}
}
func updateLatency(m map[int]time.Duration, regionID int, d time.Duration) {

View File

@@ -195,6 +195,24 @@ func TestAddReportHistoryAndSetPreferredDERP(t *testing.T) {
wantPrevLen: 1, // t=[0123]s all gone. (too old, older than 10 min)
wantDERP: 3, // only option
},
{
name: "preferred_derp_hysteresis_no_switch",
steps: []step{
{0 * time.Second, report("d1", 4, "d2", 5)},
{1 * time.Second, report("d1", 4, "d2", 3)},
},
wantPrevLen: 2,
wantDERP: 1, // 2 didn't get fast enough
},
{
name: "preferred_derp_hysteresis_do_switch",
steps: []step{
{0 * time.Second, report("d1", 4, "d2", 5)},
{1 * time.Second, report("d1", 4, "d2", 1)},
},
wantPrevLen: 2,
wantDERP: 2, // 2 got fast enough
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -554,9 +572,47 @@ func TestLogConciseReport(t *testing.T) {
var buf bytes.Buffer
c := &Client{Logf: func(f string, a ...interface{}) { fmt.Fprintf(&buf, f, a...) }}
c.logConciseReport(tt.r, dm)
if got := buf.String(); got != tt.want {
if got := strings.TrimPrefix(buf.String(), "[v1] report: "); got != tt.want {
t.Errorf("unexpected result.\n got: %#q\nwant: %#q\n", got, tt.want)
}
})
}
}
func TestSortRegions(t *testing.T) {
unsortedMap := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{},
}
for rid := 1; rid <= 5; rid++ {
var nodes []*tailcfg.DERPNode
nodes = append(nodes, &tailcfg.DERPNode{
Name: fmt.Sprintf("%da", rid),
RegionID: rid,
HostName: fmt.Sprintf("derp%d-1", rid),
IPv4: fmt.Sprintf("%d.0.0.1", rid),
IPv6: fmt.Sprintf("%d::1", rid),
})
unsortedMap.Regions[rid] = &tailcfg.DERPRegion{
RegionID: rid,
Nodes: nodes,
}
}
report := newReport()
report.RegionLatency[1] = time.Second * time.Duration(5)
report.RegionLatency[2] = time.Second * time.Duration(3)
report.RegionLatency[3] = time.Second * time.Duration(6)
report.RegionLatency[4] = time.Second * time.Duration(0)
report.RegionLatency[5] = time.Second * time.Duration(2)
sortedMap := sortRegions(unsortedMap, report)
// Sorting by latency this should result in rid: 5, 2, 1, 3
// rid 4 with latency 0 should be at the end
want := []int{5, 2, 1, 3, 4}
got := make([]int, len(sortedMap))
for i, r := range sortedMap {
got[i] = r.RegionID
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v; want %v", got, want)
}
}

View File

@@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"math/bits"
"runtime"
"syscall"
"unsafe"
@@ -63,13 +62,13 @@ func get() (*Table, error) {
}
func (t *Table) addEntries(fam int) error {
size := new(uint32)
var size uint32
var addr unsafe.Pointer
var buf []byte
for {
err, _, _ := getTCPTable.Call(
uintptr(addr),
uintptr(unsafe.Pointer(size)),
uintptr(unsafe.Pointer(&size)),
1, // sorted
uintptr(fam),
tcpTableOwnerPidAll,
@@ -80,25 +79,21 @@ func (t *Table) addEntries(fam int) error {
}
if err == uintptr(syscall.ERROR_INSUFFICIENT_BUFFER) {
const maxSize = 10 << 20
if *size > maxSize || *size < 4 {
return fmt.Errorf("unreasonable kernel-reported size %d", *size)
if size > maxSize || size < 4 {
return fmt.Errorf("unreasonable kernel-reported size %d", size)
}
buf = make([]byte, *size)
buf = make([]byte, size)
addr = unsafe.Pointer(&buf[0])
continue
}
runtime.KeepAlive(size)
runtime.KeepAlive(addr)
return syscall.Errno(err)
}
if len(buf) < int(*size) {
runtime.KeepAlive(size)
runtime.KeepAlive(addr)
if len(buf) < int(size) {
return errors.New("unexpected size growth from system call")
}
buf = buf[:*size]
buf = buf[:size]
numEntries := *(*uint32)(unsafe.Pointer(&buf[0]))
numEntries := endian.Native.Uint32(buf[:4])
buf = buf[4:]
var recSize int
@@ -133,8 +128,6 @@ func (t *Table) addEntries(fam int) error {
}
buf = buf[recSize:]
}
runtime.KeepAlive(addr)
runtime.KeepAlive(size)
return nil
}

View File

@@ -60,7 +60,7 @@ func (p *Pipe) Read(b []byte) (n int, err error) {
for {
p.mu.Lock()
closed := p.closed
timedout := !p.readTimeout.IsZero() && time.Now().After(p.readTimeout)
timedout := !p.readTimeout.IsZero() && !time.Now().Before(p.readTimeout)
blocked := p.blocked
if !closed && !timedout && len(p.buf) > 0 {
n2 := copy(b, p.buf)
@@ -99,7 +99,7 @@ func (p *Pipe) Write(b []byte) (n int, err error) {
for {
p.mu.Lock()
closed := p.closed
timedout := !p.writeTimeout.IsZero() && time.Now().After(p.writeTimeout)
timedout := !p.writeTimeout.IsZero() && !time.Now().Before(p.writeTimeout)
blocked := p.blocked
if !closed && !timedout {
n2 := len(b)

View File

@@ -35,7 +35,7 @@ func TestPipeTimeout(t *testing.T) {
p := NewPipe("p1", 1<<16)
p.SetWriteDeadline(time.Now().Add(-1 * time.Second))
n, err := p.Write([]byte{'h'})
if err == nil || !errors.Is(err, ErrWriteTimeout) || !errors.Is(err, ErrTimeout) {
if !errors.Is(err, ErrWriteTimeout) || !errors.Is(err, ErrTimeout) {
t.Errorf("missing write timeout got err: %v", err)
}
if n != 0 {
@@ -49,7 +49,7 @@ func TestPipeTimeout(t *testing.T) {
p.SetReadDeadline(time.Now().Add(-1 * time.Second))
b := make([]byte, 1)
n, err := p.Read(b)
if err == nil || !errors.Is(err, ErrReadTimeout) || !errors.Is(err, ErrTimeout) {
if !errors.Is(err, ErrReadTimeout) || !errors.Is(err, ErrTimeout) {
t.Errorf("missing read timeout got err: %v", err)
}
if n != 0 {
@@ -65,7 +65,7 @@ func TestPipeTimeout(t *testing.T) {
if err := p.Block(); err != nil {
t.Fatal(err)
}
if _, err := p.Write([]byte{'h'}); err == nil || !errors.Is(err, ErrWriteTimeout) {
if _, err := p.Write([]byte{'h'}); !errors.Is(err, ErrWriteTimeout) {
t.Fatalf("want write timeout got: %v", err)
}
})
@@ -80,11 +80,10 @@ func TestPipeTimeout(t *testing.T) {
if err := p.Block(); err != nil {
t.Fatal(err)
}
if _, err := p.Read(b); err == nil || !errors.Is(err, ErrReadTimeout) {
if _, err := p.Read(b); !errors.Is(err, ErrReadTimeout) {
t.Fatalf("want read timeout got: %v", err)
}
})
}
func TestLimit(t *testing.T) {
@@ -117,4 +116,8 @@ func TestLimit(t *testing.T) {
} else if n != 1 {
t.Errorf("Read(%q): n=%d want 1", string(b), n)
}
if err := <-errCh; err != nil {
t.Error(err)
}
}

View File

@@ -36,9 +36,6 @@ type Header interface {
// purpose of computing length and checksum fields. Marshal
// implementations must not allocate memory.
Marshal(buf []byte) error
// ToResponse transforms the header into one for a response packet.
// For instance, this swaps the source and destination IPs.
ToResponse()
}
// Generate generates a new packet with the given Header and

View File

@@ -24,6 +24,17 @@ const (
TCP IPProto = 0x06
UDP IPProto = 0x11
// TSMP is the Tailscale Message Protocol (our ICMP-ish
// thing), an IP protocol used only between Tailscale nodes
// (still encrypted by WireGuard) that communicates why things
// failed, etc.
//
// Proto number 99 is reserved for "any private encryption
// scheme". We never accept these from the host OS stack nor
// send them to the host network stack. It's only used between
// nodes.
TSMP IPProto = 99
// Fragment represents any non-first IP fragment, for which we
// don't have the sub-protocol header (and therefore can't
// figure out what the sub-protocol is).
@@ -47,6 +58,8 @@ func (p IPProto) String() string {
return "UDP"
case TCP:
return "TCP"
case TSMP:
return "TSMP"
default:
return "Unknown"
}

View File

@@ -6,47 +6,11 @@ package packet
import (
"encoding/binary"
"fmt"
"errors"
"inet.af/netaddr"
)
// IP4 is an IPv4 address.
type IP4 uint32
// IPFromNetaddr converts a netaddr.IP to an IP4. Panics if !ip.Is4.
func IP4FromNetaddr(ip netaddr.IP) IP4 {
ipbytes := ip.As4()
return IP4(binary.BigEndian.Uint32(ipbytes[:]))
}
// Netaddr converts ip to a netaddr.IP.
func (ip IP4) Netaddr() netaddr.IP {
return netaddr.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}
func (ip IP4) String() string {
return fmt.Sprintf("%d.%d.%d.%d", byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}
// IsMulticast returns whether ip is a multicast address.
func (ip IP4) IsMulticast() bool {
return byte(ip>>24)&0xf0 == 0xe0
}
// IsLinkLocalUnicast returns whether ip is a link-local unicast
// address.
func (ip IP4) IsLinkLocalUnicast() bool {
return byte(ip>>24) == 169 && byte(ip>>16) == 254
}
// IsMostLinkLocalUnicast returns whether ip is a link-local unicast
// address other than the magical "169.254.169.254" address used by
// GCP DNS.
func (ip IP4) IsMostLinkLocalUnicast() bool {
return ip.IsLinkLocalUnicast() && ip != 0xA9FEA9FE
}
// ip4HeaderLength is the length of an IPv4 header with no IP options.
const ip4HeaderLength = 20
@@ -54,8 +18,8 @@ const ip4HeaderLength = 20
type IP4Header struct {
IPProto IPProto
IPID uint16
SrcIP IP4
DstIP IP4
Src netaddr.IP
Dst netaddr.IP
}
// Len implements Header.
@@ -63,6 +27,8 @@ func (h IP4Header) Len() int {
return ip4HeaderLength
}
var errWrongFamily = errors.New("wrong address family for src/dst IP")
// Marshal implements Header.
func (h IP4Header) Marshal(buf []byte) error {
if len(buf) < h.Len() {
@@ -71,6 +37,9 @@ func (h IP4Header) Marshal(buf []byte) error {
if len(buf) > maxPacketLength {
return errLargePacket
}
if !h.Src.Is4() || !h.Dst.Is4() {
return errWrongFamily
}
buf[0] = 0x40 | (byte(h.Len() >> 2)) // IPv4 + IHL
buf[1] = 0x00 // DSCP + ECN
@@ -83,8 +52,10 @@ func (h IP4Header) Marshal(buf []byte) error {
// it later, because the checksum computation runs over these
// bytes and expects them to be zero.
binary.BigEndian.PutUint16(buf[10:12], 0)
binary.BigEndian.PutUint32(buf[12:16], uint32(h.SrcIP)) // Src
binary.BigEndian.PutUint32(buf[16:20], uint32(h.DstIP)) // Dst
src := h.Src.As4()
dst := h.Dst.As4()
copy(buf[12:16], src[:])
copy(buf[16:20], dst[:])
binary.BigEndian.PutUint16(buf[10:12], ip4Checksum(buf[0:20])) // Checksum
@@ -93,7 +64,7 @@ func (h IP4Header) Marshal(buf []byte) error {
// ToResponse implements Header.
func (h *IP4Header) ToResponse() {
h.SrcIP, h.DstIP = h.DstIP, h.SrcIP
h.Src, h.Dst = h.Dst, h.Src
// Flip the bits in the IPID. If incoming IPIDs are distinct, so are these.
h.IPID = ^h.IPID
}
@@ -135,8 +106,9 @@ func (h IP4Header) marshalPseudo(buf []byte) error {
}
length := len(buf) - h.Len()
binary.BigEndian.PutUint32(buf[8:12], uint32(h.SrcIP))
binary.BigEndian.PutUint32(buf[12:16], uint32(h.DstIP))
src, dst := h.Src.As4(), h.Dst.As4()
copy(buf[8:12], src[:])
copy(buf[12:16], dst[:])
buf[16] = 0x0
buf[17] = uint8(h.IPProto)
binary.BigEndian.PutUint16(buf[18:20], uint16(length))

View File

@@ -6,45 +6,10 @@ package packet
import (
"encoding/binary"
"fmt"
"inet.af/netaddr"
)
// IP6 is an IPv6 address.
type IP6 struct {
Hi, Lo uint64
}
// IP6FromNetaddr converts a netaddr.IP to an IP6. Panics if !ip.Is6.
func IP6FromNetaddr(ip netaddr.IP) IP6 {
if !ip.Is6() {
panic(fmt.Sprintf("IP6FromNetaddr called with non-v6 addr %q", ip))
}
b := ip.As16()
return IP6{binary.BigEndian.Uint64(b[:8]), binary.BigEndian.Uint64(b[8:])}
}
// Netaddr converts ip to a netaddr.IP.
func (ip IP6) Netaddr() netaddr.IP {
var b [16]byte
binary.BigEndian.PutUint64(b[:8], ip.Hi)
binary.BigEndian.PutUint64(b[8:], ip.Lo)
return netaddr.IPFrom16(b)
}
func (ip IP6) String() string {
return ip.Netaddr().String()
}
func (ip IP6) IsMulticast() bool {
return (ip.Hi >> 56) == 0xFF
}
func (ip IP6) IsLinkLocalUnicast() bool {
return (ip.Hi >> 48) == 0xFE80
}
// ip6HeaderLength is the length of an IPv6 header with no IP options.
const ip6HeaderLength = 40
@@ -52,8 +17,8 @@ const ip6HeaderLength = 40
type IP6Header struct {
IPProto IPProto
IPID uint32 // only lower 20 bits used
SrcIP IP6
DstIP IP6
Src netaddr.IP
Dst netaddr.IP
}
// Len implements Header.
@@ -75,17 +40,16 @@ func (h IP6Header) Marshal(buf []byte) error {
binary.BigEndian.PutUint16(buf[4:6], uint16(len(buf)-ip6HeaderLength)) // Total length
buf[6] = uint8(h.IPProto) // Inner protocol
buf[7] = 64 // TTL
binary.BigEndian.PutUint64(buf[8:16], h.SrcIP.Hi)
binary.BigEndian.PutUint64(buf[16:24], h.SrcIP.Lo)
binary.BigEndian.PutUint64(buf[24:32], h.DstIP.Hi)
binary.BigEndian.PutUint64(buf[32:40], h.DstIP.Lo)
src, dst := h.Src.As16(), h.Dst.As16()
copy(buf[8:24], src[:])
copy(buf[24:40], dst[:])
return nil
}
// ToResponse implements Header.
func (h *IP6Header) ToResponse() {
h.SrcIP, h.DstIP = h.DstIP, h.SrcIP
h.Src, h.Dst = h.Dst, h.Src
// Flip the bits in the IPID. If incoming IPIDs are distinct, so are these.
h.IPID = (^h.IPID) & 0x000FFFFF
}
@@ -100,10 +64,9 @@ func (h IP6Header) marshalPseudo(buf []byte) error {
return errLargePacket
}
binary.BigEndian.PutUint64(buf[:8], h.SrcIP.Hi)
binary.BigEndian.PutUint64(buf[8:16], h.SrcIP.Lo)
binary.BigEndian.PutUint64(buf[16:24], h.DstIP.Hi)
binary.BigEndian.PutUint64(buf[24:32], h.DstIP.Lo)
src, dst := h.Src.As16(), h.Dst.As16()
copy(buf[:16], src[:])
copy(buf[16:32], dst[:])
binary.BigEndian.PutUint32(buf[32:36], uint32(len(buf)-h.Len()))
buf[36] = 0
buf[37] = 0

View File

@@ -7,18 +7,25 @@ package packet
import (
"encoding/binary"
"fmt"
"net"
"strings"
"inet.af/netaddr"
"tailscale.com/types/strbuilder"
)
// RFC1858: prevent overlapping fragment attacks.
const minFrag = 60 + 20 // max IPv4 header + basic TCP header
type TCPFlag uint8
const (
TCPSyn = 0x02
TCPAck = 0x10
TCPSynAck = TCPSyn | TCPAck
TCPFin TCPFlag = 0x01
TCPSyn TCPFlag = 0x02
TCPRst TCPFlag = 0x04
TCPPsh TCPFlag = 0x08
TCPAck TCPFlag = 0x10
TCPSynAck TCPFlag = TCPSyn | TCPAck
)
// Parsed is a minimal decoding of a packet suitable for use in filters.
@@ -38,64 +45,50 @@ type Parsed struct {
IPVersion uint8
// IPProto is the IP subprotocol (UDP, TCP, etc.). Valid iff IPVersion != 0.
IPProto IPProto
// SrcIP4 is the IPv4 source address. Valid iff IPVersion == 4.
SrcIP4 IP4
// DstIP4 is the IPv4 destination address. Valid iff IPVersion == 4.
DstIP4 IP4
// SrcIP6 is the IPv6 source address. Valid iff IPVersion == 6.
SrcIP6 IP6
// DstIP6 is the IPv6 destination address. Valid iff IPVersion == 6.
DstIP6 IP6
// SrcPort is the TCP/UDP source port. Valid iff IPProto == TCP || IPProto == UDP.
SrcPort uint16
// DstPort is the TCP/UDP source port. Valid iff IPProto == TCP || IPProto == UDP.
DstPort uint16
// SrcIP4 is the source address. Family matches IPVersion. Port is
// valid iff IPProto == TCP || IPProto == UDP.
Src netaddr.IPPort
// DstIP4 is the destination address. Family matches IPVersion.
Dst netaddr.IPPort
// TCPFlags is the packet's TCP flag bigs. Valid iff IPProto == TCP.
TCPFlags uint8
TCPFlags TCPFlag
}
func (p *Parsed) String() string {
switch p.IPVersion {
case 4:
sb := strbuilder.Get()
sb.WriteString(p.IPProto.String())
sb.WriteByte('{')
writeIP4Port(sb, p.SrcIP4, p.SrcPort)
sb.WriteString(" > ")
writeIP4Port(sb, p.DstIP4, p.DstPort)
sb.WriteByte('}')
return sb.String()
case 6:
sb := strbuilder.Get()
sb.WriteString(p.IPProto.String())
sb.WriteByte('{')
writeIP6Port(sb, p.SrcIP6, p.SrcPort)
sb.WriteString(" > ")
writeIP6Port(sb, p.DstIP6, p.DstPort)
sb.WriteByte('}')
return sb.String()
default:
if p.IPVersion != 4 && p.IPVersion != 6 {
return "Unknown{???}"
}
sb := strbuilder.Get()
sb.WriteString(p.IPProto.String())
sb.WriteByte('{')
writeIPPort(sb, p.Src)
sb.WriteString(" > ")
writeIPPort(sb, p.Dst)
sb.WriteByte('}')
return sb.String()
}
func writeIP4Port(sb *strbuilder.Builder, ip IP4, port uint16) {
sb.WriteUint(uint64(byte(ip >> 24)))
sb.WriteByte('.')
sb.WriteUint(uint64(byte(ip >> 16)))
sb.WriteByte('.')
sb.WriteUint(uint64(byte(ip >> 8)))
sb.WriteByte('.')
sb.WriteUint(uint64(byte(ip)))
sb.WriteByte(':')
sb.WriteUint(uint64(port))
}
func writeIP6Port(sb *strbuilder.Builder, ip IP6, port uint16) {
sb.WriteByte('[')
sb.WriteString(ip.Netaddr().String()) // TODO: faster?
sb.WriteString("]:")
sb.WriteUint(uint64(port))
// writeIPPort writes ipp.String() into sb, with fewer allocations.
//
// TODO: make netaddr more efficient in this area, and retire this func.
func writeIPPort(sb *strbuilder.Builder, ipp netaddr.IPPort) {
if ipp.IP.Is4() {
raw := ipp.IP.As4()
sb.WriteUint(uint64(raw[0]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[1]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[2]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[3]))
sb.WriteByte(':')
} else {
sb.WriteByte('[')
sb.WriteString(ipp.IP.String()) // TODO: faster?
sb.WriteString("]:")
}
sb.WriteUint(uint64(ipp.Port))
}
// Decode extracts data from the packet in b into q.
@@ -140,8 +133,8 @@ func (q *Parsed) decode4(b []byte) {
}
// If it's valid IPv4, then the IP addresses are valid
q.SrcIP4 = IP4(binary.BigEndian.Uint32(b[12:16]))
q.DstIP4 = IP4(binary.BigEndian.Uint32(b[16:20]))
q.Src.IP = netaddr.IPv4(b[12], b[13], b[14], b[15])
q.Dst.IP = netaddr.IPv4(b[16], b[17], b[18], b[19])
q.subofs = int((b[0] & 0x0F) << 2)
if q.subofs > q.length {
@@ -183,8 +176,8 @@ func (q *Parsed) decode4(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = 0
q.DstPort = 0
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp4HeaderLength
return
case IGMP:
@@ -196,9 +189,9 @@ func (q *Parsed) decode4(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = binary.BigEndian.Uint16(sub[0:2])
q.DstPort = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = sub[13] & 0x3F
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = TCPFlag(sub[13]) & 0x3F
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
@@ -207,10 +200,14 @@ func (q *Parsed) decode4(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = binary.BigEndian.Uint16(sub[0:2])
q.DstPort = binary.BigEndian.Uint16(sub[2:4])
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.dataofs = q.subofs + udpHeaderLength
return
case TSMP:
// Inter-tailscale messages.
q.dataofs = q.subofs
return
default:
q.IPProto = Unknown
return
@@ -249,10 +246,10 @@ func (q *Parsed) decode6(b []byte) {
return
}
q.SrcIP6.Hi = binary.BigEndian.Uint64(b[8:16])
q.SrcIP6.Lo = binary.BigEndian.Uint64(b[16:24])
q.DstIP6.Hi = binary.BigEndian.Uint64(b[24:32])
q.DstIP6.Lo = binary.BigEndian.Uint64(b[32:40])
// okay to ignore `ok` here, because IPs pulled from packets are
// always well-formed stdlib IPs.
q.Src.IP, _ = netaddr.FromStdIP(net.IP(b[8:24]))
q.Dst.IP, _ = netaddr.FromStdIP(net.IP(b[24:40]))
// We don't support any IPv6 extension headers. Don't try to
// be clever. Therefore, the IP subprotocol always starts at
@@ -276,17 +273,17 @@ func (q *Parsed) decode6(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = 0
q.DstPort = 0
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp6HeaderLength
case TCP:
if len(sub) < tcpHeaderLength {
q.IPProto = Unknown
return
}
q.SrcPort = binary.BigEndian.Uint16(sub[0:2])
q.DstPort = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = sub[13] & 0x3F
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = TCPFlag(sub[13]) & 0x3F
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
@@ -295,9 +292,13 @@ func (q *Parsed) decode6(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = binary.BigEndian.Uint16(sub[0:2])
q.DstPort = binary.BigEndian.Uint16(sub[2:4])
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.dataofs = q.subofs + udpHeaderLength
case TSMP:
// Inter-tailscale messages.
q.dataofs = q.subofs
return
default:
q.IPProto = Unknown
return
@@ -312,8 +313,8 @@ func (q *Parsed) IP4Header() IP4Header {
return IP4Header{
IPID: ipid,
IPProto: q.IPProto,
SrcIP: q.SrcIP4,
DstIP: q.DstIP4,
Src: q.Src.IP,
Dst: q.Dst.IP,
}
}
@@ -334,8 +335,8 @@ func (q *Parsed) UDP4Header() UDP4Header {
}
return UDP4Header{
IP4Header: q.IP4Header(),
SrcPort: q.SrcPort,
DstPort: q.DstPort,
SrcPort: q.Src.Port,
DstPort: q.Dst.Port,
}
}

View File

@@ -12,54 +12,12 @@ import (
"inet.af/netaddr"
)
func mustIP4(s string) IP4 {
ip, err := netaddr.ParseIP(s)
func mustIPPort(s string) netaddr.IPPort {
ipp, err := netaddr.ParseIPPort(s)
if err != nil {
panic(err)
}
return IP4FromNetaddr(ip)
}
func mustIP6(s string) IP6 {
ip, err := netaddr.ParseIP(s)
if err != nil {
panic(err)
}
return IP6FromNetaddr(ip)
}
func TestIP4String(t *testing.T) {
const str = "1.2.3.4"
ip := mustIP4(str)
var got string
allocs := testing.AllocsPerRun(1000, func() {
got = ip.String()
})
if got != str {
t.Errorf("got %q; want %q", got, str)
}
if allocs != 1 {
t.Errorf("allocs = %v; want 1", allocs)
}
}
func TestIP6String(t *testing.T) {
const str = "2607:f8b0:400a:809::200e"
ip := mustIP6(str)
var got string
allocs := testing.AllocsPerRun(1000, func() {
got = ip.String()
})
if got != str {
t.Errorf("got %q; want %q", got, str)
}
if allocs != 2 {
t.Errorf("allocs = %v; want 1", allocs)
}
return ipp
}
var icmp4RequestBuffer = []byte{
@@ -83,10 +41,8 @@ var icmp4RequestDecode = Parsed{
IPVersion: 4,
IPProto: ICMPv4,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 0,
DstPort: 0,
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
var icmp4ReplyBuffer = []byte{
@@ -109,10 +65,8 @@ var icmp4ReplyDecode = Parsed{
IPVersion: 4,
IPProto: ICMPv4,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 0,
DstPort: 0,
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
// ICMPv6 Router Solicitation
@@ -132,8 +86,8 @@ var icmp6PacketDecode = Parsed{
length: len(icmp6PacketBuffer),
IPVersion: 6,
IPProto: ICMPv6,
SrcIP6: mustIP6("fe80::fb57:1dea:9c39:8fb7"),
DstIP6: mustIP6("ff02::2"),
Src: mustIPPort("[fe80::fb57:1dea:9c39:8fb7]:0"),
Dst: mustIPPort("[ff02::2]:0"),
}
// This is a malformed IPv4 packet.
@@ -170,10 +124,8 @@ var tcp4PacketDecode = Parsed{
IPVersion: 4,
IPProto: TCP,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 123,
DstPort: 567,
Src: mustIPPort("1.2.3.4:123"),
Dst: mustIPPort("5.6.7.8:567"),
TCPFlags: TCPSynAck,
}
@@ -198,10 +150,8 @@ var tcp6RequestDecode = Parsed{
IPVersion: 6,
IPProto: TCP,
SrcIP6: mustIP6("2001:559:bc13:5400:1749:4628:3934:e1b"),
DstIP6: mustIP6("2607:f8b0:400a:809::200e"),
SrcPort: 42080,
DstPort: 80,
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:42080"),
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:80"),
TCPFlags: TCPSyn,
}
@@ -226,10 +176,8 @@ var udp4RequestDecode = Parsed{
IPVersion: 4,
IPProto: UDP,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 123,
DstPort: 567,
Src: mustIPPort("1.2.3.4:123"),
Dst: mustIPPort("5.6.7.8:567"),
}
var invalid4RequestBuffer = []byte{
@@ -250,8 +198,8 @@ var invalid4RequestDecode = Parsed{
IPVersion: 4,
IPProto: Unknown,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
var udp6RequestBuffer = []byte{
@@ -275,10 +223,8 @@ var udp6RequestDecode = Parsed{
IPVersion: 6,
IPProto: UDP,
SrcIP6: mustIP6("2001:559:bc13:5400:1749:4628:3934:e1b"),
DstIP6: mustIP6("2607:f8b0:400a:809::200e"),
SrcPort: 54276,
DstPort: 443,
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:54276"),
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:443"),
}
var udp4ReplyBuffer = []byte{
@@ -301,10 +247,8 @@ var udp4ReplyDecode = Parsed{
length: len(udp4ReplyBuffer),
IPProto: UDP,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 567,
DstPort: 123,
Src: mustIPPort("1.2.3.4:567"),
Dst: mustIPPort("5.6.7.8:123"),
}
var igmpPacketBuffer = []byte{
@@ -326,11 +270,42 @@ var igmpPacketDecode = Parsed{
IPVersion: 4,
IPProto: IGMP,
SrcIP4: mustIP4("192.168.1.82"),
DstIP4: mustIP4("224.0.0.251"),
Src: mustIPPort("192.168.1.82:0"),
Dst: mustIPPort("224.0.0.251:0"),
}
func TestParsed(t *testing.T) {
var ipv4TSMPBuffer = []byte{
// IPv4 header:
0x45, 0x00,
0x00, 0x1b, // 20 + 7 bytes total
0x00, 0x00, // ID
0x00, 0x00, // Fragment
0x40, // TTL
byte(TSMP),
0x5f, 0xc3, // header checksum (wrong here)
// source IP:
0x64, 0x5e, 0x0c, 0x0e,
// dest IP:
0x64, 0x4a, 0x46, 0x03,
byte(TSMPTypeRejectedConn),
byte(TCP),
byte(RejectedDueToACLs),
0x00, 123, // src port
0x00, 80, // dst port
}
var ipv4TSMPDecode = Parsed{
b: ipv4TSMPBuffer,
subofs: 20,
dataofs: 20,
length: 27,
IPVersion: 4,
IPProto: TSMP,
Src: mustIPPort("100.94.12.14:0"),
Dst: mustIPPort("100.74.70.3:0"),
}
func TestParsedString(t *testing.T) {
tests := []struct {
name string
qdecode Parsed
@@ -344,6 +319,7 @@ func TestParsed(t *testing.T) {
{"icmp6", icmp6PacketDecode, "ICMPv6{[fe80::fb57:1dea:9c39:8fb7]:0 > [ff02::2]:0}"},
{"igmp", igmpPacketDecode, "IGMP{192.168.1.82:0 > 224.0.0.251:0}"},
{"unknown", unknownPacketDecode, "Unknown{???}"},
{"ipv4_tsmp", ipv4TSMPDecode, "TSMP{100.94.12.14:0 > 100.74.70.3:0}"},
}
for _, tt := range tests {
@@ -380,6 +356,7 @@ func TestDecode(t *testing.T) {
{"igmp", igmpPacketBuffer, igmpPacketDecode},
{"unknown", unknownPacketBuffer, unknownPacketDecode},
{"invalid4", invalid4RequestBuffer, invalid4RequestDecode},
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
}
for _, tt := range tests {
@@ -387,7 +364,7 @@ func TestDecode(t *testing.T) {
var got Parsed
got.Decode(tt.buf)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("mismatch\n got: %#v\nwant: %#v", got, tt.want)
t.Errorf("mismatch\n got: %s %#v\nwant: %s %#v", got.String(), got, tt.want.String(), tt.want)
}
})
}
@@ -472,9 +449,16 @@ func TestMarshalResponse(t *testing.T) {
icmpHeader := icmp4RequestDecode.ICMP4Header()
udpHeader := udp4RequestDecode.UDP4Header()
type HeaderToResponser interface {
Header
// ToResponse transforms the header into one for a response packet.
// For instance, this swaps the source and destination IPs.
ToResponse()
}
tests := []struct {
name string
header Header
header HeaderToResponser
want []byte
}{
{"icmp", &icmpHeader, icmp4ReplyBuffer},

140
net/packet/tsmp.go Normal file
View File

@@ -0,0 +1,140 @@
// 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.
// TSMP is our ICMP-like "Tailscale Message Protocol" for signaling
// Tailscale-specific messages between nodes. It uses IP protocol 99
// (reserved for "any private encryption scheme") within
// Wireguard's normal encryption between peers and never hits the host
// network stack.
package packet
import (
"encoding/binary"
"errors"
"fmt"
"inet.af/netaddr"
"tailscale.com/net/flowtrack"
)
// TailscaleRejectedHeader is a TSMP message that says that one
// Tailscale node has rejected the connection from another. Unlike a
// TCP RST, this includes a reason.
//
// On the wire, after the IP header, it's currently 7 bytes:
// * '!'
// * IPProto byte (IANA protocol number: TCP or UDP)
// * 'A' or 'S' (RejectedDueToACLs, RejectedDueToShieldsUp)
// * srcPort big endian uint16
// * dstPort big endian uint16
//
// In the future it might also accept 16 byte IP flow src/dst IPs
// after the header, if they're different than the IP-level ones.
type TailscaleRejectedHeader struct {
IPSrc netaddr.IP // IPv4 or IPv6 header's src IP
IPDst netaddr.IP // IPv4 or IPv6 header's dst IP
Src netaddr.IPPort // rejected flow's src
Dst netaddr.IPPort // rejected flow's dst
Proto IPProto // proto that was rejected (TCP or UDP)
Reason TailscaleRejectReason // why the connection was rejected
}
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst}
}
func (rh TailscaleRejectedHeader) String() string {
return fmt.Sprintf("TSMP-reject-flow{%s %s > %s}: %s", rh.Proto, rh.Src, rh.Dst, rh.Reason)
}
type TSMPType uint8
const (
TSMPTypeRejectedConn TSMPType = '!'
)
type TailscaleRejectReason byte
const (
RejectedDueToACLs TailscaleRejectReason = 'A'
RejectedDueToShieldsUp TailscaleRejectReason = 'S'
)
func (r TailscaleRejectReason) String() string {
switch r {
case RejectedDueToACLs:
return "acl"
case RejectedDueToShieldsUp:
return "shields"
}
return fmt.Sprintf("0x%02x", byte(r))
}
func (h TailscaleRejectedHeader) Len() int {
var ipHeaderLen int
if h.IPSrc.Is4() {
ipHeaderLen = ip4HeaderLength
} else if h.IPSrc.Is6() {
ipHeaderLen = ip6HeaderLength
}
return ipHeaderLen +
1 + // TSMPType byte
1 + // IPProto byte
1 + // TailscaleRejectReason byte
2*2 // 2 uint16 ports
}
func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if len(buf) > maxPacketLength {
return errLargePacket
}
if h.Src.IP.Is4() {
iph := IP4Header{
IPProto: TSMP,
Src: h.IPSrc,
Dst: h.IPDst,
}
iph.Marshal(buf)
buf = buf[ip4HeaderLength:]
} else if h.Src.IP.Is6() {
iph := IP6Header{
IPProto: TSMP,
Src: h.IPSrc,
Dst: h.IPDst,
}
iph.Marshal(buf)
buf = buf[ip6HeaderLength:]
} else {
return errors.New("bogus src IP")
}
buf[0] = byte(TSMPTypeRejectedConn)
buf[1] = byte(h.Proto)
buf[2] = byte(h.Reason)
binary.BigEndian.PutUint16(buf[3:5], h.Src.Port)
binary.BigEndian.PutUint16(buf[5:7], h.Dst.Port)
return nil
}
// AsTailscaleRejectedHeader parses pp as an incoming rejection
// connection TSMP message.
//
// ok reports whether pp was a valid TSMP rejection packet.
func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok bool) {
p := pp.Payload()
if len(p) < 7 || p[0] != byte(TSMPTypeRejectedConn) {
return
}
return TailscaleRejectedHeader{
Proto: IPProto(p[1]),
Reason: TailscaleRejectReason(p[2]),
IPSrc: pp.Src.IP,
IPDst: pp.Dst.IP,
Src: netaddr.IPPort{IP: pp.Dst.IP, Port: binary.BigEndian.Uint16(p[3:5])},
Dst: netaddr.IPPort{IP: pp.Src.IP, Port: binary.BigEndian.Uint16(p[5:7])},
}, true
}

63
net/packet/tsmp_test.go Normal file
View File

@@ -0,0 +1,63 @@
// 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 packet
import (
"testing"
"inet.af/netaddr"
)
func TestTailscaleRejectedHeader(t *testing.T) {
tests := []struct {
h TailscaleRejectedHeader
wantStr string
}{
{
h: TailscaleRejectedHeader{
IPSrc: netaddr.MustParseIP("5.5.5.5"),
IPDst: netaddr.MustParseIP("1.2.3.4"),
Src: netaddr.MustParseIPPort("1.2.3.4:567"),
Dst: netaddr.MustParseIPPort("5.5.5.5:443"),
Proto: TCP,
Reason: RejectedDueToACLs,
},
wantStr: "TSMP-reject-flow{TCP 1.2.3.4:567 > 5.5.5.5:443}: acl",
},
{
h: TailscaleRejectedHeader{
IPSrc: netaddr.MustParseIP("2::2"),
IPDst: netaddr.MustParseIP("1::1"),
Src: netaddr.MustParseIPPort("[1::1]:567"),
Dst: netaddr.MustParseIPPort("[2::2]:443"),
Proto: UDP,
Reason: RejectedDueToShieldsUp,
},
wantStr: "TSMP-reject-flow{UDP [1::1]:567 > [2::2]:443}: shields",
},
}
for i, tt := range tests {
gotStr := tt.h.String()
if gotStr != tt.wantStr {
t.Errorf("%v. String = %q; want %q", i, gotStr, tt.wantStr)
continue
}
pkt := make([]byte, tt.h.Len())
tt.h.Marshal(pkt)
var p Parsed
p.Decode(pkt)
t.Logf("Parsed: %+v", p)
t.Logf("Parsed: %s", p.String())
back, ok := p.AsTailscaleRejectedHeader()
if !ok {
t.Errorf("%v. %q (%02x) didn't parse back", i, gotStr, pkt)
continue
}
if back != tt.h {
t.Errorf("%v. %q parsed back as %q", i, tt.h, back)
}
}
}

View File

@@ -4,7 +4,32 @@
package tsaddr
import "testing"
import (
"testing"
"inet.af/netaddr"
)
func TestInCrostiniRange(t *testing.T) {
tests := []struct {
ip netaddr.IP
want bool
}{
{netaddr.IPv4(192, 168, 0, 1), false},
{netaddr.IPv4(100, 101, 102, 103), false},
{netaddr.IPv4(100, 115, 92, 0), true},
{netaddr.IPv4(100, 115, 92, 5), true},
{netaddr.IPv4(100, 115, 92, 255), true},
{netaddr.IPv4(100, 115, 93, 40), true},
{netaddr.IPv4(100, 115, 94, 1), false},
}
for _, test := range tests {
if got := ChromeOSVMRange().Contains(test.ip); got != test.want {
t.Errorf("inCrostiniRange(%q) = %v, want %v", test.ip, got, test.want)
}
}
}
func TestChromeOSVMRange(t *testing.T) {
if got, want := ChromeOSVMRange().String(), "100.115.92.0/23"; got != want {

View File

@@ -8,9 +8,8 @@ package portlist
import (
"fmt"
"os/exec"
"strings"
exec "tailscale.com/tempfork/osexec"
)
var osHideWindow func(*exec.Cmd) // non-nil on Windows; see portlist_windows.go

View File

@@ -12,11 +12,10 @@ import (
"fmt"
"log"
"os"
"os/exec"
"strings"
"sync/atomic"
"time"
exec "tailscale.com/tempfork/osexec"
)
// We have to run netstat, which is a bit expensive, so don't do it too often.

View File

@@ -47,6 +47,153 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
}
}
func TestLessThan(t *testing.T) {
tests := []struct {
name string
a, b Port
want bool
}{
{
"Port a < b",
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
Port{Proto: "tcp", Port: 101, Process: "proc1", inode: "inode1"},
true,
},
{
"Port a > b",
Port{Proto: "tcp", Port: 101, Process: "proc1", inode: "inode1"},
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
false,
},
{
"Proto a < b",
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
Port{Proto: "udp", Port: 100, Process: "proc1", inode: "inode1"},
true,
},
{
"Proto a < b",
Port{Proto: "udp", Port: 100, Process: "proc1", inode: "inode1"},
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
false,
},
{
"inode a < b",
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode2"},
true,
},
{
"inode a > b",
Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode2"},
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
false,
},
{
"Process a < b",
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode1"},
true,
},
{
"Process a > b",
Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode1"},
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
false,
},
{
"Port evaluated first",
Port{Proto: "udp", Port: 100, Process: "proc2", inode: "inode2"},
Port{Proto: "tcp", Port: 101, Process: "proc1", inode: "inode1"},
true,
},
{
"Proto evaluated second",
Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode2"},
Port{Proto: "udp", Port: 100, Process: "proc1", inode: "inode1"},
true,
},
{
"inode evaluated third",
Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode1"},
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode2"},
true,
},
{
"Process evaluated fourth",
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode1"},
true,
},
{
"equal",
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"},
false,
},
}
for _, tt := range tests {
got := tt.a.lessThan(&tt.b)
if got != tt.want {
t.Errorf("%s: Equal = %v; want %v", tt.name, got, tt.want)
}
}
}
func TestSameInodes(t *testing.T) {
port1 := Port{Proto: "tcp", Port: 100, Process: "proc", inode: "inode1"}
port2 := Port{Proto: "tcp", Port: 100, Process: "proc", inode: "inode1"}
portProto := Port{Proto: "udp", Port: 100, Process: "proc", inode: "inode1"}
portPort := Port{Proto: "tcp", Port: 101, Process: "proc", inode: "inode1"}
portInode := Port{Proto: "tcp", Port: 100, Process: "proc", inode: "inode2"}
portProcess := Port{Proto: "tcp", Port: 100, Process: "other", inode: "inode1"}
tests := []struct {
name string
a, b List
want bool
}{
{
"identical",
List{port1, port1},
List{port2, port2},
true,
},
{
"proto differs",
List{port1, port1},
List{port2, portProto},
false,
},
{
"port differs",
List{port1, port1},
List{port2, portPort},
false,
},
{
"inode differs",
List{port1, port1},
List{port2, portInode},
false,
},
{
// SameInodes does not check the Process field
"Process differs",
List{port1, port1},
List{port2, portProcess},
true,
},
}
for _, tt := range tests {
got := tt.a.SameInodes(tt.b)
if got != tt.want {
t.Errorf("%s: Equal = %v; want %v", tt.name, got, tt.want)
}
}
}
func BenchmarkGetList(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {

View File

@@ -5,11 +5,11 @@
package portlist
import (
"os/exec"
"syscall"
"time"
"golang.org/x/sys/windows"
exec "tailscale.com/tempfork/osexec"
)
// Forking on Windows is insanely expensive, so don't do it too often.

View File

@@ -64,10 +64,33 @@ func listen(path string, port uint16) (ln net.Listener, _ uint16, err error) {
if err != nil {
return nil, 0, err
}
os.Chmod(path, 0666)
os.Chmod(path, socketPermissionsForOS())
return pipe, 0, err
}
// socketPermissionsForOS returns the permissions to use for the
// tailscaled.sock.
func socketPermissionsForOS() os.FileMode {
if runtime.GOOS == "linux" {
// On Linux, the ipn/ipnserver package looks at the Unix peer creds
// and only permits read-only actions from non-root users, so we want
// this opened up wider.
//
// TODO(bradfitz): unify this all one in place probably, moving some
// of ipnserver (which does much of the "safe" bits) here. Maybe
// instead of net.Listener, we should return a type that returns
// an identity in addition to a net.Conn? (returning a wrapped net.Conn
// would surprise downstream callers probably)
//
// TODO(bradfitz): if OpenBSD and FreeBSD do the equivalent peercreds
// stuff that's in ipn/ipnserver/conn_ucred.go, they should also
// return 0666 here.
return 0666
}
// Otherwise, root only.
return 0600
}
// connectMacOSAppSandbox connects to the Tailscale Network Extension,
// which is necessarily running within the macOS App Sandbox. Our
// little dance to connect a regular user binary to the sandboxed

View File

@@ -10,7 +10,7 @@
check_file() {
got=$1
for year in `seq 2019 2020`; do
for year in `seq 2019 2021`; do
want=$(cat <<EOF
// Copyright (c) $year Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style

24
shell.nix Normal file
View File

@@ -0,0 +1,24 @@
# This is a shell.nix file used to describe the environment that tailscale needs
# for development. This includes a lot of the basic tools that you need in order
# to get started. We hope this file will be useful for users of Nix on macOS or
# Linux.
#
# For more information about this and why this file is useful, see here:
# https://nixos.org/guides/nix-pills/developing-with-nix-shell.html
#
# Also look into direnv: https://direnv.net/, this can make it so that you can
# automatically get your environment set up when you change folders into the
# project.
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
# This specifies the tools that are needed for people to get started with
# development. These tools include:
# - The Go compiler toolchain (and all additional tooling with it)
# - goimports, a robust formatting tool for Go source code
# - gopls, the language server for Go to increase editor integration
# - git, the version control program (used in some scripts)
buildInputs = with pkgs; [
go goimports gopls git
];
}

View File

@@ -33,7 +33,7 @@ func NewWaitGroupChan() *WaitGroupChan {
}
// DoneChan returns a channel that's closed on completion.
func (c *WaitGroupChan) DoneChan() <-chan struct{} { return c.done }
func (wg *WaitGroupChan) DoneChan() <-chan struct{} { return wg.done }
// Add adds delta, which may be negative, to the WaitGroupChan
// counter. If the counter becomes zero, all goroutines blocked on
@@ -46,10 +46,10 @@ func (c *WaitGroupChan) DoneChan() <-chan struct{} { return c.done }
// than zero, may happen at any time. Typically this means the calls
// to Add should execute before the statement creating the goroutine
// or other event to be waited for.
func (c *WaitGroupChan) Add(delta int) {
n := atomic.AddInt64(&c.n, int64(delta))
func (wg *WaitGroupChan) Add(delta int) {
n := atomic.AddInt64(&wg.n, int64(delta))
if n == 0 {
close(c.done)
close(wg.done)
}
}

View File

@@ -14,7 +14,6 @@ import (
"strings"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"go4.org/mem"
"golang.org/x/oauth2"
"inet.af/netaddr"
@@ -23,6 +22,18 @@ import (
"tailscale.com/types/structs"
)
// CurrentMapRequestVersion is the current MapRequest.Version value.
//
// History of versions:
// 3: implicit compression, keep-alives
// 4: opt-in keep-alives via KeepAlive field, opt-in compression via Compress
// 5: 2020-10-19, implies IncludeIPv6, delta Peers/UserProfiles, supports MagicDNS
// 6: 2020-12-07: means MapResponse.PacketFilter nil means unchanged
// 7: 2020-12-15: FilterRule.SrcIPs accepts CIDRs+ranges, doesn't warn about 0.0.0.0/::
// 8: 2020-12-19: client can receive IPv6 addresses and routes if beta enabled server-side
// 9: 2020-12-30: client doesn't auto-add implicit search domains from peers; only DNSConfig.Domains
const CurrentMapRequestVersion = 9
type ID int64
type UserID ID
@@ -112,8 +123,6 @@ type User struct {
Logins []LoginID
Roles []RoleID
Created time.Time
// Note: be sure to update Clone when adding new fields
}
type Login struct {
@@ -138,27 +147,32 @@ type UserProfile struct {
}
type Node struct {
ID NodeID
Name string // DNS
User UserID
ID NodeID
Name string // DNS
// User is the user who created the node. If ACL tags are in
// use for the node then it doesn't reflect the ACL identity
// that the node is running as.
User UserID
// Sharer, if non-zero, is the user who shared this node, if different than User.
Sharer UserID `json:",omitempty"`
Key NodeKey
KeyExpiry time.Time
Machine MachineKey
DiscoKey DiscoKey
Addresses []wgcfg.CIDR // IP addresses of this Node directly
AllowedIPs []wgcfg.CIDR // range of IP addresses to route to this node
Endpoints []string `json:",omitempty"` // IP+port (public via STUN, and local LANs)
DERP string `json:",omitempty"` // DERP-in-IP:port ("127.3.3.40:N") endpoint
Addresses []netaddr.IPPrefix // IP addresses of this Node directly
AllowedIPs []netaddr.IPPrefix // range of IP addresses to route to this node
Endpoints []string `json:",omitempty"` // IP+port (public via STUN, and local LANs)
DERP string `json:",omitempty"` // DERP-in-IP:port ("127.3.3.40:N") endpoint
Hostinfo Hostinfo
Created time.Time
LastSeen *time.Time `json:",omitempty"`
KeepAlive bool // open and keep open a connection to this peer
KeepAlive bool `json:",omitempty"` // open and keep open a connection to this peer
MachineAuthorized bool // TODO(crawshaw): replace with MachineStatus
// NOTE: any new fields containing pointers in this type
// require changes to Node.Clone.
MachineAuthorized bool `json:",omitempty"` // TODO(crawshaw): replace with MachineStatus
}
type MachineStatus int
@@ -275,9 +289,6 @@ type Service struct {
Description string `json:",omitempty"` // text description of service
// TODO(apenwarr): allow advertising services on subnet IPs?
// TODO(apenwarr): add "tags" here for each service?
// NOTE: any new fields containing pointers in this type
// require changes to Hostinfo.Clone.
}
// Hostinfo contains a summary of a Tailscale host.
@@ -287,21 +298,23 @@ type Service struct {
type Hostinfo struct {
// TODO(crawshaw): mark all these fields ",omitempty" when all the
// iOS apps are updated with the latest swift version of this struct.
IPNVersion string // version of this code
FrontendLogID string `json:",omitempty"` // logtail ID of frontend instance
BackendLogID string `json:",omitempty"` // logtail ID of backend instance
OS string // operating system the client runs on (a version.OS value)
OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro")
Hostname string // name of the host the client runs on
GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary)
RoutableIPs []wgcfg.CIDR `json:",omitempty"` // set of IP ranges this client can route
RequestTags []string `json:",omitempty"` // set of ACL tags this node wants to claim
Services []Service `json:",omitempty"` // services advertised by this machine
NetInfo *NetInfo `json:",omitempty"`
IPNVersion string `json:",omitempty"` // version of this code
FrontendLogID string `json:",omitempty"` // logtail ID of frontend instance
BackendLogID string `json:",omitempty"` // logtail ID of backend instance
OS string // operating system the client runs on (a version.OS value)
OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro")
Hostname string // name of the host the client runs on
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user
GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary)
RoutableIPs []netaddr.IPPrefix `json:",omitempty"` // set of IP ranges this client can route
RequestTags []string `json:",omitempty"` // set of ACL tags this node wants to claim
Services []Service `json:",omitempty"` // services advertised by this machine
NetInfo *NetInfo `json:",omitempty"`
// NOTE: any new fields containing pointers in this type
// require changes to Hostinfo.Clone and Hostinfo.Equal.
// require changes to Hostinfo.Equal.
}
// NetInfo contains information about the host's network state.
@@ -353,7 +366,7 @@ type NetInfo struct {
// the control plane.
DERPLatency map[string]float64 `json:",omitempty"`
// Update Clone and BasicallyEqual when adding fields.
// Update BasicallyEqual when adding fields.
}
func (ni *NetInfo) String() string {
@@ -478,11 +491,9 @@ type MapRequest struct {
// we want to signal to the control server that we're capable of something
// different.
//
// History of versions:
// 3: implicit compression, keep-alives
// 4: opt-in keep-alives via KeepAlive field, opt-in compression via Compress
// 5: 2020-10-19, implies IncludeIPv6, DeltaPeers/DeltaUserProfiles, supports MagicDNS
Version int
// For current values and history, see CurrentMapRequestVersion above.
Version int
Compress string // "zstd" or "" (no compression)
KeepAlive bool // whether server should send keep-alives back to us
NodeKey NodeKey
@@ -504,6 +515,10 @@ type MapRequest struct {
// OmitPeers is whether the client is okay with the Peers list
// being omitted in the response. (For example, a client on
// start up using ReadOnly to get the DERP map.)
//
// If OmitPeers is true, Stream is false, and ReadOnly is false,
// then the server will let clients update their endpoints without
// breaking existing long-polling (Stream == true) connections.
OmitPeers bool `json:",omitempty"`
// DebugFlags is a list of strings specifying debugging and
@@ -516,8 +531,8 @@ type MapRequest struct {
// Current DebugFlags values are:
// * "warn-ip-forwarding-off": client is trying to be a subnet
// router but their IP forwarding is broken.
// * "v6-overlay": IPv6 development flag to have control send
// v6 node addrs
// * "minimize-netmap": have control minimize the netmap, removing
// peers that are unreachable per ACLS.
DebugFlags []string `json:",omitempty"`
}
@@ -529,11 +544,11 @@ type PortRange struct {
var PortRangeAny = PortRange{0, 65535}
// NetPortRange represents a single subnet:portrange.
// NetPortRange represents a range of ports that's allowed for one or more IPs.
type NetPortRange struct {
_ structs.Incomparable
IP string // "*" means all
Bits *int // backward compatibility: if missing, means "all" bits
IP string // IP, CIDR, Range, or "*" (same formats as FilterRule.SrcIPs)
Bits *int // deprecated; the old way to turn IP into a CIDR
Ports PortRange
}
@@ -544,19 +559,26 @@ type NetPortRange struct {
// allowed if a source IP is mathces of those CIDRs.
type FilterRule struct {
// SrcIPs are the source IPs/networks to match.
// The special value "*" means to match all.
//
// It may take the following forms:
// * an IP address (IPv4 or IPv6)
// * the string "*" to match everything (both IPv4 & IPv6)
// * a CIDR (e.g. "192.168.0.0/16")
// * a range of two IPs, inclusive, separated by hyphen ("2eff::1-2eff::0800")
SrcIPs []string
// SrcBits values correspond to the SrcIPs above.
// SrcBits is deprecated; it's the old way to specify a CIDR
// prior to MapRequest.Version 7. Its values correspond to the
// SrcIPs above.
//
// If present at the same index, it changes the SrcIP above to
// be a network with /n CIDR bits. If the slice is nil or
// insufficiently long, the default value (for an IPv4
// address) for a position is 32, as if the SrcIPs above were
// a /32 mask. For a "*" SrcIPs value, the corresponding
// SrcBits value is ignored.
// TODO: for IPv6, clarify default bits length.
SrcBits []int
// If an entry of SrcBits is present for the same index as a
// SrcIPs entry, it changes the SrcIP above to be a network
// with /n CIDR bits. If the slice is nil or insufficiently
// long, the default value (for an IPv4 address) for a
// position is 32, as if the SrcIPs above were a /32 mask. For
// a "*" SrcIPs value, the corresponding SrcBits value is
// ignored.
SrcBits []int `json:",omitempty"`
// DstPorts are the port ranges to allow once a source IP
// matches (is in the CIDR described by SrcIPs & SrcBits).
@@ -600,14 +622,15 @@ type MapResponse struct {
// Peers, if non-empty, is the complete list of peers.
// It will be set in the first MapResponse for a long-polled request/response.
// Subsequent responses will be delta-encoded if DeltaPeers was set in the request.
// Subsequent responses will be delta-encoded if MapRequest.Version >= 5 and server
// chooses, in which case Peers will be nil or zero length.
// If Peers is non-empty, PeersChanged and PeersRemoved should
// be ignored (and should be empty).
// Peers is always returned sorted by Node.ID.
Peers []*Node `json:",omitempty"`
// PeersChanged are the Nodes (identified by their ID) that
// have changed or been added since the past update on the
// HTTP response. It's only set if MapRequest.DeltaPeers was true.
// HTTP response. It's not used by the server if MapRequest.Version < 5.
// PeersChanged is always returned sorted by Node.ID.
PeersChanged []*Node `json:",omitempty"`
// PeersRemoved are the NodeIDs that are no longer in the peer list.
@@ -616,18 +639,47 @@ type MapResponse struct {
// DNS is the same as DNSConfig.Nameservers.
//
// TODO(dmytro): should be sent in DNSConfig.Nameservers once clients have updated.
DNS []wgcfg.IP `json:",omitempty"`
// SearchPaths are the same as DNSConfig.Domains.
//
// TODO(dmytro): should be sent in DNSConfig.Domains once clients have updated.
SearchPaths []string `json:",omitempty"`
DNSConfig DNSConfig `json:",omitempty"`
DNS []netaddr.IP `json:",omitempty"`
// ACLs
Domain string
// SearchPaths is the old way to specify DNS search
// domains. Clients should use these values if set, but the
// server will omit this field for clients with
// MapRequest.Version >= 9. Clients should prefer to use
// DNSConfig.Domains instead.
SearchPaths []string `json:",omitempty"`
// DNSConfig contains the DNS settings for the client to use.
//
// TODO(bradfitz): make this a pointer and conditionally sent
// only if changed, like DERPMap, PacketFilter, etc. It's
// small, though.
DNSConfig DNSConfig `json:",omitempty"`
// Domain is the name of the network that this node is
// in. It's either of the form "example.com" (for user
// foo@example.com, for multi-user networks) or
// "foo@gmail.com" (for siloed users on shared email
// providers). Its exact form should not be depended on; new
// forms are coming later.
Domain string
// CollectServices reports whether this node's Tailnet has
// requested that info about services be included in HostInfo.
// If unset, the most recent non-empty MapResponse value in
// the HTTP response stream is used.
CollectServices opt.Bool `json:",omitempty"`
// PacketFilter are the firewall rules.
//
// For MapRequest.Version >= 6, a nil value means the most
// previously streamed non-nil MapResponse.PacketFilter within
// the same HTTP response. A non-nil but empty list always means
// no PacketFilter (that is, to block everything).
PacketFilter []FilterRule
UserProfiles []UserProfile // as of 1.1.541: may be new or updated user profiles only
UserProfiles []UserProfile // as of 1.1.541 (mapver 5): may be new or updated user profiles only
Roles []Role // deprecated; clients should not rely on Roles
// TODO: Groups []Group
// TODO: Capabilities []Capability
@@ -729,6 +781,7 @@ func (n *Node) Equal(n2 *Node) bool {
n.ID == n2.ID &&
n.Name == n2.Name &&
n.User == n2.User &&
n.Sharer == n2.Sharer &&
n.Key == n2.Key &&
n.KeyExpiry.Equal(n2.KeyExpiry) &&
n.Machine == n2.Machine &&
@@ -755,7 +808,7 @@ func eqStrings(a, b []string) bool {
return true
}
func eqCIDRs(a, b []wgcfg.CIDR) bool {
func eqCIDRs(a, b []netaddr.IPPrefix) bool {
if len(a) != len(b) || ((a == nil) != (b == nil)) {
return false
}

View File

@@ -7,7 +7,6 @@
package tailcfg
import (
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
@@ -65,12 +64,13 @@ var _NodeNeedsRegeneration = Node(struct {
ID NodeID
Name string
User UserID
Sharer UserID
Key NodeKey
KeyExpiry time.Time
Machine MachineKey
DiscoKey DiscoKey
Addresses []wgcfg.CIDR
AllowedIPs []wgcfg.CIDR
Addresses []netaddr.IPPrefix
AllowedIPs []netaddr.IPPrefix
Endpoints []string
DERP string
Hostinfo Hostinfo
@@ -105,8 +105,10 @@ var _HostinfoNeedsRegeneration = Hostinfo(struct {
OSVersion string
DeviceModel string
Hostname string
ShieldsUp bool
ShareeNode bool
GoArch string
RoutableIPs []wgcfg.CIDR
RoutableIPs []netaddr.IPPrefix
RequestTags []string
Services []Service
NetInfo *NetInfo

View File

@@ -11,7 +11,8 @@ import (
"testing"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/types/wgkey"
)
func fieldsOf(t reflect.Type) (fields []string) {
@@ -23,18 +24,21 @@ func fieldsOf(t reflect.Type) (fields []string) {
func TestHostinfoEqual(t *testing.T) {
hiHandles := []string{
"IPNVersion", "FrontendLogID", "BackendLogID", "OS", "OSVersion",
"DeviceModel", "Hostname", "GoArch", "RoutableIPs", "RequestTags", "Services",
"NetInfo",
"IPNVersion", "FrontendLogID", "BackendLogID",
"OS", "OSVersion", "DeviceModel", "Hostname",
"ShieldsUp", "ShareeNode",
"GoArch",
"RoutableIPs", "RequestTags",
"Services", "NetInfo",
}
if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) {
t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
have, hiHandles)
}
nets := func(strs ...string) (ns []wgcfg.CIDR) {
nets := func(strs ...string) (ns []netaddr.IPPrefix) {
for _, s := range strs {
n, err := wgcfg.ParseCIDR(s)
n, err := netaddr.ParseIPPrefix(s)
if err != nil {
panic(err)
}
@@ -169,6 +173,11 @@ func TestHostinfoEqual(t *testing.T) {
&Hostinfo{Services: []Service{Service{Proto: TCP, Port: 1234, Description: "foo"}}},
true,
},
{
&Hostinfo{ShareeNode: true},
&Hostinfo{},
false,
},
}
for i, tt := range tests {
got := tt.a.Equal(tt.b)
@@ -179,15 +188,20 @@ func TestHostinfoEqual(t *testing.T) {
}
func TestNodeEqual(t *testing.T) {
nodeHandles := []string{"ID", "Name", "User", "Key", "KeyExpiry", "Machine", "DiscoKey", "Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo", "Created", "LastSeen", "KeepAlive", "MachineAuthorized"}
nodeHandles := []string{
"ID", "Name", "User", "Sharer",
"Key", "KeyExpiry", "Machine", "DiscoKey",
"Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo",
"Created", "LastSeen", "KeepAlive", "MachineAuthorized",
}
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
have, nodeHandles)
}
newPublicKey := func(t *testing.T) wgcfg.Key {
newPublicKey := func(t *testing.T) wgkey.Key {
t.Helper()
k, err := wgcfg.NewPrivateKey()
k, err := wgkey.NewPrivate()
if err != nil {
t.Fatal(err)
}
@@ -256,23 +270,23 @@ func TestNodeEqual(t *testing.T) {
true,
},
{
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []netaddr.IPPrefix{}},
&Node{Addresses: nil},
false,
},
{
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []netaddr.IPPrefix{}},
&Node{Addresses: []netaddr.IPPrefix{}},
true,
},
{
&Node{AllowedIPs: []wgcfg.CIDR{}},
&Node{AllowedIPs: []netaddr.IPPrefix{}},
&Node{AllowedIPs: nil},
false,
},
{
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []netaddr.IPPrefix{}},
&Node{Addresses: []netaddr.IPPrefix{}},
true,
},
{
@@ -421,8 +435,8 @@ func TestCloneNode(t *testing.T) {
}{
{"nil_fields", &Node{}},
{"zero_fields", &Node{
Addresses: make([]wgcfg.CIDR, 0),
AllowedIPs: make([]wgcfg.CIDR, 0),
Addresses: make([]netaddr.IPPrefix, 0),
AllowedIPs: make([]netaddr.IPPrefix, 0),
Endpoints: make([]string, 0),
}},
}

View File

@@ -1,47 +0,0 @@
This is a temporary fork of Go 1.13's os/exec package,
to work around https://github.com/golang/go/issues/36644.
The main modification (outside of removing some tests that require
internal-only packages to run) is:
```
commit 3c66be240f1ee1f1b5f03bed79eb0d9f8c08965a
Author: Avery Pennarun <apenwarr@gmail.com>
Date: Sun Jan 19 03:17:30 2020 -0500
Cmd.Wait(): handle EINTR return code from os.Process.Wait().
This is probably not actually the correct fix; most likely
os.Process.Wait() itself should be fixed to retry on EINTR so that it
never leaks out of that function. But if we're going to patch a
particular module, it's safer to patch a higher-level one like os/exec
rather than the os module itself.
diff --git a/exec.go b/exec.go
index 17ef003e..5375e673 100644
--- a/exec.go
+++ b/exec.go
@@ -498,7 +498,21 @@ func (c *Cmd) Wait() error {
}
c.finished = true
- state, err := c.Process.Wait()
+ var err error
+ var state *os.ProcessState
+ for {
+ state, err = c.Process.Wait()
+ if err != nil {
+ xe, ok := err.(*os.SyscallError)
+ if ok {
+ if xe.Unwrap() == syscall.EINTR {
+ // temporary error, retry wait syscall
+ continue
+ }
+ }
+ }
+ break
+ }
if c.waitDone != nil {
close(c.waitDone)
}
```

View File

@@ -1,23 +0,0 @@
// Copyright 2019 The Go 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 exec
import (
"testing"
)
func BenchmarkExecHostname(b *testing.B) {
b.ReportAllocs()
path, err := LookPath("hostname")
if err != nil {
b.Fatalf("could not find hostname: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := Command(path).Run(); err != nil {
b.Fatalf("hostname: %v", err)
}
}
}

View File

@@ -1,39 +0,0 @@
// Copyright 2017 The Go 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 exec
import (
"reflect"
"testing"
)
func TestDedupEnv(t *testing.T) {
tests := []struct {
noCase bool
in []string
want []string
}{
{
noCase: true,
in: []string{"k1=v1", "k2=v2", "K1=v3"},
want: []string{"K1=v3", "k2=v2"},
},
{
noCase: false,
in: []string{"k1=v1", "K1=V2", "k1=v3"},
want: []string{"k1=v3", "K1=V2"},
},
{
in: []string{"=a", "=b", "foo", "bar"},
want: []string{"=b", "foo", "bar"},
},
}
for _, tt := range tests {
got := dedupEnvCase(tt.noCase, tt.in)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Dedup(%v, %q) = %q; want %q", tt.noCase, tt.in, got, tt.want)
}
}
}

View File

@@ -1,156 +0,0 @@
// Copyright 2012 The Go 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 exec_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
"time"
)
func ExampleLookPath() {
path, err := exec.LookPath("fortune")
if err != nil {
log.Fatal("installing fortune is in your future")
}
fmt.Printf("fortune is available at %s\n", path)
}
func ExampleCommand() {
cmd := exec.Command("tr", "a-z", "A-Z")
cmd.Stdin = strings.NewReader("some input")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("in all caps: %q\n", out.String())
}
func ExampleCommand_environment() {
cmd := exec.Command("prog")
cmd.Env = append(os.Environ(),
"FOO=duplicate_value", // ignored
"FOO=actual_value", // this value is used
)
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
func ExampleCmd_Output() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}
func ExampleCmd_Run() {
cmd := exec.Command("sleep", "1")
log.Printf("Running command and waiting for it to finish...")
err := cmd.Run()
log.Printf("Command finished with error: %v", err)
}
func ExampleCmd_Start() {
cmd := exec.Command("sleep", "5")
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)
}
func ExampleCmd_StdoutPipe() {
cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
var person struct {
Name string
Age int
}
if err := json.NewDecoder(stdout).Decode(&person); err != nil {
log.Fatal(err)
}
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
fmt.Printf("%s is %d years old\n", person.Name, person.Age)
}
func ExampleCmd_StdinPipe() {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
func ExampleCmd_StderrPipe() {
cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
stderr, err := cmd.StderrPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
slurp, _ := ioutil.ReadAll(stderr)
fmt.Printf("%s\n", slurp)
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
}
func ExampleCmd_CombinedOutput() {
cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", stdoutStderr)
}
func ExampleCommandContext() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
// This will fail after 100 milliseconds. The 5 second sleep
// will be interrupted.
}
}

View File

@@ -1,797 +0,0 @@
// Copyright 2009 The Go 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 exec runs external commands. It wraps os.StartProcess to make it
// easier to remap stdin and stdout, connect I/O with pipes, and do other
// adjustments.
//
// Unlike the "system" library call from C and other languages, the
// os/exec package intentionally does not invoke the system shell and
// does not expand any glob patterns or handle other expansions,
// pipelines, or redirections typically done by shells. The package
// behaves more like C's "exec" family of functions. To expand glob
// patterns, either call the shell directly, taking care to escape any
// dangerous input, or use the path/filepath package's Glob function.
// To expand environment variables, use package os's ExpandEnv.
//
// Note that the examples in this package assume a Unix system.
// They may not run on Windows, and they do not run in the Go Playground
// used by golang.org and godoc.org.
package exec
import (
"bytes"
"context"
"errors"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
)
// Error is returned by LookPath when it fails to classify a file as an
// executable.
type Error struct {
// Name is the file name for which the error occurred.
Name string
// Err is the underlying error.
Err error
}
func (e *Error) Error() string {
return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error()
}
func (e *Error) Unwrap() error { return e.Err }
// Cmd represents an external command being prepared or run.
//
// A Cmd cannot be reused after calling its Run, Output or CombinedOutput
// methods.
type Cmd struct {
// Path is the path of the command to run.
//
// This is the only field that must be set to a non-zero
// value. If Path is relative, it is evaluated relative
// to Dir.
Path string
// Args holds command line arguments, including the command as Args[0].
// If the Args field is empty or nil, Run uses {Path}.
//
// In typical use, both Path and Args are set by calling Command.
Args []string
// Env specifies the environment of the process.
// Each entry is of the form "key=value".
// If Env is nil, the new process uses the current process's
// environment.
// If Env contains duplicate environment keys, only the last
// value in the slice for each duplicate key is used.
// As a special case on Windows, SYSTEMROOT is always added if
// missing and not explicitly set to the empty string.
Env []string
// Dir specifies the working directory of the command.
// If Dir is the empty string, Run runs the command in the
// calling process's current directory.
Dir string
// Stdin specifies the process's standard input.
//
// If Stdin is nil, the process reads from the null device (os.DevNull).
//
// If Stdin is an *os.File, the process's standard input is connected
// directly to that file.
//
// Otherwise, during the execution of the command a separate
// goroutine reads from Stdin and delivers that data to the command
// over a pipe. In this case, Wait does not complete until the goroutine
// stops copying, either because it has reached the end of Stdin
// (EOF or a read error) or because writing to the pipe returned an error.
Stdin io.Reader
// Stdout and Stderr specify the process's standard output and error.
//
// If either is nil, Run connects the corresponding file descriptor
// to the null device (os.DevNull).
//
// If either is an *os.File, the corresponding output from the process
// is connected directly to that file.
//
// Otherwise, during the execution of the command a separate goroutine
// reads from the process over a pipe and delivers that data to the
// corresponding Writer. In this case, Wait does not complete until the
// goroutine reaches EOF or encounters an error.
//
// If Stdout and Stderr are the same writer, and have a type that can
// be compared with ==, at most one goroutine at a time will call Write.
Stdout io.Writer
Stderr io.Writer
// ExtraFiles specifies additional open files to be inherited by the
// new process. It does not include standard input, standard output, or
// standard error. If non-nil, entry i becomes file descriptor 3+i.
//
// ExtraFiles is not supported on Windows.
ExtraFiles []*os.File
// SysProcAttr holds optional, operating system-specific attributes.
// Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
SysProcAttr *syscall.SysProcAttr
// Process is the underlying process, once started.
Process *os.Process
// ProcessState contains information about an exited process,
// available after a call to Wait or Run.
ProcessState *os.ProcessState
ctx context.Context // nil means none
lookPathErr error // LookPath error, if any.
finished bool // when Wait was called
childFiles []*os.File
closeAfterStart []io.Closer
closeAfterWait []io.Closer
goroutine []func() error
errch chan error // one send per goroutine
waitDone chan struct{}
}
// Command returns the Cmd struct to execute the named program with
// the given arguments.
//
// It sets only the Path and Args in the returned structure.
//
// If name contains no path separators, Command uses LookPath to
// resolve name to a complete path if possible. Otherwise it uses name
// directly as Path.
//
// The returned Cmd's Args field is constructed from the command name
// followed by the elements of arg, so arg should not include the
// command name itself. For example, Command("echo", "hello").
// Args[0] is always name, not the possibly resolved Path.
//
// On Windows, processes receive the whole command line as a single string
// and do their own parsing. Command combines and quotes Args into a command
// line string with an algorithm compatible with applications using
// CommandLineToArgvW (which is the most common way). Notable exceptions are
// msiexec.exe and cmd.exe (and thus, all batch files), which have a different
// unquoting algorithm. In these or other similar cases, you can do the
// quoting yourself and provide the full command line in SysProcAttr.CmdLine,
// leaving Args empty.
func Command(name string, arg ...string) *Cmd {
cmd := &Cmd{
Path: name,
Args: append([]string{name}, arg...),
}
if filepath.Base(name) == name {
if lp, err := LookPath(name); err != nil {
cmd.lookPathErr = err
} else {
cmd.Path = lp
}
}
return cmd
}
// CommandContext is like Command but includes a context.
//
// The provided context is used to kill the process (by calling
// os.Process.Kill) if the context becomes done before the command
// completes on its own.
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd {
if ctx == nil {
panic("nil Context")
}
cmd := Command(name, arg...)
cmd.ctx = ctx
return cmd
}
// String returns a human-readable description of c.
// It is intended only for debugging.
// In particular, it is not suitable for use as input to a shell.
// The output of String may vary across Go releases.
func (c *Cmd) String() string {
if c.lookPathErr != nil {
// failed to resolve path; report the original requested path (plus args)
return strings.Join(c.Args, " ")
}
// report the exact executable path (plus args)
b := new(strings.Builder)
b.WriteString(c.Path)
for _, a := range c.Args[1:] {
b.WriteByte(' ')
b.WriteString(a)
}
return b.String()
}
// interfaceEqual protects against panics from doing equality tests on
// two interfaces with non-comparable underlying types.
func interfaceEqual(a, b interface{}) bool {
defer func() {
recover()
}()
return a == b
}
func (c *Cmd) envv() []string {
if c.Env != nil {
return c.Env
}
return os.Environ()
}
func (c *Cmd) argv() []string {
if len(c.Args) > 0 {
return c.Args
}
return []string{c.Path}
}
// skipStdinCopyError optionally specifies a function which reports
// whether the provided stdin copy error should be ignored.
// It is non-nil everywhere but Plan 9, which lacks EPIPE. See exec_posix.go.
var skipStdinCopyError func(error) bool
func (c *Cmd) stdin() (f *os.File, err error) {
if c.Stdin == nil {
f, err = os.Open(os.DevNull)
if err != nil {
return
}
c.closeAfterStart = append(c.closeAfterStart, f)
return
}
if f, ok := c.Stdin.(*os.File); ok {
return f, nil
}
pr, pw, err := os.Pipe()
if err != nil {
return
}
c.closeAfterStart = append(c.closeAfterStart, pr)
c.closeAfterWait = append(c.closeAfterWait, pw)
c.goroutine = append(c.goroutine, func() error {
_, err := io.Copy(pw, c.Stdin)
if skip := skipStdinCopyError; skip != nil && skip(err) {
err = nil
}
if err1 := pw.Close(); err == nil {
err = err1
}
return err
})
return pr, nil
}
func (c *Cmd) stdout() (f *os.File, err error) {
return c.writerDescriptor(c.Stdout)
}
func (c *Cmd) stderr() (f *os.File, err error) {
if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
return c.childFiles[1], nil
}
return c.writerDescriptor(c.Stderr)
}
func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
if w == nil {
f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
if err != nil {
return
}
c.closeAfterStart = append(c.closeAfterStart, f)
return
}
if f, ok := w.(*os.File); ok {
return f, nil
}
pr, pw, err := os.Pipe()
if err != nil {
return
}
c.closeAfterStart = append(c.closeAfterStart, pw)
c.closeAfterWait = append(c.closeAfterWait, pr)
c.goroutine = append(c.goroutine, func() error {
_, err := io.Copy(w, pr)
pr.Close() // in case io.Copy stopped due to write error
return err
})
return pw, nil
}
func (c *Cmd) closeDescriptors(closers []io.Closer) {
for _, fd := range closers {
fd.Close()
}
}
// Run starts the specified command and waits for it to complete.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command starts but does not complete successfully, the error is of
// type *ExitError. Other error types may be returned for other situations.
//
// If the calling goroutine has locked the operating system thread
// with runtime.LockOSThread and modified any inheritable OS-level
// thread state (for example, Linux or Plan 9 name spaces), the new
// process will inherit the caller's thread state.
func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
}
return c.Wait()
}
// lookExtensions finds windows executable by its dir and path.
// It uses LookPath to try appropriate extensions.
// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`.
func lookExtensions(path, dir string) (string, error) {
if filepath.Base(path) == path {
path = filepath.Join(".", path)
}
if dir == "" {
return LookPath(path)
}
if filepath.VolumeName(path) != "" {
return LookPath(path)
}
if len(path) > 1 && os.IsPathSeparator(path[0]) {
return LookPath(path)
}
dirandpath := filepath.Join(dir, path)
// We assume that LookPath will only add file extension.
lp, err := LookPath(dirandpath)
if err != nil {
return "", err
}
ext := strings.TrimPrefix(lp, dirandpath)
return path + ext, nil
}
// Start starts the specified command but does not wait for it to complete.
//
// The Wait method will return the exit code and release associated resources
// once the command exits.
func (c *Cmd) Start() error {
if c.lookPathErr != nil {
c.closeDescriptors(c.closeAfterStart)
c.closeDescriptors(c.closeAfterWait)
return c.lookPathErr
}
if runtime.GOOS == "windows" {
lp, err := lookExtensions(c.Path, c.Dir)
if err != nil {
c.closeDescriptors(c.closeAfterStart)
c.closeDescriptors(c.closeAfterWait)
return err
}
c.Path = lp
}
if c.Process != nil {
return errors.New("exec: already started")
}
if c.ctx != nil {
select {
case <-c.ctx.Done():
c.closeDescriptors(c.closeAfterStart)
c.closeDescriptors(c.closeAfterWait)
return c.ctx.Err()
default:
}
}
c.childFiles = make([]*os.File, 0, 3+len(c.ExtraFiles))
type F func(*Cmd) (*os.File, error)
for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
fd, err := setupFd(c)
if err != nil {
c.closeDescriptors(c.closeAfterStart)
c.closeDescriptors(c.closeAfterWait)
return err
}
c.childFiles = append(c.childFiles, fd)
}
c.childFiles = append(c.childFiles, c.ExtraFiles...)
var err error
c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
Dir: c.Dir,
Files: c.childFiles,
Env: addCriticalEnv(dedupEnv(c.envv())),
Sys: c.SysProcAttr,
})
if err != nil {
c.closeDescriptors(c.closeAfterStart)
c.closeDescriptors(c.closeAfterWait)
return err
}
c.closeDescriptors(c.closeAfterStart)
// Don't allocate the channel unless there are goroutines to fire.
if len(c.goroutine) > 0 {
c.errch = make(chan error, len(c.goroutine))
for _, fn := range c.goroutine {
go func(fn func() error) {
c.errch <- fn()
}(fn)
}
}
if c.ctx != nil {
c.waitDone = make(chan struct{})
go func() {
select {
case <-c.ctx.Done():
c.Process.Kill()
case <-c.waitDone:
}
}()
}
return nil
}
// An ExitError reports an unsuccessful exit by a command.
type ExitError struct {
*os.ProcessState
// Stderr holds a subset of the standard error output from the
// Cmd.Output method if standard error was not otherwise being
// collected.
//
// If the error output is long, Stderr may contain only a prefix
// and suffix of the output, with the middle replaced with
// text about the number of omitted bytes.
//
// Stderr is provided for debugging, for inclusion in error messages.
// Users with other needs should redirect Cmd.Stderr as needed.
Stderr []byte
}
func (e *ExitError) Error() string {
return e.ProcessState.String()
}
// Wait waits for the command to exit and waits for any copying to
// stdin or copying from stdout or stderr to complete.
//
// The command must have been started by Start.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command fails to run or doesn't complete successfully, the
// error is of type *ExitError. Other error types may be
// returned for I/O problems.
//
// If any of c.Stdin, c.Stdout or c.Stderr are not an *os.File, Wait also waits
// for the respective I/O loop copying to or from the process to complete.
//
// Wait releases any resources associated with the Cmd.
func (c *Cmd) Wait() error {
if c.Process == nil {
return errors.New("exec: not started")
}
if c.finished {
return errors.New("exec: Wait was already called")
}
c.finished = true
var err error
var state *os.ProcessState
for {
state, err = c.Process.Wait()
if err != nil {
xe, ok := err.(*os.SyscallError)
if ok {
if xe.Unwrap() == syscall.EINTR {
// temporary error, retry wait syscall
continue
}
}
}
break
}
if c.waitDone != nil {
close(c.waitDone)
}
c.ProcessState = state
var copyError error
for range c.goroutine {
if err := <-c.errch; err != nil && copyError == nil {
copyError = err
}
}
c.closeDescriptors(c.closeAfterWait)
if err != nil {
return err
} else if !state.Success() {
return &ExitError{ProcessState: state}
}
return copyError
}
// Output runs the command and returns its standard output.
// Any returned error will usually be of type *ExitError.
// If c.Stderr was nil, Output populates ExitError.Stderr.
func (c *Cmd) Output() ([]byte, error) {
if c.Stdout != nil {
return nil, errors.New("exec: Stdout already set")
}
var stdout bytes.Buffer
c.Stdout = &stdout
captureErr := c.Stderr == nil
if captureErr {
c.Stderr = &prefixSuffixSaver{N: 32 << 10}
}
err := c.Run()
if err != nil && captureErr {
if ee, ok := err.(*ExitError); ok {
ee.Stderr = c.Stderr.(*prefixSuffixSaver).Bytes()
}
}
return stdout.Bytes(), err
}
// CombinedOutput runs the command and returns its combined standard
// output and standard error.
func (c *Cmd) CombinedOutput() ([]byte, error) {
if c.Stdout != nil {
return nil, errors.New("exec: Stdout already set")
}
if c.Stderr != nil {
return nil, errors.New("exec: Stderr already set")
}
var b bytes.Buffer
c.Stdout = &b
c.Stderr = &b
err := c.Run()
return b.Bytes(), err
}
// StdinPipe returns a pipe that will be connected to the command's
// standard input when the command starts.
// The pipe will be closed automatically after Wait sees the command exit.
// A caller need only call Close to force the pipe to close sooner.
// For example, if the command being run will not exit until standard input
// is closed, the caller must close the pipe.
func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
if c.Stdin != nil {
return nil, errors.New("exec: Stdin already set")
}
if c.Process != nil {
return nil, errors.New("exec: StdinPipe after process started")
}
pr, pw, err := os.Pipe()
if err != nil {
return nil, err
}
c.Stdin = pr
c.closeAfterStart = append(c.closeAfterStart, pr)
wc := &closeOnce{File: pw}
c.closeAfterWait = append(c.closeAfterWait, wc)
return wc, nil
}
type closeOnce struct {
*os.File
once sync.Once
err error
}
func (c *closeOnce) Close() error {
c.once.Do(c.close)
return c.err
}
func (c *closeOnce) close() {
c.err = c.File.Close()
}
// StdoutPipe returns a pipe that will be connected to the command's
// standard output when the command starts.
//
// Wait will close the pipe after seeing the command exit, so most callers
// need not close the pipe themselves; however, an implication is that
// it is incorrect to call Wait before all reads from the pipe have completed.
// For the same reason, it is incorrect to call Run when using StdoutPipe.
// See the example for idiomatic usage.
func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
if c.Stdout != nil {
return nil, errors.New("exec: Stdout already set")
}
if c.Process != nil {
return nil, errors.New("exec: StdoutPipe after process started")
}
pr, pw, err := os.Pipe()
if err != nil {
return nil, err
}
c.Stdout = pw
c.closeAfterStart = append(c.closeAfterStart, pw)
c.closeAfterWait = append(c.closeAfterWait, pr)
return pr, nil
}
// StderrPipe returns a pipe that will be connected to the command's
// standard error when the command starts.
//
// Wait will close the pipe after seeing the command exit, so most callers
// need not close the pipe themselves; however, an implication is that
// it is incorrect to call Wait before all reads from the pipe have completed.
// For the same reason, it is incorrect to use Run when using StderrPipe.
// See the StdoutPipe example for idiomatic usage.
func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
if c.Stderr != nil {
return nil, errors.New("exec: Stderr already set")
}
if c.Process != nil {
return nil, errors.New("exec: StderrPipe after process started")
}
pr, pw, err := os.Pipe()
if err != nil {
return nil, err
}
c.Stderr = pw
c.closeAfterStart = append(c.closeAfterStart, pw)
c.closeAfterWait = append(c.closeAfterWait, pr)
return pr, nil
}
// prefixSuffixSaver is an io.Writer which retains the first N bytes
// and the last N bytes written to it. The Bytes() methods reconstructs
// it with a pretty error message.
type prefixSuffixSaver struct {
N int // max size of prefix or suffix
prefix []byte
suffix []byte // ring buffer once len(suffix) == N
suffixOff int // offset to write into suffix
skipped int64
// TODO(bradfitz): we could keep one large []byte and use part of it for
// the prefix, reserve space for the '... Omitting N bytes ...' message,
// then the ring buffer suffix, and just rearrange the ring buffer
// suffix when Bytes() is called, but it doesn't seem worth it for
// now just for error messages. It's only ~64KB anyway.
}
func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) {
lenp := len(p)
p = w.fill(&w.prefix, p)
// Only keep the last w.N bytes of suffix data.
if overage := len(p) - w.N; overage > 0 {
p = p[overage:]
w.skipped += int64(overage)
}
p = w.fill(&w.suffix, p)
// w.suffix is full now if p is non-empty. Overwrite it in a circle.
for len(p) > 0 { // 0, 1, or 2 iterations.
n := copy(w.suffix[w.suffixOff:], p)
p = p[n:]
w.skipped += int64(n)
w.suffixOff += n
if w.suffixOff == w.N {
w.suffixOff = 0
}
}
return lenp, nil
}
// fill appends up to len(p) bytes of p to *dst, such that *dst does not
// grow larger than w.N. It returns the un-appended suffix of p.
func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) {
if remain := w.N - len(*dst); remain > 0 {
add := minInt(len(p), remain)
*dst = append(*dst, p[:add]...)
p = p[add:]
}
return p
}
func (w *prefixSuffixSaver) Bytes() []byte {
if w.suffix == nil {
return w.prefix
}
if w.skipped == 0 {
return append(w.prefix, w.suffix...)
}
var buf bytes.Buffer
buf.Grow(len(w.prefix) + len(w.suffix) + 50)
buf.Write(w.prefix)
buf.WriteString("\n... omitting ")
buf.WriteString(strconv.FormatInt(w.skipped, 10))
buf.WriteString(" bytes ...\n")
buf.Write(w.suffix[w.suffixOff:])
buf.Write(w.suffix[:w.suffixOff])
return buf.Bytes()
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
// dedupEnv returns a copy of env with any duplicates removed, in favor of
// later values.
// Items not of the normal environment "key=value" form are preserved unchanged.
func dedupEnv(env []string) []string {
return dedupEnvCase(runtime.GOOS == "windows", env)
}
// dedupEnvCase is dedupEnv with a case option for testing.
// If caseInsensitive is true, the case of keys is ignored.
func dedupEnvCase(caseInsensitive bool, env []string) []string {
out := make([]string, 0, len(env))
saw := make(map[string]int, len(env)) // key => index into out
for _, kv := range env {
eq := strings.Index(kv, "=")
if eq < 0 {
out = append(out, kv)
continue
}
k := kv[:eq]
if caseInsensitive {
k = strings.ToLower(k)
}
if dupIdx, isDup := saw[k]; isDup {
out[dupIdx] = kv
continue
}
saw[k] = len(out)
out = append(out, kv)
}
return out
}
// addCriticalEnv adds any critical environment variables that are required
// (or at least almost always required) on the operating system.
// Currently this is only used for Windows.
func addCriticalEnv(env []string) []string {
if runtime.GOOS != "windows" {
return env
}
for _, kv := range env {
eq := strings.Index(kv, "=")
if eq < 0 {
continue
}
k := kv[:eq]
if strings.EqualFold(k, "SYSTEMROOT") {
// We already have it.
return env
}
}
return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT"))
}

View File

@@ -1,24 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9,!windows
package exec
import (
"os"
"syscall"
)
func init() {
skipStdinCopyError = func(err error) bool {
// Ignore EPIPE errors copying to stdin if the program
// completed successfully otherwise.
// See Issue 9173.
pe, ok := err.(*os.PathError)
return ok &&
pe.Op == "write" && pe.Path == "|1" &&
pe.Err == syscall.EPIPE
}
}

View File

@@ -1,23 +0,0 @@
// Copyright 2017 The Go 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 exec
import (
"os"
"syscall"
)
func init() {
skipStdinCopyError = func(err error) bool {
// Ignore ERROR_BROKEN_PIPE and ERROR_NO_DATA errors copying
// to stdin if the program completed successfully otherwise.
// See Issue 20445.
const _ERROR_NO_DATA = syscall.Errno(0xe8)
pe, ok := err.(*os.PathError)
return ok &&
pe.Op == "write" && pe.Path == "|1" &&
(pe.Err == syscall.ERROR_BROKEN_PIPE || pe.Err == _ERROR_NO_DATA)
}
}

View File

@@ -1,61 +0,0 @@
// Copyright 2015 The Go 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 exec
import (
"io"
"testing"
)
func TestPrefixSuffixSaver(t *testing.T) {
tests := []struct {
N int
writes []string
want string
}{
{
N: 2,
writes: nil,
want: "",
},
{
N: 2,
writes: []string{"a"},
want: "a",
},
{
N: 2,
writes: []string{"abc", "d"},
want: "abcd",
},
{
N: 2,
writes: []string{"abc", "d", "e"},
want: "ab\n... omitting 1 bytes ...\nde",
},
{
N: 2,
writes: []string{"ab______________________yz"},
want: "ab\n... omitting 22 bytes ...\nyz",
},
{
N: 2,
writes: []string{"ab_______________________y", "z"},
want: "ab\n... omitting 23 bytes ...\nyz",
},
}
for i, tt := range tests {
w := &prefixSuffixSaver{N: tt.N}
for _, s := range tt.writes {
n, err := io.WriteString(w, s)
if err != nil || n != len(s) {
t.Errorf("%d. WriteString(%q) = %v, %v; want %v, %v", i, s, n, err, len(s), nil)
}
}
if got := string(w.Bytes()); got != tt.want {
t.Errorf("%d. Bytes = %q; want %q", i, got, tt.want)
}
}
}

View File

@@ -1,23 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build js,wasm
package exec
import (
"errors"
)
// ErrNotFound is the error resulting if a path search failed to find an executable file.
var ErrNotFound = errors.New("executable file not found in $PATH")
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
// Wasm can not execute processes, so act as if there are no executables at all.
return "", &Error{file, ErrNotFound}
}

View File

@@ -1,55 +0,0 @@
// Copyright 2011 The Go 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 exec
import (
"errors"
"os"
"path/filepath"
"strings"
)
// ErrNotFound is the error resulting if a path search failed to find an executable file.
var ErrNotFound = errors.New("executable file not found in $path")
func findExecutable(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return nil
}
return os.ErrPermission
}
// LookPath searches for an executable named file in the
// directories named by the path environment variable.
// If file begins with "/", "#", "./", or "../", it is tried
// directly and the path is not consulted.
// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
// skip the path lookup for these prefixes
skip := []string{"/", "#", "./", "../"}
for _, p := range skip {
if strings.HasPrefix(file, p) {
err := findExecutable(file)
if err == nil {
return file, nil
}
return "", &Error{file, err}
}
}
path := os.Getenv("path")
for _, dir := range filepath.SplitList(path) {
path := filepath.Join(dir, file)
if err := findExecutable(path); err == nil {
return path, nil
}
}
return "", &Error{file, ErrNotFound}
}

View File

@@ -1,33 +0,0 @@
// Copyright 2011 The Go 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 exec
import (
"testing"
)
var nonExistentPaths = []string{
"some-non-existent-path",
"non-existent-path/slashed",
}
func TestLookPathNotFound(t *testing.T) {
for _, name := range nonExistentPaths {
path, err := LookPath(name)
if err == nil {
t.Fatalf("LookPath found %q in $PATH", name)
}
if path != "" {
t.Fatalf("LookPath path == %q when err != nil", path)
}
perr, ok := err.(*Error)
if !ok {
t.Fatal("LookPath error is not an exec.Error")
}
if perr.Name != name {
t.Fatalf("want Error name %q, got %q", name, perr.Name)
}
}
}

View File

@@ -1,58 +0,0 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package exec
import (
"errors"
"os"
"path/filepath"
"strings"
)
// ErrNotFound is the error resulting if a path search failed to find an executable file.
var ErrNotFound = errors.New("executable file not found in $PATH")
func findExecutable(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return nil
}
return os.ErrPermission
}
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
// NOTE(rsc): I wish we could use the Plan 9 behavior here
// (only bypass the path if file begins with / or ./ or ../)
// but that would not match all the Unix shells.
if strings.Contains(file, "/") {
err := findExecutable(file)
if err == nil {
return file, nil
}
return "", &Error{file, err}
}
path := os.Getenv("PATH")
for _, dir := range filepath.SplitList(path) {
if dir == "" {
// Unix shell semantics: path element "" means "."
dir = "."
}
path := filepath.Join(dir, file)
if err := findExecutable(path); err == nil {
return path, nil
}
}
return "", &Error{file, ErrNotFound}
}

View File

@@ -1,50 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package exec
import (
"os"
"testing"
)
func TestLookPathUnixEmptyPath(t *testing.T) {
tmp := t.TempDir()
wd, err := os.Getwd()
if err != nil {
t.Fatal("Getwd failed: ", err)
}
err = os.Chdir(tmp)
if err != nil {
t.Fatal("Chdir failed: ", err)
}
defer os.Chdir(wd)
f, err := os.OpenFile("exec_me", os.O_CREATE|os.O_EXCL, 0700)
if err != nil {
t.Fatal("OpenFile failed: ", err)
}
err = f.Close()
if err != nil {
t.Fatal("Close failed: ", err)
}
pathenv := os.Getenv("PATH")
defer os.Setenv("PATH", pathenv)
err = os.Setenv("PATH", "")
if err != nil {
t.Fatal("Setenv failed: ", err)
}
path, err := LookPath("exec_me")
if err == nil {
t.Fatal("LookPath found exec_me in empty $PATH")
}
if path != "" {
t.Fatalf("LookPath path == %q when err != nil", path)
}
}

View File

@@ -1,93 +0,0 @@
// Copyright 2010 The Go 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 exec
import (
"errors"
"os"
"path/filepath"
"strings"
)
// ErrNotFound is the error resulting if a path search failed to find an executable file.
var ErrNotFound = errors.New("executable file not found in %PATH%")
func chkStat(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if d.IsDir() {
return os.ErrPermission
}
return nil
}
func hasExt(file string) bool {
i := strings.LastIndex(file, ".")
if i < 0 {
return false
}
return strings.LastIndexAny(file, `:\/`) < i
}
func findExecutable(file string, exts []string) (string, error) {
if len(exts) == 0 {
return file, chkStat(file)
}
if hasExt(file) {
if chkStat(file) == nil {
return file, nil
}
}
for _, e := range exts {
if f := file + e; chkStat(f) == nil {
return f, nil
}
}
return "", os.ErrNotExist
}
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
// LookPath also uses PATHEXT environment variable to match
// a suitable candidate.
// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
var exts []string
x := os.Getenv(`PATHEXT`)
if x != "" {
for _, e := range strings.Split(strings.ToLower(x), `;`) {
if e == "" {
continue
}
if e[0] != '.' {
e = "." + e
}
exts = append(exts, e)
}
} else {
exts = []string{".com", ".exe", ".bat", ".cmd"}
}
if strings.ContainsAny(file, `:\/`) {
if f, err := findExecutable(file, exts); err == nil {
return f, nil
} else {
return "", &Error{file, err}
}
}
if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
return f, nil
}
path := os.Getenv("path")
for _, dir := range filepath.SplitList(path) {
if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil {
return f, nil
}
}
return "", &Error{file, ErrNotFound}
}

View File

@@ -44,11 +44,12 @@ func AddHandlers(mux *http.ServeMux) {
func Cmdline(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
fmt.Fprint(w, strings.Join(os.Args, "\x00"))
}
func sleep(w http.ResponseWriter, d time.Duration) {
var clientGone <-chan bool
//lint:ignore SA1019 CloseNotifier is deprecated but it functions and this is a temporary fork
if cn, ok := w.(http.CloseNotifier); ok {
clientGone = cn.CloseNotify()
}

View File

@@ -1,11 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package registry
func (k Key) SetValue(name string, valtype uint32, data []byte) error {
return k.setValue(name, valtype, data)
}

View File

@@ -1,8 +0,0 @@
package registry_test
// Tailscale's redo-based build system doesn't know how to skip running Go tests
// in directories that don't contain files for the current OS.
//
// https://github.com/tailscale/corp/issues/293
//
// So this is a dummy file for now to make it happy.

View File

@@ -1,204 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
// Package registry provides access to the Windows registry.
//
// Here is a simple example, opening a registry key and reading a string value from it.
//
// k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
// if err != nil {
// log.Fatal(err)
// }
// defer k.Close()
//
// s, _, err := k.GetStringValue("SystemRoot")
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("Windows system root is %q\n", s)
//
package registry
import (
"io"
"syscall"
"time"
)
const (
// Registry key security and access rights.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878.aspx
// for details.
ALL_ACCESS = 0xf003f
CREATE_LINK = 0x00020
CREATE_SUB_KEY = 0x00004
ENUMERATE_SUB_KEYS = 0x00008
EXECUTE = 0x20019
NOTIFY = 0x00010
QUERY_VALUE = 0x00001
READ = 0x20019
SET_VALUE = 0x00002
WOW64_32KEY = 0x00200
WOW64_64KEY = 0x00100
WRITE = 0x20006
)
// Key is a handle to an open Windows registry key.
// Keys can be obtained by calling OpenKey; there are
// also some predefined root keys such as CURRENT_USER.
// Keys can be used directly in the Windows API.
type Key syscall.Handle
const (
// Windows defines some predefined root keys that are always open.
// An application can use these keys as entry points to the registry.
// Normally these keys are used in OpenKey to open new keys,
// but they can also be used anywhere a Key is required.
CLASSES_ROOT = Key(syscall.HKEY_CLASSES_ROOT)
CURRENT_USER = Key(syscall.HKEY_CURRENT_USER)
LOCAL_MACHINE = Key(syscall.HKEY_LOCAL_MACHINE)
USERS = Key(syscall.HKEY_USERS)
CURRENT_CONFIG = Key(syscall.HKEY_CURRENT_CONFIG)
PERFORMANCE_DATA = Key(syscall.HKEY_PERFORMANCE_DATA)
)
// Close closes open key k.
func (k Key) Close() error {
return syscall.RegCloseKey(syscall.Handle(k))
}
// WaitChange waits for k to change using RegNotifyChangeKeyValue.
// The subtree parameter is whether subtrees should also be watched.
func (k Key) WaitChange(subtree bool) error {
return regNotifyChangeKeyValue(syscall.Handle(k), subtree, 0, 0, false)
}
// OpenKey opens a new key with path name relative to key k.
// It accepts any open key, including CURRENT_USER and others,
// and returns the new key and an error.
// The access parameter specifies desired access rights to the
// key to be opened.
func OpenKey(k Key, path string, access uint32) (Key, error) {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
var subkey syscall.Handle
err = syscall.RegOpenKeyEx(syscall.Handle(k), p, 0, access, &subkey)
if err != nil {
return 0, err
}
return Key(subkey), nil
}
// OpenRemoteKey opens a predefined registry key on another
// computer pcname. The key to be opened is specified by k, but
// can only be one of LOCAL_MACHINE, PERFORMANCE_DATA or USERS.
// If pcname is "", OpenRemoteKey returns local computer key.
func OpenRemoteKey(pcname string, k Key) (Key, error) {
var err error
var p *uint16
if pcname != "" {
p, err = syscall.UTF16PtrFromString(`\\` + pcname)
if err != nil {
return 0, err
}
}
var remoteKey syscall.Handle
err = regConnectRegistry(p, syscall.Handle(k), &remoteKey)
if err != nil {
return 0, err
}
return Key(remoteKey), nil
}
// ReadSubKeyNames returns the names of subkeys of key k.
// The parameter n controls the number of returned names,
// analogous to the way os.File.Readdirnames works.
func (k Key) ReadSubKeyNames(n int) ([]string, error) {
names := make([]string, 0)
// Registry key size limit is 255 bytes and described there:
// https://msdn.microsoft.com/library/windows/desktop/ms724872.aspx
buf := make([]uint16, 256) //plus extra room for terminating zero byte
loopItems:
for i := uint32(0); ; i++ {
if n > 0 {
if len(names) == n {
return names, nil
}
}
l := uint32(len(buf))
for {
err := syscall.RegEnumKeyEx(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil)
if err == nil {
break
}
if err == syscall.ERROR_MORE_DATA {
// Double buffer size and try again.
l = uint32(2 * len(buf))
buf = make([]uint16, l)
continue
}
if err == _ERROR_NO_MORE_ITEMS {
break loopItems
}
return names, err
}
names = append(names, syscall.UTF16ToString(buf[:l]))
}
if n > len(names) {
return names, io.EOF
}
return names, nil
}
// CreateKey creates a key named path under open key k.
// CreateKey returns the new key and a boolean flag that reports
// whether the key already existed.
// The access parameter specifies the access rights for the key
// to be created.
func CreateKey(k Key, path string, access uint32) (newk Key, openedExisting bool, err error) {
var h syscall.Handle
var d uint32
err = regCreateKeyEx(syscall.Handle(k), syscall.StringToUTF16Ptr(path),
0, nil, _REG_OPTION_NON_VOLATILE, access, nil, &h, &d)
if err != nil {
return 0, false, err
}
return Key(h), d == _REG_OPENED_EXISTING_KEY, nil
}
// DeleteKey deletes the subkey path of key k and its values.
func DeleteKey(k Key, path string) error {
return regDeleteKey(syscall.Handle(k), syscall.StringToUTF16Ptr(path))
}
// A KeyInfo describes the statistics of a key. It is returned by Stat.
type KeyInfo struct {
SubKeyCount uint32
MaxSubKeyLen uint32 // size of the key's subkey with the longest name, in Unicode characters, not including the terminating zero byte
ValueCount uint32
MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte
MaxValueLen uint32 // longest data component among the key's values, in bytes
lastWriteTime syscall.Filetime
}
// ModTime returns the key's last write time.
func (ki *KeyInfo) ModTime() time.Time {
return time.Unix(0, ki.lastWriteTime.Nanoseconds())
}
// Stat retrieves information about the open key k.
func (k Key) Stat() (*KeyInfo, error) {
var ki KeyInfo
err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil,
&ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount,
&ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime)
if err != nil {
return nil, err
}
return &ki, nil
}

View File

@@ -1,9 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build generate
package registry
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall.go

View File

@@ -1,701 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package registry_test
import (
"bytes"
"crypto/rand"
"os"
"syscall"
"testing"
"time"
"unsafe"
"tailscale.com/tempfork/registry"
)
func randKeyName(prefix string) string {
const numbers = "0123456789"
buf := make([]byte, 10)
rand.Read(buf)
for i, b := range buf {
buf[i] = numbers[b%byte(len(numbers))]
}
return prefix + string(buf)
}
func TestReadSubKeyNames(t *testing.T) {
k, err := registry.OpenKey(registry.CLASSES_ROOT, "TypeLib", registry.ENUMERATE_SUB_KEYS)
if err != nil {
t.Fatal(err)
}
defer k.Close()
names, err := k.ReadSubKeyNames(-1)
if err != nil {
t.Fatal(err)
}
var foundStdOle bool
for _, name := range names {
// Every PC has "stdole 2.0 OLE Automation" library installed.
if name == "{00020430-0000-0000-C000-000000000046}" {
foundStdOle = true
}
}
if !foundStdOle {
t.Fatal("could not find stdole 2.0 OLE Automation")
}
}
func TestCreateOpenDeleteKey(t *testing.T) {
k, err := registry.OpenKey(registry.CURRENT_USER, "Software", registry.QUERY_VALUE)
if err != nil {
t.Fatal(err)
}
defer k.Close()
testKName := randKeyName("TestCreateOpenDeleteKey_")
testK, exist, err := registry.CreateKey(k, testKName, registry.CREATE_SUB_KEY)
if err != nil {
t.Fatal(err)
}
defer testK.Close()
if exist {
t.Fatalf("key %q already exists", testKName)
}
testKAgain, exist, err := registry.CreateKey(k, testKName, registry.CREATE_SUB_KEY)
if err != nil {
t.Fatal(err)
}
defer testKAgain.Close()
if !exist {
t.Fatalf("key %q should already exist", testKName)
}
testKOpened, err := registry.OpenKey(k, testKName, registry.ENUMERATE_SUB_KEYS)
if err != nil {
t.Fatal(err)
}
defer testKOpened.Close()
err = registry.DeleteKey(k, testKName)
if err != nil {
t.Fatal(err)
}
testKOpenedAgain, err := registry.OpenKey(k, testKName, registry.ENUMERATE_SUB_KEYS)
if err == nil {
defer testKOpenedAgain.Close()
t.Fatalf("key %q should already been deleted", testKName)
}
if err != registry.ErrNotExist {
t.Fatalf(`unexpected error ("not exist" expected): %v`, err)
}
}
func TestWatch(t *testing.T) {
k, err := registry.OpenKey(registry.CURRENT_USER, "Software", registry.QUERY_VALUE|registry.WRITE)
if err != nil {
t.Fatal(err)
}
defer k.Close()
testKName := randKeyName("TestWatch_")
testK, _, err := registry.CreateKey(k, testKName, registry.CREATE_SUB_KEY|registry.NOTIFY|registry.WRITE)
if err != nil {
t.Fatal(err)
}
defer testK.Close()
timer := time.AfterFunc(100*time.Millisecond, func() {
err := registry.DeleteKey(k, testKName)
t.Logf("DeleteKey: %v", err)
})
defer timer.Stop()
t.Logf("pre-wait")
t0 := time.Now()
err = testK.WaitChange(true)
t.Logf("WaitChange after %v: %v", time.Since(t0).Round(time.Millisecond), err)
}
func equalStringSlice(a, b []string) bool {
if len(a) != len(b) {
return false
}
if a == nil {
return true
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
type ValueTest struct {
Type uint32
Name string
Value interface{}
WillFail bool
}
var ValueTests = []ValueTest{
{Type: registry.SZ, Name: "String1", Value: ""},
{Type: registry.SZ, Name: "String2", Value: "\000", WillFail: true},
{Type: registry.SZ, Name: "String3", Value: "Hello World"},
{Type: registry.SZ, Name: "String4", Value: "Hello World\000", WillFail: true},
{Type: registry.EXPAND_SZ, Name: "ExpString1", Value: ""},
{Type: registry.EXPAND_SZ, Name: "ExpString2", Value: "\000", WillFail: true},
{Type: registry.EXPAND_SZ, Name: "ExpString3", Value: "Hello World"},
{Type: registry.EXPAND_SZ, Name: "ExpString4", Value: "Hello\000World", WillFail: true},
{Type: registry.EXPAND_SZ, Name: "ExpString5", Value: "%PATH%"},
{Type: registry.EXPAND_SZ, Name: "ExpString6", Value: "%NO_SUCH_VARIABLE%"},
{Type: registry.EXPAND_SZ, Name: "ExpString7", Value: "%PATH%;."},
{Type: registry.BINARY, Name: "Binary1", Value: []byte{}},
{Type: registry.BINARY, Name: "Binary2", Value: []byte{1, 2, 3}},
{Type: registry.BINARY, Name: "Binary3", Value: []byte{3, 2, 1, 0, 1, 2, 3}},
{Type: registry.DWORD, Name: "Dword1", Value: uint64(0)},
{Type: registry.DWORD, Name: "Dword2", Value: uint64(1)},
{Type: registry.DWORD, Name: "Dword3", Value: uint64(0xff)},
{Type: registry.DWORD, Name: "Dword4", Value: uint64(0xffff)},
{Type: registry.QWORD, Name: "Qword1", Value: uint64(0)},
{Type: registry.QWORD, Name: "Qword2", Value: uint64(1)},
{Type: registry.QWORD, Name: "Qword3", Value: uint64(0xff)},
{Type: registry.QWORD, Name: "Qword4", Value: uint64(0xffff)},
{Type: registry.QWORD, Name: "Qword5", Value: uint64(0xffffff)},
{Type: registry.QWORD, Name: "Qword6", Value: uint64(0xffffffff)},
{Type: registry.MULTI_SZ, Name: "MultiString1", Value: []string{"a", "b", "c"}},
{Type: registry.MULTI_SZ, Name: "MultiString2", Value: []string{"abc", "", "cba"}},
{Type: registry.MULTI_SZ, Name: "MultiString3", Value: []string{""}},
{Type: registry.MULTI_SZ, Name: "MultiString4", Value: []string{"abcdef"}},
{Type: registry.MULTI_SZ, Name: "MultiString5", Value: []string{"\000"}, WillFail: true},
{Type: registry.MULTI_SZ, Name: "MultiString6", Value: []string{"a\000b"}, WillFail: true},
{Type: registry.MULTI_SZ, Name: "MultiString7", Value: []string{"ab", "\000", "cd"}, WillFail: true},
{Type: registry.MULTI_SZ, Name: "MultiString8", Value: []string{"\000", "cd"}, WillFail: true},
{Type: registry.MULTI_SZ, Name: "MultiString9", Value: []string{"ab", "\000"}, WillFail: true},
}
func setValues(t *testing.T, k registry.Key) {
for _, test := range ValueTests {
var err error
switch test.Type {
case registry.SZ:
err = k.SetStringValue(test.Name, test.Value.(string))
case registry.EXPAND_SZ:
err = k.SetExpandStringValue(test.Name, test.Value.(string))
case registry.MULTI_SZ:
err = k.SetStringsValue(test.Name, test.Value.([]string))
case registry.BINARY:
err = k.SetBinaryValue(test.Name, test.Value.([]byte))
case registry.DWORD:
err = k.SetDWordValue(test.Name, uint32(test.Value.(uint64)))
case registry.QWORD:
err = k.SetQWordValue(test.Name, test.Value.(uint64))
default:
t.Fatalf("unsupported type %d for %s value", test.Type, test.Name)
}
if test.WillFail {
if err == nil {
t.Fatalf("setting %s value %q should fail, but succeeded", test.Name, test.Value)
}
} else {
if err != nil {
t.Fatal(err)
}
}
}
}
func enumerateValues(t *testing.T, k registry.Key) {
names, err := k.ReadValueNames(-1)
if err != nil {
t.Error(err)
return
}
haveNames := make(map[string]bool)
for _, n := range names {
haveNames[n] = false
}
for _, test := range ValueTests {
wantFound := !test.WillFail
_, haveFound := haveNames[test.Name]
if wantFound && !haveFound {
t.Errorf("value %s is not found while enumerating", test.Name)
}
if haveFound && !wantFound {
t.Errorf("value %s is found while enumerating, but expected to fail", test.Name)
}
if haveFound {
delete(haveNames, test.Name)
}
}
for n, v := range haveNames {
t.Errorf("value %s (%v) is found while enumerating, but has not been cretaed", n, v)
}
}
func testErrNotExist(t *testing.T, name string, err error) {
if err == nil {
t.Errorf("%s value should not exist", name)
return
}
if err != registry.ErrNotExist {
t.Errorf("reading %s value should return 'not exist' error, but got: %s", name, err)
return
}
}
func testErrUnexpectedType(t *testing.T, test ValueTest, gottype uint32, err error) {
if err == nil {
t.Errorf("GetXValue(%q) should not succeed", test.Name)
return
}
if err != registry.ErrUnexpectedType {
t.Errorf("reading %s value should return 'unexpected key value type' error, but got: %s", test.Name, err)
return
}
if gottype != test.Type {
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
return
}
}
func testGetStringValue(t *testing.T, k registry.Key, test ValueTest) {
got, gottype, err := k.GetStringValue(test.Name)
if err != nil {
t.Errorf("GetStringValue(%s) failed: %v", test.Name, err)
return
}
if got != test.Value {
t.Errorf("want %s value %q, got %q", test.Name, test.Value, got)
return
}
if gottype != test.Type {
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
return
}
if gottype == registry.EXPAND_SZ {
_, err = registry.ExpandString(got)
if err != nil {
t.Errorf("ExpandString(%s) failed: %v", got, err)
return
}
}
}
func testGetIntegerValue(t *testing.T, k registry.Key, test ValueTest) {
got, gottype, err := k.GetIntegerValue(test.Name)
if err != nil {
t.Errorf("GetIntegerValue(%s) failed: %v", test.Name, err)
return
}
if got != test.Value.(uint64) {
t.Errorf("want %s value %v, got %v", test.Name, test.Value, got)
return
}
if gottype != test.Type {
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
return
}
}
func testGetBinaryValue(t *testing.T, k registry.Key, test ValueTest) {
got, gottype, err := k.GetBinaryValue(test.Name)
if err != nil {
t.Errorf("GetBinaryValue(%s) failed: %v", test.Name, err)
return
}
if !bytes.Equal(got, test.Value.([]byte)) {
t.Errorf("want %s value %v, got %v", test.Name, test.Value, got)
return
}
if gottype != test.Type {
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
return
}
}
func testGetStringsValue(t *testing.T, k registry.Key, test ValueTest) {
got, gottype, err := k.GetStringsValue(test.Name)
if err != nil {
t.Errorf("GetStringsValue(%s) failed: %v", test.Name, err)
return
}
if !equalStringSlice(got, test.Value.([]string)) {
t.Errorf("want %s value %#v, got %#v", test.Name, test.Value, got)
return
}
if gottype != test.Type {
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
return
}
}
func testGetValue(t *testing.T, k registry.Key, test ValueTest, size int) {
if size <= 0 {
return
}
// read data with no buffer
gotsize, gottype, err := k.GetValue(test.Name, nil)
if err != nil {
t.Errorf("GetValue(%s, [%d]byte) failed: %v", test.Name, size, err)
return
}
if gotsize != size {
t.Errorf("want %s value size of %d, got %v", test.Name, size, gotsize)
return
}
if gottype != test.Type {
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
return
}
// read data with short buffer
gotsize, gottype, err = k.GetValue(test.Name, make([]byte, size-1))
if err == nil {
t.Errorf("GetValue(%s, [%d]byte) should fail, but succeeded", test.Name, size-1)
return
}
if err != registry.ErrShortBuffer {
t.Errorf("reading %s value should return 'short buffer' error, but got: %s", test.Name, err)
return
}
if gotsize != size {
t.Errorf("want %s value size of %d, got %v", test.Name, size, gotsize)
return
}
if gottype != test.Type {
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
return
}
// read full data
gotsize, gottype, err = k.GetValue(test.Name, make([]byte, size))
if err != nil {
t.Errorf("GetValue(%s, [%d]byte) failed: %v", test.Name, size, err)
return
}
if gotsize != size {
t.Errorf("want %s value size of %d, got %v", test.Name, size, gotsize)
return
}
if gottype != test.Type {
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
return
}
// check GetValue returns ErrNotExist as required
_, _, err = k.GetValue(test.Name+"_not_there", make([]byte, size))
if err == nil {
t.Errorf("GetValue(%q) should not succeed", test.Name)
return
}
if err != registry.ErrNotExist {
t.Errorf("GetValue(%q) should return 'not exist' error, but got: %s", test.Name, err)
return
}
}
func testValues(t *testing.T, k registry.Key) {
for _, test := range ValueTests {
switch test.Type {
case registry.SZ, registry.EXPAND_SZ:
if test.WillFail {
_, _, err := k.GetStringValue(test.Name)
testErrNotExist(t, test.Name, err)
} else {
testGetStringValue(t, k, test)
_, gottype, err := k.GetIntegerValue(test.Name)
testErrUnexpectedType(t, test, gottype, err)
// Size of utf16 string in bytes is not perfect,
// but correct for current test values.
// Size also includes terminating 0.
testGetValue(t, k, test, (len(test.Value.(string))+1)*2)
}
_, _, err := k.GetStringValue(test.Name + "_string_not_created")
testErrNotExist(t, test.Name+"_string_not_created", err)
case registry.DWORD, registry.QWORD:
testGetIntegerValue(t, k, test)
_, gottype, err := k.GetBinaryValue(test.Name)
testErrUnexpectedType(t, test, gottype, err)
_, _, err = k.GetIntegerValue(test.Name + "_int_not_created")
testErrNotExist(t, test.Name+"_int_not_created", err)
size := 8
if test.Type == registry.DWORD {
size = 4
}
testGetValue(t, k, test, size)
case registry.BINARY:
testGetBinaryValue(t, k, test)
_, gottype, err := k.GetStringsValue(test.Name)
testErrUnexpectedType(t, test, gottype, err)
_, _, err = k.GetBinaryValue(test.Name + "_byte_not_created")
testErrNotExist(t, test.Name+"_byte_not_created", err)
testGetValue(t, k, test, len(test.Value.([]byte)))
case registry.MULTI_SZ:
if test.WillFail {
_, _, err := k.GetStringsValue(test.Name)
testErrNotExist(t, test.Name, err)
} else {
testGetStringsValue(t, k, test)
_, gottype, err := k.GetStringValue(test.Name)
testErrUnexpectedType(t, test, gottype, err)
size := 0
for _, s := range test.Value.([]string) {
size += len(s) + 1 // nil terminated
}
size += 1 // extra nil at the end
size *= 2 // count bytes, not uint16
testGetValue(t, k, test, size)
}
_, _, err := k.GetStringsValue(test.Name + "_strings_not_created")
testErrNotExist(t, test.Name+"_strings_not_created", err)
default:
t.Errorf("unsupported type %d for %s value", test.Type, test.Name)
continue
}
}
}
func testStat(t *testing.T, k registry.Key) {
subk, _, err := registry.CreateKey(k, "subkey", registry.CREATE_SUB_KEY)
if err != nil {
t.Error(err)
return
}
defer subk.Close()
defer registry.DeleteKey(k, "subkey")
ki, err := k.Stat()
if err != nil {
t.Error(err)
return
}
if ki.SubKeyCount != 1 {
t.Error("key must have 1 subkey")
}
if ki.MaxSubKeyLen != 6 {
t.Error("key max subkey name length must be 6")
}
if ki.ValueCount != 24 {
t.Errorf("key must have 24 values, but is %d", ki.ValueCount)
}
if ki.MaxValueNameLen != 12 {
t.Errorf("key max value name length must be 10, but is %d", ki.MaxValueNameLen)
}
if ki.MaxValueLen != 38 {
t.Errorf("key max value length must be 38, but is %d", ki.MaxValueLen)
}
if mt, ct := ki.ModTime(), time.Now(); ct.Sub(mt) > 100*time.Millisecond {
t.Errorf("key mod time is not close to current time: mtime=%v current=%v delta=%v", mt, ct, ct.Sub(mt))
}
}
func deleteValues(t *testing.T, k registry.Key) {
for _, test := range ValueTests {
if test.WillFail {
continue
}
err := k.DeleteValue(test.Name)
if err != nil {
t.Error(err)
continue
}
}
names, err := k.ReadValueNames(-1)
if err != nil {
t.Error(err)
return
}
if len(names) != 0 {
t.Errorf("some values remain after deletion: %v", names)
}
}
func TestValues(t *testing.T) {
softwareK, err := registry.OpenKey(registry.CURRENT_USER, "Software", registry.QUERY_VALUE)
if err != nil {
t.Fatal(err)
}
defer softwareK.Close()
testKName := randKeyName("TestValues_")
k, exist, err := registry.CreateKey(softwareK, testKName, registry.CREATE_SUB_KEY|registry.QUERY_VALUE|registry.SET_VALUE)
if err != nil {
t.Fatal(err)
}
defer k.Close()
if exist {
t.Fatalf("key %q already exists", testKName)
}
defer registry.DeleteKey(softwareK, testKName)
setValues(t, k)
enumerateValues(t, k)
testValues(t, k)
testStat(t, k)
deleteValues(t, k)
}
func TestExpandString(t *testing.T) {
got, err := registry.ExpandString("%PATH%")
if err != nil {
t.Fatal(err)
}
want := os.Getenv("PATH")
if got != want {
t.Errorf("want %q string expanded, got %q", want, got)
}
}
func TestInvalidValues(t *testing.T) {
softwareK, err := registry.OpenKey(registry.CURRENT_USER, "Software", registry.QUERY_VALUE)
if err != nil {
t.Fatal(err)
}
defer softwareK.Close()
testKName := randKeyName("TestInvalidValues_")
k, exist, err := registry.CreateKey(softwareK, testKName, registry.CREATE_SUB_KEY|registry.QUERY_VALUE|registry.SET_VALUE)
if err != nil {
t.Fatal(err)
}
defer k.Close()
if exist {
t.Fatalf("key %q already exists", testKName)
}
defer registry.DeleteKey(softwareK, testKName)
var tests = []struct {
Type uint32
Name string
Data []byte
}{
{registry.DWORD, "Dword1", nil},
{registry.DWORD, "Dword2", []byte{1, 2, 3}},
{registry.QWORD, "Qword1", nil},
{registry.QWORD, "Qword2", []byte{1, 2, 3}},
{registry.QWORD, "Qword3", []byte{1, 2, 3, 4, 5, 6, 7}},
{registry.MULTI_SZ, "MultiString1", nil},
{registry.MULTI_SZ, "MultiString2", []byte{0}},
{registry.MULTI_SZ, "MultiString3", []byte{'a', 'b', 0}},
{registry.MULTI_SZ, "MultiString4", []byte{'a', 0, 0, 'b', 0}},
{registry.MULTI_SZ, "MultiString5", []byte{'a', 0, 0}},
}
for _, test := range tests {
err := k.SetValue(test.Name, test.Type, test.Data)
if err != nil {
t.Fatalf("SetValue for %q failed: %v", test.Name, err)
}
}
for _, test := range tests {
switch test.Type {
case registry.DWORD, registry.QWORD:
value, valType, err := k.GetIntegerValue(test.Name)
if err == nil {
t.Errorf("GetIntegerValue(%q) succeeded. Returns type=%d value=%v", test.Name, valType, value)
}
case registry.MULTI_SZ:
value, valType, err := k.GetStringsValue(test.Name)
if err == nil {
if len(value) != 0 {
t.Errorf("GetStringsValue(%q) succeeded. Returns type=%d value=%v", test.Name, valType, value)
}
}
default:
t.Errorf("unsupported type %d for %s value", test.Type, test.Name)
}
}
}
func TestGetMUIStringValue(t *testing.T) {
if err := registry.LoadRegLoadMUIString(); err != nil {
t.Skip("regLoadMUIString not supported; skipping")
}
if err := procGetDynamicTimeZoneInformation.Find(); err != nil {
t.Skipf("%s not supported; skipping", procGetDynamicTimeZoneInformation.Name)
}
var dtzi DynamicTimezoneinformation
if _, err := GetDynamicTimeZoneInformation(&dtzi); err != nil {
t.Fatal(err)
}
tzKeyName := syscall.UTF16ToString(dtzi.TimeZoneKeyName[:])
timezoneK, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\`+tzKeyName, registry.READ)
if err != nil {
t.Fatal(err)
}
defer timezoneK.Close()
type testType struct {
name string
want string
}
var tests = []testType{
{"MUI_Std", syscall.UTF16ToString(dtzi.StandardName[:])},
}
if dtzi.DynamicDaylightTimeDisabled == 0 {
tests = append(tests, testType{"MUI_Dlt", syscall.UTF16ToString(dtzi.DaylightName[:])})
}
for _, test := range tests {
got, err := timezoneK.GetMUIStringValue(test.name)
if err != nil {
t.Error("GetMUIStringValue:", err)
}
if got != test.want {
t.Errorf("GetMUIStringValue: %s: Got %q, want %q", test.name, got, test.want)
}
}
}
type DynamicTimezoneinformation struct {
Bias int32
StandardName [32]uint16
StandardDate syscall.Systemtime
StandardBias int32
DaylightName [32]uint16
DaylightDate syscall.Systemtime
DaylightBias int32
TimeZoneKeyName [128]uint16
DynamicDaylightTimeDisabled uint8
}
var (
kernel32DLL = syscall.NewLazyDLL("kernel32")
procGetDynamicTimeZoneInformation = kernel32DLL.NewProc("GetDynamicTimeZoneInformation")
)
func GetDynamicTimeZoneInformation(dtzi *DynamicTimezoneinformation) (rc uint32, err error) {
r0, _, e1 := syscall.Syscall(procGetDynamicTimeZoneInformation.Addr(), 1, uintptr(unsafe.Pointer(dtzi)), 0, 0)
rc = uint32(r0)
if rc == 0xffffffff {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}

View File

@@ -1,33 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package registry
import "syscall"
const (
_REG_OPTION_NON_VOLATILE = 0
_REG_CREATED_NEW_KEY = 1
_REG_OPENED_EXISTING_KEY = 2
_ERROR_NO_MORE_ITEMS syscall.Errno = 259
)
func LoadRegLoadMUIString() error {
return procRegLoadMUIStringW.Find()
}
//sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW
//sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW
//sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW
//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW
//sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW
//sys regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW
//sys regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) = advapi32.RegConnectRegistryW
//sys regNotifyChangeKeyValue(key syscall.Handle, watchSubtree bool, notifyFilter uint32, event syscall.Handle, async bool) (regerrno error) = advapi32.RegNotifyChangeKeyValue
//sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW

View File

@@ -1,386 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package registry
import (
"errors"
"io"
"syscall"
"unicode/utf16"
"unsafe"
)
const (
// Registry value types.
NONE = 0
SZ = 1
EXPAND_SZ = 2
BINARY = 3
DWORD = 4
DWORD_BIG_ENDIAN = 5
LINK = 6
MULTI_SZ = 7
RESOURCE_LIST = 8
FULL_RESOURCE_DESCRIPTOR = 9
RESOURCE_REQUIREMENTS_LIST = 10
QWORD = 11
)
var (
// ErrShortBuffer is returned when the buffer was too short for the operation.
ErrShortBuffer = syscall.ERROR_MORE_DATA
// ErrNotExist is returned when a registry key or value does not exist.
ErrNotExist = syscall.ERROR_FILE_NOT_FOUND
// ErrUnexpectedType is returned by Get*Value when the value's type was unexpected.
ErrUnexpectedType = errors.New("unexpected key value type")
)
// GetValue retrieves the type and data for the specified value associated
// with an open key k. It fills up buffer buf and returns the retrieved
// byte count n. If buf is too small to fit the stored value it returns
// ErrShortBuffer error along with the required buffer size n.
// If no buffer is provided, it returns true and actual buffer size n.
// If no buffer is provided, GetValue returns the value's type only.
// If the value does not exist, the error returned is ErrNotExist.
//
// GetValue is a low level function. If value's type is known, use the appropriate
// Get*Value function instead.
func (k Key) GetValue(name string, buf []byte) (n int, valtype uint32, err error) {
pname, err := syscall.UTF16PtrFromString(name)
if err != nil {
return 0, 0, err
}
var pbuf *byte
if len(buf) > 0 {
pbuf = (*byte)(unsafe.Pointer(&buf[0]))
}
l := uint32(len(buf))
err = syscall.RegQueryValueEx(syscall.Handle(k), pname, nil, &valtype, pbuf, &l)
if err != nil {
return int(l), valtype, err
}
return int(l), valtype, nil
}
func (k Key) getValue(name string, buf []byte) (data []byte, valtype uint32, err error) {
p, err := syscall.UTF16PtrFromString(name)
if err != nil {
return nil, 0, err
}
var t uint32
n := uint32(len(buf))
for {
err = syscall.RegQueryValueEx(syscall.Handle(k), p, nil, &t, (*byte)(unsafe.Pointer(&buf[0])), &n)
if err == nil {
return buf[:n], t, nil
}
if err != syscall.ERROR_MORE_DATA {
return nil, 0, err
}
if n <= uint32(len(buf)) {
return nil, 0, err
}
buf = make([]byte, n)
}
}
// GetStringValue retrieves the string value for the specified
// value name associated with an open key k. It also returns the value's type.
// If value does not exist, GetStringValue returns ErrNotExist.
// If value is not SZ or EXPAND_SZ, it will return the correct value
// type and ErrUnexpectedType.
func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) {
data, typ, err2 := k.getValue(name, make([]byte, 64))
if err2 != nil {
return "", typ, err2
}
switch typ {
case SZ, EXPAND_SZ:
default:
return "", typ, ErrUnexpectedType
}
if len(data) == 0 {
return "", typ, nil
}
u := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[: len(data)/2 : len(data)/2]
return syscall.UTF16ToString(u), typ, nil
}
// GetMUIStringValue retrieves the localized string value for
// the specified value name associated with an open key k.
// If the value name doesn't exist or the localized string value
// can't be resolved, GetMUIStringValue returns ErrNotExist.
// GetMUIStringValue panics if the system doesn't support
// regLoadMUIString; use LoadRegLoadMUIString to check if
// regLoadMUIString is supported before calling this function.
func (k Key) GetMUIStringValue(name string) (string, error) {
pname, err := syscall.UTF16PtrFromString(name)
if err != nil {
return "", err
}
buf := make([]uint16, 1024)
var buflen uint32
var pdir *uint16
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path
// Try to resolve the string value using the system directory as
// a DLL search path; this assumes the string value is of the form
// @[path]\dllname,-strID but with no path given, e.g. @tzres.dll,-320.
// This approach works with tzres.dll but may have to be revised
// in the future to allow callers to provide custom search paths.
var s string
s, err = ExpandString("%SystemRoot%\\system32\\")
if err != nil {
return "", err
}
pdir, err = syscall.UTF16PtrFromString(s)
if err != nil {
return "", err
}
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
}
for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed
if buflen <= uint32(len(buf)) {
break // Buffer not growing, assume race; break
}
buf = make([]uint16, buflen)
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
}
if err != nil {
return "", err
}
return syscall.UTF16ToString(buf), nil
}
// ExpandString expands environment-variable strings and replaces
// them with the values defined for the current user.
// Use ExpandString to expand EXPAND_SZ strings.
func ExpandString(value string) (string, error) {
if value == "" {
return "", nil
}
p, err := syscall.UTF16PtrFromString(value)
if err != nil {
return "", err
}
r := make([]uint16, 100)
for {
n, err := expandEnvironmentStrings(p, &r[0], uint32(len(r)))
if err != nil {
return "", err
}
if n <= uint32(len(r)) {
return syscall.UTF16ToString(r[:n]), nil
}
r = make([]uint16, n)
}
}
// GetStringsValue retrieves the []string value for the specified
// value name associated with an open key k. It also returns the value's type.
// If value does not exist, GetStringsValue returns ErrNotExist.
// If value is not MULTI_SZ, it will return the correct value
// type and ErrUnexpectedType.
func (k Key) GetStringsValue(name string) (val []string, valtype uint32, err error) {
data, typ, err2 := k.getValue(name, make([]byte, 64))
if err2 != nil {
return nil, typ, err2
}
if typ != MULTI_SZ {
return nil, typ, ErrUnexpectedType
}
if len(data) == 0 {
return nil, typ, nil
}
p := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[: len(data)/2 : len(data)/2]
if len(p) == 0 {
return nil, typ, nil
}
if p[len(p)-1] == 0 {
p = p[:len(p)-1] // remove terminating null
}
val = make([]string, 0, 5)
from := 0
for i, c := range p {
if c == 0 {
val = append(val, string(utf16.Decode(p[from:i])))
from = i + 1
}
}
return val, typ, nil
}
// GetIntegerValue retrieves the integer value for the specified
// value name associated with an open key k. It also returns the value's type.
// If value does not exist, GetIntegerValue returns ErrNotExist.
// If value is not DWORD or QWORD, it will return the correct value
// type and ErrUnexpectedType.
func (k Key) GetIntegerValue(name string) (val uint64, valtype uint32, err error) {
data, typ, err2 := k.getValue(name, make([]byte, 8))
if err2 != nil {
return 0, typ, err2
}
switch typ {
case DWORD:
if len(data) != 4 {
return 0, typ, errors.New("DWORD value is not 4 bytes long")
}
var val32 uint32
copy((*[4]byte)(unsafe.Pointer(&val32))[:], data)
return uint64(val32), DWORD, nil
case QWORD:
if len(data) != 8 {
return 0, typ, errors.New("QWORD value is not 8 bytes long")
}
copy((*[8]byte)(unsafe.Pointer(&val))[:], data)
return val, QWORD, nil
default:
return 0, typ, ErrUnexpectedType
}
}
// GetBinaryValue retrieves the binary value for the specified
// value name associated with an open key k. It also returns the value's type.
// If value does not exist, GetBinaryValue returns ErrNotExist.
// If value is not BINARY, it will return the correct value
// type and ErrUnexpectedType.
func (k Key) GetBinaryValue(name string) (val []byte, valtype uint32, err error) {
data, typ, err2 := k.getValue(name, make([]byte, 64))
if err2 != nil {
return nil, typ, err2
}
if typ != BINARY {
return nil, typ, ErrUnexpectedType
}
return data, typ, nil
}
func (k Key) setValue(name string, valtype uint32, data []byte) error {
p, err := syscall.UTF16PtrFromString(name)
if err != nil {
return err
}
if len(data) == 0 {
return regSetValueEx(syscall.Handle(k), p, 0, valtype, nil, 0)
}
return regSetValueEx(syscall.Handle(k), p, 0, valtype, &data[0], uint32(len(data)))
}
// SetDWordValue sets the data and type of a name value
// under key k to value and DWORD.
func (k Key) SetDWordValue(name string, value uint32) error {
return k.setValue(name, DWORD, (*[4]byte)(unsafe.Pointer(&value))[:])
}
// SetQWordValue sets the data and type of a name value
// under key k to value and QWORD.
func (k Key) SetQWordValue(name string, value uint64) error {
return k.setValue(name, QWORD, (*[8]byte)(unsafe.Pointer(&value))[:])
}
func (k Key) setStringValue(name string, valtype uint32, value string) error {
v, err := syscall.UTF16FromString(value)
if err != nil {
return err
}
buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[: len(v)*2 : len(v)*2]
return k.setValue(name, valtype, buf)
}
// SetStringValue sets the data and type of a name value
// under key k to value and SZ. The value must not contain a zero byte.
func (k Key) SetStringValue(name, value string) error {
return k.setStringValue(name, SZ, value)
}
// SetExpandStringValue sets the data and type of a name value
// under key k to value and EXPAND_SZ. The value must not contain a zero byte.
func (k Key) SetExpandStringValue(name, value string) error {
return k.setStringValue(name, EXPAND_SZ, value)
}
// SetStringsValue sets the data and type of a name value
// under key k to value and MULTI_SZ. The value strings
// must not contain a zero byte.
func (k Key) SetStringsValue(name string, value []string) error {
ss := ""
for _, s := range value {
for i := 0; i < len(s); i++ {
if s[i] == 0 {
return errors.New("string cannot have 0 inside")
}
}
ss += s + "\x00"
}
v := utf16.Encode([]rune(ss + "\x00"))
buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[: len(v)*2 : len(v)*2]
return k.setValue(name, MULTI_SZ, buf)
}
// SetBinaryValue sets the data and type of a name value
// under key k to value and BINARY.
func (k Key) SetBinaryValue(name string, value []byte) error {
return k.setValue(name, BINARY, value)
}
// DeleteValue removes a named value from the key k.
func (k Key) DeleteValue(name string) error {
return regDeleteValue(syscall.Handle(k), syscall.StringToUTF16Ptr(name))
}
// ReadValueNames returns the value names of key k.
// The parameter n controls the number of returned names,
// analogous to the way os.File.Readdirnames works.
func (k Key) ReadValueNames(n int) ([]string, error) {
ki, err := k.Stat()
if err != nil {
return nil, err
}
names := make([]string, 0, ki.ValueCount)
buf := make([]uint16, ki.MaxValueNameLen+1) // extra room for terminating null character
loopItems:
for i := uint32(0); ; i++ {
if n > 0 {
if len(names) == n {
return names, nil
}
}
l := uint32(len(buf))
for {
err := regEnumValue(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil)
if err == nil {
break
}
if err == syscall.ERROR_MORE_DATA {
// Double buffer size and try again.
l = uint32(2 * len(buf))
buf = make([]uint16, l)
continue
}
if err == _ERROR_NO_MORE_ITEMS {
break loopItems
}
return names, err
}
names = append(names, syscall.UTF16ToString(buf[:l]))
}
if n > len(names) {
return names, io.EOF
}
return names, nil
}

View File

@@ -1,141 +0,0 @@
// Code generated by 'go generate'; DO NOT EDIT.
package registry
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
procRegCreateKeyExW = modadvapi32.NewProc("RegCreateKeyExW")
procRegDeleteKeyW = modadvapi32.NewProc("RegDeleteKeyW")
procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW")
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW")
procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW")
procRegConnectRegistryW = modadvapi32.NewProc("RegConnectRegistryW")
procRegNotifyChangeKeyValue = modadvapi32.NewProc("RegNotifyChangeKeyValue")
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
)
func regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) {
r0, _, _ := syscall.Syscall9(procRegCreateKeyExW.Addr(), 9, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(reserved), uintptr(unsafe.Pointer(class)), uintptr(options), uintptr(desired), uintptr(unsafe.Pointer(sa)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(disposition)))
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) {
r0, _, _ := syscall.Syscall(procRegDeleteKeyW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(subkey)), 0)
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) {
r0, _, _ := syscall.Syscall6(procRegSetValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(valueName)), uintptr(reserved), uintptr(vtype), uintptr(unsafe.Pointer(buf)), uintptr(bufsize))
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) {
r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen)), 0)
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) {
r0, _, _ := syscall.Syscall(procRegDeleteValueW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(name)), 0)
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) {
r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0)
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) {
r0, _, _ := syscall.Syscall(procRegConnectRegistryW.Addr(), 3, uintptr(unsafe.Pointer(machinename)), uintptr(key), uintptr(unsafe.Pointer(result)))
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func regNotifyChangeKeyValue(key syscall.Handle, watchSubtree bool, notifyFilter uint32, event syscall.Handle, async bool) (regerrno error) {
var _p0 uint32
if watchSubtree {
_p0 = 1
} else {
_p0 = 0
}
var _p1 uint32
if async {
_p1 = 1
} else {
_p1 = 0
}
r0, _, _ := syscall.Syscall6(procRegNotifyChangeKeyValue.Addr(), 5, uintptr(key), uintptr(_p0), uintptr(notifyFilter), uintptr(event), uintptr(_p1), 0)
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) {
r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size))
n = uint32(r0)
if n == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}

View File

@@ -75,13 +75,18 @@ type LogLineTracker struct {
logf logger.Logf
listenFor []string
mu sync.Mutex
seen map[string]bool // format string => false (if not yet seen but wanted) or true (once seen)
mu sync.Mutex
closed bool
seen map[string]bool // format string => false (if not yet seen but wanted) or true (once seen)
}
// Logf logs to its underlying logger and also tracks that the given format pattern has been seen.
func (lt *LogLineTracker) Logf(format string, args ...interface{}) {
lt.mu.Lock()
if lt.closed {
lt.mu.Unlock()
return
}
if v, ok := lt.seen[format]; ok && !v {
lt.seen[format] = true
}
@@ -101,3 +106,10 @@ func (lt *LogLineTracker) Check() []string {
}
return notSeen
}
// Close closes lt. After calling Close, calls to Logf become no-ops.
func (lt *LogLineTracker) Close() {
lt.mu.Lock()
defer lt.mu.Unlock()
lt.closed = true
}

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