Compare commits

...

71 Commits

Author SHA1 Message Date
Josh Bleecher Snyder
7823f665ad net/interfaces: add debugging code for crawshaw 2021-01-14 17:19:06 -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
68 changed files with 3335 additions and 503 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

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.

724
api.md Normal file
View File

@@ -0,0 +1,724 @@
# 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
## 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.
#### `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
}
}
}
```
#### `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"}
```
#### `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" : []
}
```
#### `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
#### `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"
}
}
```
#### `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": ["*:*"] },
]
}
```
#### `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"}
```
### Devices
#### <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,
}
]
}
```
### DNS
#### `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"],
}
```
#### `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,
}
```
#### `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,
}
```
#### `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,
}
```
#### `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"],
}
```
#### `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

@@ -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

@@ -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,16 +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]
if ps.ShareeNode {
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)
}
active := peerActive(ps)
if statusArgs.active && !active {
continue
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
@@ -188,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 ps.DNSName
}
return fmt.Sprintf("- (%q)", 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

@@ -9,7 +9,6 @@ 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/internal/unix from github.com/jsimonetti/rtnetlink
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
@@ -25,10 +24,10 @@ 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
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
@@ -52,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+
@@ -74,6 +74,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/strbuilder from tailscale.com/net/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
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+
@@ -89,7 +90,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
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
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
golang.org/x/crypto/cryptobyte from crypto/ecdsa+

View File

@@ -9,7 +9,7 @@ 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+
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
@@ -28,15 +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
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+
@@ -57,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+
@@ -82,6 +112,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/strbuilder from tailscale.com/net/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
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
@@ -93,13 +124,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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/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
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
@@ -140,6 +172,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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+

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"
)
@@ -147,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

@@ -20,9 +20,15 @@ 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

View File

@@ -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
@@ -201,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 {
@@ -545,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) {
@@ -553,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) {
@@ -636,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()
}
}

View File

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

View File

@@ -107,16 +107,17 @@ 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 wgkey.Private
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 wgkey.Key
@@ -144,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 {
@@ -190,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())
@@ -521,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
@@ -547,7 +565,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
}
request := tailcfg.MapRequest{
Version: 8,
Version: tailcfg.CurrentMapRequestVersion,
KeepAlive: c.keepAlive,
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
DiscoKey: c.discoPubKey,
@@ -555,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
@@ -607,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.
@@ -643,6 +667,7 @@ 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
@@ -724,23 +749,36 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
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: lastParsedPacketFilter,
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 {
@@ -753,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 {

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

@@ -18,6 +18,7 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/wgkey"
"tailscale.com/util/dnsname"
"tailscale.com/wgengine/filter"
)
@@ -38,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
@@ -56,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()
}
@@ -268,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
@@ -319,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
}

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()
}

11
go.mod
View File

@@ -12,24 +12,22 @@ 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/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-20201216134343-bde56ed16391
github.com/klauspost/compress v1.10.10
github.com/kr/pty v1.1.1
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-20201214215404-77d1e9757027
github.com/tailscale/wireguard-go v0.0.0-20201228234719-da0d2727455d
github.com/tailscale/wireguard-go v0.0.0-20210114205708-a1377e83f551
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/intern v0.0.0-20201223061701-969c7e87e7cb // indirect
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
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
@@ -39,7 +37,8 @@ require (
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8
gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6
honnef.co/go/tools v0.1.0
inet.af/netaddr v0.0.0-20201231012616-c5dc91d2a016
inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d
rsc.io/goversion v1.2.0
)

374
go.sum
View File

@@ -1,8 +1,34 @@
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/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/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=
@@ -18,61 +44,170 @@ 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-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/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.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/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/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=
@@ -86,86 +221,157 @@ github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a h1:wMv2mvcHRH4jq
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/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI=
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-20201220011020-db78fad0bebf h1:HuBwLWbDNIh/G72KSImSEx+dnd7FPGFI1e60LMJtLjU=
github.com/tailscale/wireguard-go v0.0.0-20201220011020-db78fad0bebf/go.mod h1:9PbAnF5CAklkURoO0uQhm+YUjDmm9T9oCyTGlCHuTPQ=
github.com/tailscale/wireguard-go v0.0.0-20201228234719-da0d2727455d h1:ha3qx0YBsEYM1VpLoAxVyLsz74H2a/Kv/id+2Bo/WLU=
github.com/tailscale/wireguard-go v0.0.0-20201228234719-da0d2727455d/go.mod h1:FEGDKc5yHNWtTS5ugWnHMNF0d9LlaHv/zQwOrVogo2U=
github.com/tailscale/wireguard-go v0.0.20201119-0.20201228205120-066446d1733a h1:RUJeuZlAm1DT6Mhk9UTsaHrDeDZhPrbKfNsaEtKF6+0=
github.com/tailscale/wireguard-go v0.0.20201119-0.20201228205120-066446d1733a/go.mod h1:UIAx57STfAZOrNVj8QGP2zG3ovWPMTD4DDubFHqMlYI=
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/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=
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/mem v0.0.0-20200706164138-185c595c3ecc/go.mod h1:NEYvpHWemiG/E5UWfaN5QAIGZeT1sa0Z2UNk6oeMb/k=
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-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/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/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.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-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-20200226121028-0de0cce0169b/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/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=
@@ -174,29 +380,51 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2l
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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/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-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-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/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=
@@ -211,20 +439,40 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
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/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-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-20201001230009-b5b87423c93b/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
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=
@@ -232,36 +480,88 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
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/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/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.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/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/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
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-20200810144936-56928fe48a98/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
inet.af/netaddr v0.0.0-20201218162718-658fec415e52/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
inet.af/netaddr v0.0.0-20201223185330-97d366981fac h1:aqMW8vft7VmOIhtQhsTWhAuZzOBGYBv+Otyvwj+VGSU=
inet.af/netaddr v0.0.0-20201223185330-97d366981fac/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o=
inet.af/netaddr v0.0.0-20201224214825-a55841caa437 h1:Li2QBwaT/hU3wE7GdyoqaX+TzIlI+V1zs/CuWrjX8e4=
inet.af/netaddr v0.0.0-20201224214825-a55841caa437/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o=
inet.af/netaddr v0.0.0-20201226233944-2d1876c01610 h1:9Nnw3NS9SL4SlFtBWSdv7onMbdY+B8nflRNZvhgxuMY=
inet.af/netaddr v0.0.0-20201226233944-2d1876c01610/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o=
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-20201231012616-c5dc91d2a016 h1:CEeeAJW60aRKE6gGJC5krs2xC/uM2l8SasvgeDXFN5Q=
inet.af/netaddr v0.0.0-20201231012616-c5dc91d2a016/go.mod h1:lbePDLSB5c45kkUmF7ETNE5X9z/yuQvWJIv1hhb5rFI=
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=
tailscale.com v1.2.10/go.mod h1:JEJiCce3MHtPCTdX2ahLc4tcnxZ7b5etish1Yt0B6+w=
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

@@ -41,12 +41,7 @@ func getVal() []interface{} {
ListenPort: 5,
Peers: []wgcfg.Peer{
{
Endpoints: []wgcfg.Endpoint{
{
Host: "foo",
Port: 5,
},
},
Endpoints: "foo:5",
},
},
},

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
@@ -103,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()

View File

@@ -201,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)
}
@@ -351,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 {
@@ -639,7 +640,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)
}
@@ -1014,16 +1015,18 @@ func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret EngineStatus
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) {
@@ -1123,9 +1126,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{}
}
@@ -1209,20 +1210,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,
}
@@ -1235,32 +1230,13 @@ func (b *LocalBackend) authReconfig() {
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.

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

@@ -26,6 +26,7 @@ func TestLocalLogLines(t *testing.T) {
"[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)
}

View File

@@ -296,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

@@ -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

@@ -6,6 +6,8 @@ package logtail
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
)
@@ -14,12 +16,38 @@ func TestFastShutdown(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
testServ := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {}))
defer testServ.Close()
l := NewLogger(Config{
BaseURL: "http://localhost:1234",
BaseURL: testServ.URL,
}, t.Logf)
l.Shutdown(ctx)
}
func TestUploadMessages(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
uploads := 0
testServ := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uploads += 1
}))
defer testServ.Close()
l := NewLogger(Config{BaseURL: testServ.URL}, t.Logf)
for i := 1; i < 10; i++ {
l.Write([]byte("log line"))
}
l.Shutdown(ctx)
cancel()
if uploads == 0 {
t.Error("no log uploads")
}
}
var sink []byte
func TestLoggerEncodeTextAllocs(t *testing.T) {

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

@@ -6,6 +6,9 @@ package interfaces
import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"go4.org/mem"
@@ -46,8 +49,10 @@ func likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) {
}
defer cmd.Wait()
fmt.Println("netstat output:")
tee := io.TeeReader(stdout, os.Stdout)
var f []mem.RO
lineread.Reader(stdout, func(lineb []byte) error {
lineread.Reader(tee, func(lineb []byte) error {
line := mem.B(lineb)
if !mem.Contains(line, mem.S("default")) {
return nil

View File

@@ -105,6 +105,7 @@ import "C"
import (
"encoding/binary"
"fmt"
"inet.af/netaddr"
)
@@ -116,6 +117,7 @@ func init() {
func likelyHomeRouterIPDarwinSyscall() (ret netaddr.IP, ok bool) {
ip := C.privateGatewayIP()
if ip < 255 {
fmt.Println("privateGatewayIP failure:", ip)
return netaddr.IP{}, false
}
var q [4]byte

View File

@@ -1102,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{}
}
@@ -1119,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
}
}
}
@@ -1129,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) {
@@ -560,3 +578,41 @@ func TestLogConciseReport(t *testing.T) {
})
}
}
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

@@ -17,10 +17,15 @@ import (
// 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.
@@ -46,7 +51,7 @@ type Parsed struct {
// 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 {
@@ -186,7 +191,7 @@ func (q *Parsed) decode4(b []byte) {
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = sub[13] & 0x3F
q.TCPFlags = TCPFlag(sub[13]) & 0x3F
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
@@ -274,7 +279,7 @@ func (q *Parsed) decode6(b []byte) {
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = sub[13] & 0x3F
q.TCPFlags = TCPFlag(sub[13]) & 0x3F
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return

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

@@ -64,7 +64,7 @@ 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, 0600)
return pipe, 0, err
}

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

View File

@@ -22,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
@@ -135,9 +147,17 @@ 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
@@ -471,14 +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, 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
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
@@ -565,7 +580,7 @@ type FilterRule struct {
// position is 32, as if the SrcIPs above were a /32 mask. For
// a "*" SrcIPs value, the corresponding SrcBits value is
// ignored.
SrcBits []int
SrcBits []int `json:",omitempty"`
// DstPorts are the port ranges to allow once a source IP
// matches (is in the CIDR described by SrcIPs & SrcBits).
@@ -627,11 +642,20 @@ type MapResponse struct {
//
// TODO(dmytro): should be sent in DNSConfig.Nameservers once clients have updated.
DNS []netaddr.IP `json:",omitempty"`
// SearchPaths are the same as DNSConfig.Domains.
// 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(dmytro): should be sent in DNSConfig.Domains once clients have updated.
SearchPaths []string `json:",omitempty"`
DNSConfig DNSConfig `json:",omitempty"`
// 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
@@ -641,6 +665,12 @@ type MapResponse struct {
// 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
@@ -753,6 +783,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 &&

View File

@@ -64,6 +64,7 @@ var _NodeNeedsRegeneration = Node(struct {
ID NodeID
Name string
User UserID
Sharer UserID
Key NodeKey
KeyExpiry time.Time
Machine MachineKey

View File

@@ -188,7 +188,12 @@ 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)

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
}

View File

@@ -43,7 +43,7 @@ func registerCommonDebug(mux *http.ServeMux) {
expvar.Publish("counter_uptime_sec", expvar.Func(func() interface{} { return int64(Uptime().Seconds()) }))
mux.Handle("/debug/pprof/", Protected(http.DefaultServeMux)) // to net/http/pprof
mux.Handle("/debug/vars", Protected(http.DefaultServeMux)) // to expvar
mux.Handle("/debug/varz", Protected(http.HandlerFunc(varzHandler)))
mux.Handle("/debug/varz", Protected(http.HandlerFunc(VarzHandler)))
mux.Handle("/debug/gc", Protected(http.HandlerFunc(gcHandler)))
}
@@ -371,7 +371,7 @@ func Error(code int, msg string, err error) HTTPError {
return HTTPError{Code: code, Msg: msg, Err: err}
}
// varzHandler is an HTTP handler to write expvar values into the
// VarzHandler is an HTTP handler to write expvar values into the
// prometheus export format:
//
// https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md
@@ -388,7 +388,7 @@ func Error(code int, msg string, err error) HTTPError {
// is not exported.
//
// This will evolve over time, or perhaps be replaced.
func varzHandler(w http.ResponseWriter, r *http.Request) {
func VarzHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; version=0.0.4")
var dump func(prefix string, kv expvar.KeyValue)

View File

@@ -82,6 +82,15 @@ func (k Private) Public() Public {
return Public(pub)
}
func (k Private) SharedSecret(pub Public) (ss [32]byte) {
apk := (*[32]byte)(&pub)
ask := (*[32]byte)(&k)
//lint:ignore SA1019 Code copied from wireguard-go, we aim for
//minimal changes from it.
curve25519.ScalarMult(&ss, ask, apk)
return ss
}
// NewPublicFromHexMem parses a public key in its hex form, given in m.
// The provided m must be exactly 64 bytes in length.
func NewPublicFromHexMem(m mem.RO) (Public, error) {

View File

@@ -132,7 +132,7 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
logf(format, args...)
case warn:
// For the warning, log the specific format string
logf("[RATE LIMITED] format string \"%s\"", format)
logf("[RATE LIMITED] format string \"%s\" (example: \"%s\")", format, fmt.Sprintf(format, args...))
}
}
}
@@ -179,3 +179,16 @@ func (fn ArgWriter) Format(f fmt.State, _ rune) {
}
var argBufioPool = &sync.Pool{New: func() interface{} { return bufio.NewWriterSize(ioutil.Discard, 1024) }}
// Filtered returns a Logf that silently swallows some log lines.
// Each inbound format and args is evaluated and printed to a string s.
// The original format and args are passed to logf if and only if allow(s) returns true.
func Filtered(logf Logf, allow func(s string) bool) Logf {
return func(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
if !allow(msg) {
return
}
logf(format, args...)
}
}

View File

@@ -45,8 +45,8 @@ func TestRateLimiter(t *testing.T) {
"templated format string no. 0",
"boring string with constant formatting (constant)",
"templated format string no. 1",
"[RATE LIMITED] format string \"boring string with constant formatting %s\"",
"[RATE LIMITED] format string \"templated format string no. %d\"",
"[RATE LIMITED] format string \"boring string with constant formatting %s\" (example: \"boring string with constant formatting (constant)\")",
"[RATE LIMITED] format string \"templated format string no. %d\" (example: \"templated format string no. 2\")",
"Make sure this string makes it through the rest (that are blocked) 4",
"4 shouldn't get filtered.",
}

19
util/dnsname/dnsname.go Normal file
View File

@@ -0,0 +1,19 @@
// 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 dnsname contains string functions for working with DNS names.
package dnsname
import "strings"
// HasSuffix reports whether the provided DNS name ends with the
// component(s) in suffix, ignoring any trailing dots.
//
// If suffix is the empty string, HasSuffix always reports false.
func HasSuffix(name, suffix string) bool {
name = strings.TrimSuffix(name, ".")
suffix = strings.TrimSuffix(suffix, ".")
nameBase := strings.TrimSuffix(name, suffix)
return len(nameBase) < len(name) && strings.HasSuffix(nameBase, ".")
}

View File

@@ -0,0 +1,28 @@
// 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 dnsname
import "testing"
func TestHasSuffix(t *testing.T) {
tests := []struct {
name, suffix string
want bool
}{
{"foo.com", "com", true},
{"foo.com.", "com", true},
{"foo.com.", "com.", true},
{"", "", false},
{"foo.com.", "", false},
{"foo.com.", "o.com", false},
}
for _, tt := range tests {
got := HasSuffix(tt.name, tt.suffix)
if got != tt.want {
t.Errorf("HasSuffix(%q, %q) = %v; want %v", tt.name, tt.suffix, got, tt.want)
}
}
}

View File

@@ -10,7 +10,7 @@ package version
// Long is a full version number for this build, of the form
// "x.y.z-commithash", or "date.yyyymmdd" if no actual version was
// provided.
const Long = "date.20201230"
const Long = "date.20210104"
// Short is a short version number for this build, of the form
// "x.y.z", or "date.yyyymmdd" if no actual version was provided.

View File

@@ -10,9 +10,9 @@ import (
"sync"
"time"
"github.com/golang/groupcache/lru"
"golang.org/x/time/rate"
"inet.af/netaddr"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet"
"tailscale.com/types/logger"
)
@@ -41,17 +41,10 @@ type Filter struct {
state *filterState
}
// tuple is a 4-tuple of source and destination IP and port. It's used
// as a lookup key in filterState.
type tuple struct {
Src netaddr.IPPort
Dst netaddr.IPPort
}
// filterState is a state cache of past seen packets.
type filterState struct {
mu sync.Mutex
lru *lru.Cache // of tuple
lru *flowtrack.Cache // from flowtrack.Tuple -> nil
}
// lruMax is the size of the LRU cache in filterState.
@@ -141,7 +134,7 @@ func New(matches []Match, localNets []netaddr.IPPrefix, shareStateWith *Filter,
state = shareStateWith.state
} else {
state = &filterState{
lru: lru.New(lruMax),
lru: &flowtrack.Cache{MaxEntries: lruMax},
}
}
f := &Filter{
@@ -334,7 +327,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
return Accept, "tcp ok"
}
case packet.UDP:
t := tuple{q.Src, q.Dst}
t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst}
f.state.mu.Lock()
_, ok := f.state.lru.Get(t)
@@ -389,7 +382,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
return Accept, "tcp ok"
}
case packet.UDP:
t := tuple{q.Src, q.Dst}
t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst}
f.state.mu.Lock()
_, ok := f.state.lru.Get(t)
@@ -413,10 +406,10 @@ func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) {
return Accept, "ok out"
}
t := tuple{q.Dst, q.Src}
var ti interface{} = t // allocate once, rather than twice inside mutex
tuple := flowtrack.Tuple{Src: q.Dst, Dst: q.Src} // src/dst reversed
f.state.mu.Lock()
f.state.lru.Add(ti, ti)
f.state.lru.Add(tuple, nil)
f.state.mu.Unlock()
return Accept, "ok out"
}

View File

@@ -414,7 +414,7 @@ func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen in
payload := make([]byte, 12)
// Set the right bit to look like a TCP SYN, if the packet ends up interpreted as TCP
payload[5] = packet.TCPSyn
payload[5] = byte(packet.TCPSyn)
b := packet.Generate(&u, payload) // payload large enough to possibly be TCP
@@ -443,7 +443,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength
payload := make([]byte, 12)
// Set the right bit to look like a TCP SYN, if the packet ends up interpreted as TCP
payload[5] = packet.TCPSyn
payload[5] = byte(packet.TCPSyn)
b := packet.Generate(&u, payload) // payload large enough to possibly be TCP

View File

@@ -5,17 +5,24 @@
package magicsock
import (
"bytes"
"crypto/hmac"
"crypto/subtle"
"encoding/binary"
"errors"
"fmt"
"hash"
"net"
"strings"
"sync"
"time"
"github.com/tailscale/wireguard-go/conn"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tai64n"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/crypto/blake2s"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/poly1305"
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
"tailscale.com/types/key"
@@ -70,17 +77,58 @@ func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs string) (conn.End
return a, nil
}
func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr) conn.Endpoint {
func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte) conn.Endpoint {
// Pre-disco: look up their addrSet.
if as, ok := c.addrsByUDP[ipp]; ok {
as.updateDst(addr)
return as
}
// Pre-disco: the peer that sent this packet has roamed beyond
// the knowledge provided by the control server. If the
// packet is valid wireguard will call UpdateDst on the
// original endpoint using this addr.
return (*singleEndpoint)(addr)
// We don't know who this peer is. It's possible that it's one of
// our legitimate peers and they've roamed to an address we don't
// know. If this is a handshake packet, we can try to identify the
// peer in question.
if as := c.peerFromPacketLocked(packet); as != nil {
as.updateDst(addr)
return as
}
// We have no idea who this is, drop the packet.
//
// In the past, when this magicsock implementation was the main
// one, we tried harder to find a match here: we would pass the
// packet into wireguard-go with a "singleEndpoint" implementation
// that wrapped the UDPAddr. Then, a patch we added to
// wireguard-go would call UpdateDst on that singleEndpoint after
// decrypting the packet and identifying the peer (if any),
// allowing us to update the relevant addrSet.
//
// This was a significant out of tree patch to wireguard-go, so we
// got rid of it, and instead switched to this logic you're
// reading now, which makes a best effort to identify sources for
// handshake packets (because they're relatively easy to turn into
// a peer public key statelessly), but otherwise drops packets
// that come from "roaming" addresses that aren't known to
// magicsock.
//
// The practical consequence of this is that some complex NAT
// traversal cases will now fail between a very old Tailscale
// client (0.96 and earlier) and a very new Tailscale
// client. However, those scenarios were likely also failing on
// all-old clients, because the probabilistic NAT opening didn't
// work reliably. So, in practice, this simplification means
// connectivity looks like this:
//
// - old+old client: unchanged
// - old+new client (easy network topology): unchanged
// - old+new client (hard network topology): was bad, now a bit worse
// - new+new client: unchanged
//
// This degradation is acceptable in that it continues to support
// the incremental upgrade of old clients that currently work
// well, which is our primary goal for the <100 clients still left
// on the oldest pre-DERP versions (as of 2021-01-12).
return nil
}
func (c *Conn) resetAddrSetStatesLocked() {
@@ -90,16 +138,6 @@ func (c *Conn) resetAddrSetStatesLocked() {
}
}
func (c *Conn) sendSingleEndpoint(b []byte, se *singleEndpoint) error {
addr := (*net.UDPAddr)(se)
if addr.IP.Equal(derpMagicIP) {
c.logf("magicsock: [unexpected] DERP BUG: attempting to send packet to DERP address %v", addr)
return nil
}
_, err := c.sendUDPStd(addr, b)
return err
}
func (c *Conn) sendAddrSet(b []byte, as *addrSet) error {
var addrBuf [8]netaddr.IPPort
dsts, roamAddr := as.appendDests(addrBuf[:0], b)
@@ -129,15 +167,71 @@ func (c *Conn) sendAddrSet(b []byte, as *addrSet) error {
return ret
}
// peerFromPacketLocked extracts returns the addrSet for the peer who sent
// packet, if derivable.
//
// The derived addrSet is a hint, not a cryptographically strong
// assertion. The returned value MUST NOT be used for any security
// critical function. Callers MUST assume that the addrset can be
// picked by a remote attacker.
func (c *Conn) peerFromPacketLocked(packet []byte) *addrSet {
if len(packet) < 4 {
return nil
}
msgType := binary.LittleEndian.Uint32(packet[:4])
if msgType != messageInitiationType {
// Can't get peer out of a non-handshake packet.
return nil
}
var msg messageInitiation
reader := bytes.NewReader(packet)
err := binary.Read(reader, binary.LittleEndian, &msg)
if err != nil {
return nil
}
// Process just enough of the handshake to extract the long-term
// peer public key. We don't verify the handshake all the way, so
// this may be a spoofed packet. The extracted peer MUST NOT be
// used for any security critical function. In our case, we use it
// as a hint for roaming addresses.
var (
pub = c.privateKey.Public()
hash [blake2s.Size]byte
chainKey [blake2s.Size]byte
peerPK key.Public
boxKey [chacha20poly1305.KeySize]byte
)
mixHash(&hash, &initialHash, pub[:])
mixHash(&hash, &hash, msg.Ephemeral[:])
mixKey(&chainKey, &initialChainKey, msg.Ephemeral[:])
ss := c.privateKey.SharedSecret(key.Public(msg.Ephemeral))
if isZero(ss[:]) {
return nil
}
kdf2(&chainKey, &boxKey, chainKey[:], ss[:])
aead, _ := chacha20poly1305.New(boxKey[:])
_, err = aead.Open(peerPK[:0], zeroNonce[:], msg.Static[:], hash[:])
if err != nil {
return nil
}
return c.addrsByKey[peerPK]
}
func shouldSprayPacket(b []byte) bool {
if len(b) < 4 {
return false
}
msgType := binary.LittleEndian.Uint32(b[:4])
switch msgType {
case device.MessageInitiationType,
device.MessageResponseType,
device.MessageCookieReplyType: // TODO: necessary?
case messageInitiationType,
messageResponseType,
messageCookieReplyType: // TODO: necessary?
return true
}
return false
@@ -325,19 +419,6 @@ func (a *addrSet) dst() netaddr.IPPort {
return a.ipPorts[i]
}
// packUDPAddr packs a UDPAddr in the form wanted by WireGuard.
func packUDPAddr(ua *net.UDPAddr) []byte {
ip := ua.IP.To4()
if ip == nil {
ip = ua.IP
}
b := make([]byte, 0, len(ip)+2)
b = append(b, ip...)
b = append(b, byte(ua.Port))
b = append(b, byte(ua.Port>>8))
return b
}
func (a *addrSet) DstToBytes() []byte {
return packIPPort(a.dst())
}
@@ -353,6 +434,12 @@ func (a *addrSet) SrcToString() string { return "" }
func (a *addrSet) ClearSrc() {}
func (a *addrSet) UpdateDst(new *net.UDPAddr) error {
return nil
}
// updateDst records receipt of a packet from new. This is used to
// potentially update the transmit address used for this addrSet.
func (a *addrSet) updateDst(new *net.UDPAddr) error {
if new.IP.Equal(derpMagicIP) {
// Never consider DERP addresses as a viable candidate for
// either curAddr or roamAddr. It's only ever a last resort
@@ -480,43 +567,103 @@ func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) {
}
}
func (a *addrSet) Addrs() []wgcfg.Endpoint {
var eps []wgcfg.Endpoint
func (a *addrSet) Addrs() string {
var addrs []string
for _, addr := range a.addrs {
eps = append(eps, wgcfg.Endpoint{
Host: addr.IP.String(),
Port: uint16(addr.Port),
})
addrs = append(addrs, addr.String())
}
a.mu.Lock()
defer a.mu.Unlock()
if a.roamAddr != nil {
eps = append(eps, wgcfg.Endpoint{
Host: a.roamAddr.IP.String(),
Port: uint16(a.roamAddr.Port),
})
addrs = append(addrs, a.roamAddr.String())
}
return eps
return strings.Join(addrs, ",")
}
// singleEndpoint is a wireguard-go/conn.Endpoint used for "roaming
// addressed" in releases of Tailscale that predate discovery
// messages. New peers use discoEndpoint.
type singleEndpoint net.UDPAddr
// Message types copied from wireguard-go/device/noise-protocol.go
const (
messageInitiationType = 1
messageResponseType = 2
messageCookieReplyType = 3
)
func (e *singleEndpoint) ClearSrc() {}
func (e *singleEndpoint) DstIP() net.IP { return (*net.UDPAddr)(e).IP }
func (e *singleEndpoint) SrcIP() net.IP { return nil }
func (e *singleEndpoint) SrcToString() string { return "" }
func (e *singleEndpoint) DstToString() string { return (*net.UDPAddr)(e).String() }
func (e *singleEndpoint) DstToBytes() []byte { return packUDPAddr((*net.UDPAddr)(e)) }
func (e *singleEndpoint) UpdateDst(dst *net.UDPAddr) error {
return fmt.Errorf("magicsock.singleEndpoint(%s).UpdateDst(%s): should never be called", (*net.UDPAddr)(e), dst)
// Cryptographic constants copied from wireguard-go/device/noise-protocol.go
var (
noiseConstruction = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
wgIdentifier = "WireGuard v1 zx2c4 Jason@zx2c4.com"
initialChainKey [blake2s.Size]byte
initialHash [blake2s.Size]byte
zeroNonce [chacha20poly1305.NonceSize]byte
)
func init() {
initialChainKey = blake2s.Sum256([]byte(noiseConstruction))
mixHash(&initialHash, &initialChainKey, []byte(wgIdentifier))
}
func (e *singleEndpoint) Addrs() []wgcfg.Endpoint {
return []wgcfg.Endpoint{{
Host: e.IP.String(),
Port: uint16(e.Port),
}}
// messageInitiation is the same as wireguard-go's MessageInitiation,
// from wireguard-go/device/noise-protocol.go.
type messageInitiation struct {
Type uint32
Sender uint32
Ephemeral wgcfg.Key
Static [wgcfg.KeySize + poly1305.TagSize]byte
Timestamp [tai64n.TimestampSize + poly1305.TagSize]byte
MAC1 [blake2s.Size128]byte
MAC2 [blake2s.Size128]byte
}
func mixKey(dst *[blake2s.Size]byte, c *[blake2s.Size]byte, data []byte) {
kdf1(dst, c[:], data)
}
func mixHash(dst *[blake2s.Size]byte, h *[blake2s.Size]byte, data []byte) {
hash, _ := blake2s.New256(nil)
hash.Write(h[:])
hash.Write(data)
hash.Sum(dst[:0])
hash.Reset()
}
func hmac1(sum *[blake2s.Size]byte, key, in0 []byte) {
mac := hmac.New(func() hash.Hash {
h, _ := blake2s.New256(nil)
return h
}, key)
mac.Write(in0)
mac.Sum(sum[:0])
}
func hmac2(sum *[blake2s.Size]byte, key, in0, in1 []byte) {
mac := hmac.New(func() hash.Hash {
h, _ := blake2s.New256(nil)
return h
}, key)
mac.Write(in0)
mac.Write(in1)
mac.Sum(sum[:0])
}
func kdf1(t0 *[blake2s.Size]byte, key, input []byte) {
hmac1(t0, key, input)
hmac1(t0, t0[:], []byte{0x1})
}
func kdf2(t0, t1 *[blake2s.Size]byte, key, input []byte) {
var prk [blake2s.Size]byte
hmac1(&prk, key, input)
hmac1(t0, prk[:], []byte{0x1})
hmac2(t1, prk[:], t0[:], []byte{0x2})
for i := range prk[:] {
prk[i] = 0
}
}
func isZero(val []byte) bool {
acc := 1
for _, b := range val {
acc &= subtle.ConstantTimeByteEq(b, 0)
}
return acc == 1
}

View File

@@ -27,9 +27,7 @@ import (
"syscall"
"time"
"github.com/golang/groupcache/lru"
"github.com/tailscale/wireguard-go/conn"
"github.com/tailscale/wireguard-go/wgcfg"
"go4.org/mem"
"golang.org/x/crypto/nacl/box"
"golang.org/x/time/rate"
@@ -711,14 +709,46 @@ func peerForIP(nm *controlclient.NetworkMap, ip netaddr.IP) (n *tailcfg.Node, ok
}
}
}
// TODO(bradfitz): this is O(n peers). Add ART to netaddr?
var best netaddr.IPPrefix
for _, p := range nm.Peers {
for _, cidr := range p.AllowedIPs {
if cidr.Contains(ip) {
return p, true
if best.IsZero() || cidr.Bits > best.Bits {
n = p
best = cidr
}
}
}
}
return nil, false
return n, n != nil
}
// PeerForIP returns the node that ip should route to.
func (c *Conn) PeerForIP(ip netaddr.IP) (n *tailcfg.Node, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.netMap == nil {
return
}
return peerForIP(c.netMap, ip)
}
// LastRecvActivityOfDisco returns the time we last got traffic from
// this endpoint (updated every ~10 seconds).
func (c *Conn) LastRecvActivityOfDisco(dk tailcfg.DiscoKey) time.Time {
c.mu.Lock()
defer c.mu.Unlock()
de, ok := c.endpointOfDisco[dk]
if !ok {
return time.Time{}
}
unix := atomic.LoadInt64(&de.lastRecvUnixAtomic)
if unix == 0 {
return time.Time{}
}
return time.Unix(unix, 0)
}
// Ping handles a "tailscale ping" CLI query.
@@ -977,8 +1007,6 @@ func (c *Conn) Send(b []byte, ep conn.Endpoint) error {
panic(fmt.Sprintf("[unexpected] Endpoint type %T", v))
case *discoEndpoint:
return v.send(b)
case *singleEndpoint:
return c.sendSingleEndpoint(b, v)
case *addrSet:
return c.sendAddrSet(b, v)
}
@@ -1139,7 +1167,7 @@ func (c *Conn) derpWriteChanOfAddr(addr netaddr.IPPort, peer key.Public) chan<-
return nil
}
// Note that derphttp.NewClient does not dial the server
// Note that derphttp.NewRegionClient does not dial the server
// so it is safe to do under the mu lock.
dc := derphttp.NewRegionClient(c.privateKey, c.logf, func() *tailcfg.DERPRegion {
if c.connCtx.Err() != nil {
@@ -1387,7 +1415,7 @@ func (c *Conn) runDerpWriter(ctx context.Context, dc *derphttp.Client, ch <-chan
// Endpoint to find the UDPAddr to return to wireguard anyway, so no
// benefit unless we can, say, always return the same fake UDPAddr for
// all packets.
func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr) conn.Endpoint {
func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte) conn.Endpoint {
c.mu.Lock()
defer c.mu.Unlock()
@@ -1399,7 +1427,7 @@ func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr) conn.Endpoint
}
}
return c.findLegacyEndpointLocked(ipp, addr)
return c.findLegacyEndpointLocked(ipp, addr, packet)
}
type udpReadResult struct {
@@ -1427,7 +1455,7 @@ func (c *Conn) awaitUDP4(b []byte) {
return
}
addr := pAddr.(*net.UDPAddr)
ipp, ok := c.pconn4.ippCache.IPPort(addr)
ipp, ok := netaddr.FromStdAddr(addr.IP, addr.Port, addr.Zone)
if !ok {
continue
}
@@ -1482,7 +1510,10 @@ Top:
if from := c.bufferedIPv4From; from != (netaddr.IPPort{}) {
c.bufferedIPv4From = netaddr.IPPort{}
addr = from.UDPAddr()
ep := c.findEndpoint(from, addr)
ep := c.findEndpoint(from, addr, c.bufferedIPv4Packet)
if ep == nil {
goto Top
}
c.noteRecvActivityFromEndpoint(ep)
return copy(b, c.bufferedIPv4Packet), ep, wgRecvAddr(ep, from, addr), nil
}
@@ -1569,11 +1600,10 @@ Top:
} else {
key := wgkey.Key(dm.src)
c.logf("magicsock: DERP packet from unknown key: %s", key.ShortString())
// TODO(danderson): after we fail to find a DERP endpoint, we
// seem to be falling through to passing the packet to
// wireguard with a garbage singleEndpoint. This feels wrong,
// should we goto Top above?
ep = c.findEndpoint(ipp, addr)
ep = c.findEndpoint(ipp, addr, b[:n])
if ep == nil {
goto Top
}
}
if !didNoteRecvActivity {
@@ -1586,7 +1616,10 @@ Top:
return 0, nil, nil, err
}
n, addr, ipp = um.n, um.addr, um.ipp
ep = c.findEndpoint(ipp, addr)
ep = c.findEndpoint(ipp, addr, b[:n])
if ep == nil {
goto Top
}
c.noteRecvActivityFromEndpoint(ep)
return n, ep, wgRecvAddr(ep, ipp, addr), nil
@@ -1615,7 +1648,7 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, *net.UDPAddr, error) {
return 0, nil, nil, err
}
addr := pAddr.(*net.UDPAddr)
ipp, ok := c.pconn6.ippCache.IPPort(addr)
ipp, ok := netaddr.FromStdAddr(addr.IP, addr.Port, addr.Zone)
if !ok {
continue
}
@@ -1627,7 +1660,10 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, *net.UDPAddr, error) {
continue
}
ep := c.findEndpoint(ipp, addr)
ep := c.findEndpoint(ipp, addr, b[:n])
if ep == nil {
continue
}
c.noteRecvActivityFromEndpoint(ep)
return n, ep, wgRecvAddr(ep, ipp, addr), nil
}
@@ -2556,10 +2592,6 @@ func (c *Conn) CreateEndpoint(pubKey [32]byte, addrs string) (conn.Endpoint, err
// RebindingUDPConn is a UDP socket that can be re-bound.
// Unix has no notion of re-binding a socket, so we swap it out for a new one.
type RebindingUDPConn struct {
// ippCache is a cache from UDPAddr => netaddr.IPPort. It's not safe for concurrent use.
// This is used by ReceiveIPv6 and awaitUDP4 (called from ReceiveIPv4).
ippCache ippCache
mu sync.Mutex
pconn net.PacketConn
}
@@ -2710,10 +2742,14 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
ss := &ipnstate.PeerStatus{
PublicKey: c.privateKey.Public(),
Addrs: c.lastEndpoints,
OS: version.OS(),
}
if c.netMap != nil {
ss.HostName = c.netMap.Hostinfo.Hostname
ss.OS = c.netMap.Hostinfo.OS
ss.DNSName = c.netMap.Name
ss.UserID = c.netMap.User
} else {
ss.HostName, _ = os.Hostname()
}
if c.derpMap != nil {
derpRegion, ok := c.derpMap.Regions[c.myDerp]
@@ -2940,19 +2976,11 @@ func (de *discoEndpoint) String() string {
return fmt.Sprintf("magicsock.discoEndpoint{%v, %v}", de.publicKey.ShortString(), de.discoShort)
}
func (de *discoEndpoint) Addrs() []wgcfg.Endpoint {
func (de *discoEndpoint) Addrs() string {
// This has to be the same string that was passed to
// CreateEndpoint, otherwise Reconfig will end up recreating
// Endpoints and losing state over time.
host, portStr, err := net.SplitHostPort(de.wgEndpointHostPort)
if err != nil {
panic(err)
}
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
panic(err)
}
return []wgcfg.Endpoint{{Host: host, Port: uint16(port)}}
return de.wgEndpointHostPort
}
func (de *discoEndpoint) ClearSrc() {}
@@ -3451,45 +3479,24 @@ func (de *discoEndpoint) stopAndReset() {
de.pendingCLIPings = nil
}
// ippCache is a cache of *net.UDPAddr => netaddr.IPPort mappings.
//
// It's not safe for concurrent use.
type ippCache struct {
c *lru.Cache
}
// IPPort is a caching wrapper around netaddr.FromStdAddr.
//
// It is not safe for concurrent use.
func (ic *ippCache) IPPort(u *net.UDPAddr) (netaddr.IPPort, bool) {
if u == nil || len(u.IP) > 16 {
return netaddr.IPPort{}, false
}
if ic.c == nil {
ic.c = lru.New(64) // arbitrary
}
key := ippCacheKey{ipLen: uint8(len(u.IP)), port: uint16(u.Port), zone: u.Zone}
copy(key.ip[:], u.IP[:])
if v, ok := ic.c.Get(key); ok {
return v.(netaddr.IPPort), true
}
ipp, ok := netaddr.FromStdAddr(u.IP, u.Port, u.Zone)
if ok {
ic.c.Add(key, ipp)
}
return ipp, ok
}
// ippCacheKey is the cache key type used by ippCache.IPPort.
// It must be comparable, being used as a map key in the lru package.
type ippCacheKey struct {
ip [16]byte
port uint16
ipLen uint8 // bytes in ip that are valid; rest are zero
zone string
}
// derpStr replaces DERP IPs in s with "derp-".
func derpStr(s string) string { return strings.ReplaceAll(s, "127.3.3.40:", "derp-") }
// WhoIs reports the node and user who owns the node with the given IP.
// If ok == true, n and u are valid.
func (c *Conn) WhoIs(ip netaddr.IP) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.netMap == nil {
return n, u, false
}
for _, p := range c.netMap.Peers {
for _, ipp := range p.Addresses {
if ipp.IsSingleIP() && ipp.IP == ip {
u, ok := c.netMap.UserProfiles[p.User]
return p, u, ok
}
}
}
return nil, u, false
}

View File

@@ -11,6 +11,7 @@ import (
"crypto/tls"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
@@ -482,12 +483,9 @@ func makeConfigs(t *testing.T, addrs []netaddr.IPPort) []wgcfg.Config {
continue
}
peer := wgcfg.Peer{
PublicKey: privKeys[peerNum].Public(),
AllowedIPs: addresses[peerNum],
Endpoints: []wgcfg.Endpoint{{
Host: addr.IP.String(),
Port: addr.Port,
}},
PublicKey: privKeys[peerNum].Public(),
AllowedIPs: addresses[peerNum],
Endpoints: addr.String(),
PersistentKeepalive: 25,
}
cfg.Peers = append(cfg.Peers, peer)
@@ -542,6 +540,145 @@ func TestDeviceStartStop(t *testing.T) {
dev.Close()
}
// A context used in TestConnClosing() which seeks to test that code which calls
// Err() to see if a connection is already being closed does not then proceed to
// try to acquire the mutex, as this would lead to deadlock. When Err() is called
// this context acquires the lock itself, in order to force a deadlock (and test
// failure on timeout).
type testConnClosingContext struct {
parent context.Context
mu *sync.Mutex
}
func (c *testConnClosingContext) Deadline() (deadline time.Time, ok bool) {
d, o := c.parent.Deadline()
return d, o
}
func (c *testConnClosingContext) Done() <-chan struct{} {
return c.parent.Done()
}
func (c *testConnClosingContext) Err() error {
// Deliberately deadlock if anything grabs the lock after checking Err()
c.mu.Lock()
return errors.New("testConnClosingContext error")
}
func (c *testConnClosingContext) Value(key interface{}) interface{} {
return c.parent.Value(key)
}
func (*testConnClosingContext) String() string {
return "testConnClosingContext"
}
func TestConnClosing(t *testing.T) {
privateKey, err := wgkey.NewPrivate()
if err != nil {
t.Fatalf("generating private key: %v", err)
}
epCh := make(chan []string, 100)
conn, err := NewConn(Options{
Logf: t.Logf,
PacketListener: nettype.Std{},
EndpointsFunc: func(eps []string) {
epCh <- eps
},
SimulatedNetwork: false,
})
if err != nil {
t.Fatalf("constructing magicsock: %v", err)
}
derpMap, cleanup := runDERPAndStun(t, t.Logf, nettype.Std{}, netaddr.IPv4(127, 0, 3, 1))
defer cleanup()
// The point of this test case is to exercise handling in derpWriteChanOfAddr() which
// returns early if connCtx.Err() returns non-nil, to avoid a deadlock on conn.mu.
// We swap in a context which always returns an error, and deliberately grabs the lock
// to cause a deadlock if magicsock.go tries to acquire the lock after calling Err().
closingCtx := testConnClosingContext{parent: conn.connCtx, mu: &conn.mu}
conn.connCtx = &closingCtx
conn.Start()
conn.SetDERPMap(derpMap)
if err := conn.SetPrivateKey(privateKey); err != nil {
t.Fatalf("setting private key in magicsock: %v", err)
}
tun := tuntest.NewChannelTUN()
tsTun := tstun.WrapTUN(t.Logf, tun.TUN())
tsTun.SetFilter(filter.NewAllowAllForTest(t.Logf))
dev := device.NewDevice(tsTun, &device.DeviceOptions{
Logger: &device.Logger{
Debug: logger.StdLogger(t.Logf),
Info: logger.StdLogger(t.Logf),
Error: logger.StdLogger(t.Logf),
},
CreateEndpoint: conn.CreateEndpoint,
CreateBind: conn.CreateBind,
SkipBindUpdate: true,
})
dev.Up()
conn.WaitReady(t)
// We don't assert any failures within the test itself. If derpWriteChanOfAddr tries to
// grab the lock it will deadlock, and conn.WaitReady(t) will call t.Fatal() after timeout.
// (verified by deliberately breaking derpWriteChanOfAddr)
}
// Exercise a code path in sendDiscoMessage if the connection has been closed.
func TestConnClosed(t *testing.T) {
mstun := &natlab.Machine{Name: "stun"}
m1 := &natlab.Machine{Name: "m1"}
m2 := &natlab.Machine{Name: "m2"}
inet := natlab.NewInternet()
sif := mstun.Attach("eth0", inet)
m1if := m1.Attach("eth0", inet)
m2if := m2.Attach("eth0", inet)
d := &devices{
m1: m1,
m1IP: m1if.V4(),
m2: m2,
m2IP: m2if.V4(),
stun: mstun,
stunIP: sif.V4(),
}
derpMap, cleanup := runDERPAndStun(t, t.Logf, d.stun, d.stunIP)
defer cleanup()
ms1 := newMagicStack(t, logger.WithPrefix(t.Logf, "conn1: "), d.m1, derpMap)
defer ms1.Close()
ms2 := newMagicStack(t, logger.WithPrefix(t.Logf, "conn2: "), d.m2, derpMap)
defer ms2.Close()
cleanup = meshStacks(t.Logf, []*magicStack{ms1, ms2})
defer cleanup()
pkt := tuntest.Ping(ms2.IP(t).IPAddr().IP, ms1.IP(t).IPAddr().IP)
if len(ms1.conn.activeDerp) == 0 {
t.Errorf("unexpected DERP empty got: %v want: >0", len(ms1.conn.activeDerp))
}
ms1.conn.Close()
ms2.conn.Close()
// This should hit a c.closed conditional in sendDiscoMessage() and return immediately.
ms1.tun.Outbound <- pkt
select {
case <-ms2.tun.Inbound:
t.Error("unexpected response with connection closed")
case <-time.After(100 * time.Millisecond):
}
if len(ms1.conn.activeDerp) > 0 {
t.Errorf("unexpected DERP active got: %v want:0", len(ms1.conn.activeDerp))
}
}
func makeNestable(t *testing.T) (logf logger.Logf, setT func(t *testing.T)) {
var mu sync.RWMutex
cur := t
@@ -1000,12 +1137,12 @@ func testTwoDevicePing(t *testing.T, d *devices) {
})
// Add DERP relay.
derpEp := wgcfg.Endpoint{Host: "127.3.3.40", Port: 1}
derpEp := "127.3.3.40:1"
ep0 := cfgs[0].Peers[0].Endpoints
ep0 = append([]wgcfg.Endpoint{derpEp}, ep0...)
ep0 = derpEp + "," + ep0
cfgs[0].Peers[0].Endpoints = ep0
ep1 := cfgs[1].Peers[0].Endpoints
ep1 = append([]wgcfg.Endpoint{derpEp}, ep1...)
ep1 = derpEp + "," + ep1
cfgs[1].Peers[0].Endpoints = ep1
if err := m1.dev.Reconfig(&cfgs[0]); err != nil {
t.Fatal(err)
@@ -1021,8 +1158,8 @@ func testTwoDevicePing(t *testing.T, d *devices) {
})
// Disable real route.
cfgs[0].Peers[0].Endpoints = []wgcfg.Endpoint{derpEp}
cfgs[1].Peers[0].Endpoints = []wgcfg.Endpoint{derpEp}
cfgs[0].Peers[0].Endpoints = derpEp
cfgs[1].Peers[0].Endpoints = derpEp
if err := m1.dev.Reconfig(&cfgs[0]); err != nil {
t.Fatal(err)
}
@@ -1076,7 +1213,7 @@ func testTwoDevicePing(t *testing.T, d *devices) {
})
}
// TestAddrSet tests addrSet appendDests and UpdateDst.
// TestAddrSet tests addrSet appendDests and updateDst.
func TestAddrSet(t *testing.T) {
tstest.PanicOnLog()
rc := tstest.NewResourceCheck()
@@ -1238,7 +1375,7 @@ func TestAddrSet(t *testing.T) {
faket = faket.Add(st.advance)
if st.updateDst != nil {
if err := tt.as.UpdateDst(st.updateDst); err != nil {
if err := tt.as.updateDst(st.updateDst); err != nil {
t.Fatal(err)
}
continue

View File

@@ -51,13 +51,14 @@ type messageOrError struct {
}
type winMon struct {
ctx context.Context
cancel context.CancelFunc
messagec chan messageOrError
logf logger.Logf
pollTicker *time.Ticker
lastState *interfaces.State
closeHandle windows.Handle // signaled upon close
ctx context.Context
cancel context.CancelFunc
messagec chan messageOrError
logf logger.Logf
pollTicker *time.Ticker
lastState *interfaces.State
closeHandle windows.Handle // signaled upon close
goroutineDoneCh chan struct{} // closed when awaitIPAndRouteChanges goroutine has stopped
mu sync.Mutex
lastNetChange time.Time
@@ -72,12 +73,13 @@ func newOSMon(logf logger.Logf) (osMon, error) {
ctx, cancel := context.WithCancel(context.Background())
m := &winMon{
logf: logf,
ctx: ctx,
cancel: cancel,
messagec: make(chan messageOrError, 1),
pollTicker: time.NewTicker(pollIntervalSlow),
closeHandle: closeHandle,
logf: logf,
ctx: ctx,
cancel: cancel,
messagec: make(chan messageOrError, 1),
pollTicker: time.NewTicker(pollIntervalSlow),
closeHandle: closeHandle,
goroutineDoneCh: make(chan struct{}),
}
go m.awaitIPAndRouteChanges()
return m, nil
@@ -87,6 +89,12 @@ func (m *winMon) Close() error {
m.cancel()
m.pollTicker.Stop()
windows.SetEvent(m.closeHandle) // wakes up any reader blocked in Receive
// Wait for awaitIPAndRouteChannges to be done, which could
// still be using the m.closeHandle.
<-m.goroutineDoneCh
windows.CloseHandle(m.closeHandle)
return nil
}
@@ -125,6 +133,7 @@ func (m *winMon) stateChanged() bool {
}
func (m *winMon) awaitIPAndRouteChanges() {
defer close(m.goroutineDoneCh)
for {
msg, err := m.getIPOrRouteChangeMessage()
if err == errClosed {

View File

@@ -0,0 +1,157 @@
// Copyright (c) 2020 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.
// netstack doesn't build on 32-bit machines (https://github.com/google/gvisor/issues/5241)
// +build amd64 arm64 ppc64le riscv64 s390x
// Package netstack wires up gVisor's netstack into Tailscale.
package netstack
import (
"context"
"errors"
"fmt"
"log"
"net"
"strings"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/types/logger"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/tstun"
)
func Impl(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock.Conn) error {
if mc == nil {
return errors.New("nil magicsock.Conn")
}
if tundev == nil {
return errors.New("nil tundev")
}
if logf == nil {
return errors.New("nil logger")
}
if e == nil {
return errors.New("nil Engine")
}
ipstack := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4},
})
const mtu = 1500
linkEP := channel.New(512, mtu, "")
const nicID = 1
if err := ipstack.CreateNIC(nicID, linkEP); err != nil {
log.Fatal(err)
}
ipstack.AddAddress(nicID, ipv4.ProtocolNumber, tcpip.Address(net.ParseIP("100.96.188.101").To4()))
// Add 0.0.0.0/0 default route.
subnet, _ := tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", 4)), tcpip.AddressMask(strings.Repeat("\x00", 4)))
ipstack.SetRouteTable([]tcpip.Route{
{
Destination: subnet,
NIC: nicID,
},
})
// use Forwarder to accept any connection from stack
fwd := tcp.NewForwarder(ipstack, 0, 16, func(r *tcp.ForwarderRequest) {
logf("XXX ForwarderRequest: %v", r)
var wq waiter.Queue
ep, err := r.CreateEndpoint(&wq)
if err != nil {
r.Complete(true)
return
}
r.Complete(false)
c := gonet.NewTCPConn(&wq, ep)
// TCP echo
go echo(c, e, mc)
})
ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket)
go func() {
for {
packetInfo, ok := linkEP.ReadContext(context.Background())
if !ok {
logf("XXX ReadContext-for-write = ok=false")
continue
}
pkt := packetInfo.Pkt
hdrNetwork := pkt.NetworkHeader()
hdrTransport := pkt.TransportHeader()
full := make([]byte, 0, pkt.Size())
full = append(full, hdrNetwork.View()...)
full = append(full, hdrTransport.View()...)
full = append(full, pkt.Data.ToView()...)
logf("XXX packet Write out: % x", full)
if err := tundev.InjectOutbound(full); err != nil {
log.Printf("netstack inject outbound: %v", err)
return
}
}
}()
tundev.PostFilterIn = func(p *packet.Parsed, t *tstun.TUN) filter.Response {
var pn tcpip.NetworkProtocolNumber
switch p.IPVersion {
case 4:
pn = header.IPv4ProtocolNumber
case 6:
pn = header.IPv6ProtocolNumber
}
logf("XXX packet in (from %v): % x", p.Src, p.Buffer())
vv := buffer.View(append([]byte(nil), p.Buffer()...)).ToVectorisedView()
packetBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: vv,
})
linkEP.InjectInbound(pn, packetBuf)
return filter.Accept
}
return nil
}
func echo(c *gonet.TCPConn, e wgengine.Engine, mc *magicsock.Conn) {
defer c.Close()
src, _ := netaddr.FromStdIP(c.RemoteAddr().(*net.TCPAddr).IP)
who := ""
if n, u, ok := mc.WhoIs(src); ok {
who = fmt.Sprintf("%v from %v", u.DisplayName, n.Name)
}
fmt.Fprintf(c, "Hello, %s! Thanks for connecting to me on port %v (Try other ports too!)\nEchoing...\n",
who,
c.LocalAddr().(*net.TCPAddr).Port)
buf := make([]byte, 1500)
for {
n, err := c.Read(buf)
if err != nil {
log.Printf("Err: %v", err)
break
}
c.Write(buf[:n])
}
log.Print("Connection closed")
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2020 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.
// netstack doesn't build on 32-bit machines (https://github.com/google/gvisor/issues/5241)
// +build !amd64,!arm64,!ppc64le,!riscv64,!s390x
package netstack
import (
"errors"
"tailscale.com/types/logger"
"tailscale.com/wgengine"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/tstun"
)
func Impl(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock.Conn) error {
return errors.New("netstack is not supported on 32-bit platforms for now; see https://github.com/google/gvisor/issues/5241")
}

157
wgengine/pendopen.go Normal file
View File

@@ -0,0 +1,157 @@
// 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 wgengine
import (
"os"
"strconv"
"time"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/tstun"
)
const tcpTimeoutBeforeDebug = 5 * time.Second
// debugConnectFailures reports whether the local node should track
// outgoing TCP connections and log which ones fail and why.
func debugConnectFailures() bool {
s := os.Getenv("TS_DEBUG_CONNECT_FAILURES")
if s == "" {
return true
}
v, _ := strconv.ParseBool(s)
return v
}
type pendingOpenFlow struct {
timer *time.Timer // until giving up on the flow
}
func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) (res filter.Response) {
res = filter.Accept // always
if pp.IPVersion == 0 ||
pp.IPProto != packet.TCP ||
pp.TCPFlags&(packet.TCPSyn|packet.TCPRst) == 0 {
return
}
flow := flowtrack.Tuple{Dst: pp.Src, Src: pp.Dst} // src/dst reversed
e.mu.Lock()
defer e.mu.Unlock()
of, ok := e.pendOpen[flow]
if !ok {
// Not a tracked flow.
return
}
of.timer.Stop()
delete(e.pendOpen, flow)
if pp.TCPFlags&packet.TCPRst != 0 {
// TODO(bradfitz): have peer send a IP proto 99 "why"
// packet first with details and log that instead
// (e.g. ACL prohibited, shields up, etc).
e.logf("open-conn-track: flow %v got RST by peer", flow)
return
}
return
}
func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN) (res filter.Response) {
res = filter.Accept // always
if pp.IPVersion == 0 ||
pp.IPProto != packet.TCP ||
pp.TCPFlags&packet.TCPSyn == 0 {
return
}
flow := flowtrack.Tuple{Src: pp.Src, Dst: pp.Dst}
timer := time.AfterFunc(tcpTimeoutBeforeDebug, func() {
e.onOpenTimeout(flow)
})
e.mu.Lock()
defer e.mu.Unlock()
if e.pendOpen == nil {
e.pendOpen = make(map[flowtrack.Tuple]*pendingOpenFlow)
}
if _, dup := e.pendOpen[flow]; dup {
// Duplicates are expected when the OS retransmits. Ignore.
return
}
e.pendOpen[flow] = &pendingOpenFlow{timer: timer}
return filter.Accept
}
func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
e.mu.Lock()
if _, ok := e.pendOpen[flow]; !ok {
// Not a tracked flow, or already handled & deleted.
e.mu.Unlock()
return
}
delete(e.pendOpen, flow)
e.mu.Unlock()
// Diagnose why it might've timed out.
n, ok := e.magicConn.PeerForIP(flow.Dst.IP)
if !ok {
e.logf("open-conn-track: timeout opening %v; no associated peer node", flow)
return
}
if n.DiscoKey.IsZero() {
e.logf("open-conn-track: timeout opening %v; peer node %v running pre-0.100", flow, n.Key.ShortString())
return
}
if n.DERP == "" {
e.logf("open-conn-track: timeout opening %v; peer node %v not connected to any DERP relay", flow, n.Key.ShortString())
return
}
var lastSeen time.Time
if n.LastSeen != nil {
lastSeen = *n.LastSeen
}
var ps *PeerStatus
if st, err := e.getStatus(); err == nil {
for _, v := range st.Peers {
if v.NodeKey == n.Key {
v := v // copy
ps = &v
}
}
} else {
e.logf("open-conn-track: timeout opening %v to node %v; failed to get engine status: %v", flow, n.Key.ShortString(), err)
return
}
if ps == nil {
e.logf("open-conn-track: timeout opening %v; target node %v in netmap but unknown to wireguard", flow, n.Key.ShortString())
return
}
// TODO(bradfitz): figure out what PeerStatus.LastHandshake
// is. It appears to be the last time we sent a handshake,
// which isn't what I expected. I thought it was when a
// handshake completed, which is what I want.
_ = ps.LastHandshake
e.logf("open-conn-track: timeout opening %v to node %v; lastSeen=%v, lastRecv=%v",
flow, n.Key.ShortString(),
agoOrNever(lastSeen), agoOrNever(e.magicConn.LastRecvActivityOfDisco(n.DiscoKey)))
}
func agoOrNever(t time.Time) string {
if t.IsZero() {
return "never"
}
return time.Since(t).Round(time.Second).String()
}

View File

@@ -18,7 +18,6 @@ import (
const (
ipv4RegBase = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`
ipv6RegBase = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`
tsRegBase = `SOFTWARE\Tailscale IPN`
)
type windowsManager struct {
@@ -91,11 +90,6 @@ func (m windowsManager) Up(config Config) error {
return err
}
newSearchList := strings.Join(config.Domains, ",")
if err := setRegistryString(tsRegBase, "SearchList", newSearchList); err != nil {
return err
}
// Force DNS re-registration in Active Directory. What we actually
// care about is that this command invokes the undocumented hidden
// function that forces Windows to notice that adapter settings

View File

@@ -26,6 +26,10 @@ type Map struct {
}
// NewMap returns a new Map with name to address mapping given by nameToIP.
//
// rootDomains are the domains whose subdomains should always be
// resolved locally to prevent leakage of sensitive names. They should
// end in a period ("user-foo.tailscale.net.").
func NewMap(initNameToIP map[string]netaddr.IP, rootDomains []string) *Map {
// TODO(dmytro): we have to allocate names and ipToName, but nameToIP can be avoided.
// It is here because control sends us names not in canonical form. Change this.

View File

@@ -17,6 +17,7 @@ import (
dns "golang.org/x/net/dns/dnsmessage"
"inet.af/netaddr"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
)
// maxResponseBytes is the maximum size of a response from a Resolver.
@@ -194,8 +195,8 @@ func (r *Resolver) Resolve(domain string, tp dns.Type) (netaddr.IP, dns.RCode, e
}
anyHasSuffix := false
for _, rootDomain := range dnsMap.rootDomains {
if strings.HasSuffix(domain, rootDomain) {
for _, suffix := range dnsMap.rootDomains {
if dnsname.HasSuffix(domain, suffix) {
anyHasSuffix = true
break
}
@@ -211,19 +212,38 @@ func (r *Resolver) Resolve(domain string, tp dns.Type) (netaddr.IP, dns.RCode, e
// Refactoring note: this must happen after we check suffixes,
// otherwise we will respond with NOTIMP to requests that should be forwarded.
switch {
case tp == dns.TypeA || tp == dns.TypeALL:
switch tp {
case dns.TypeA:
if !addr.Is4() {
return netaddr.IP{}, dns.RCodeSuccess, nil
}
return addr, dns.RCodeSuccess, nil
case tp == dns.TypeAAAA || tp == dns.TypeALL:
case dns.TypeAAAA:
if !addr.Is6() {
return netaddr.IP{}, dns.RCodeSuccess, nil
}
return addr, dns.RCodeSuccess, nil
default:
case dns.TypeALL:
// Answer with whatever we've got.
// It could be IPv4, IPv6, or a zero addr.
// TODO: Return all available resolutions (A and AAAA, if we have them).
return addr, dns.RCodeSuccess, nil
// Leave some some record types explicitly unimplemented.
// These types relate to recursive resolution or special
// DNS sematics and might be implemented in the future.
case dns.TypeNS, dns.TypeSOA, dns.TypeAXFR, dns.TypeHINFO:
return netaddr.IP{}, dns.RCodeNotImplemented, errNotImplemented
// For everything except for the few types above that are explictly not implemented, return no records.
// This is what other DNS systems do: always return NOERROR
// without any records whenever the requested record type is unknown.
// You can try this with:
// dig -t TYPE9824 example.com
// and note that NOERROR is returned, despite that record type being made up.
default:
// no records exist of this type
return netaddr.IP{}, dns.RCodeSuccess, nil
}
}

View File

@@ -214,6 +214,11 @@ func TestResolve(t *testing.T) {
{"no-ipv6", "test1.ipn.dev.", dns.TypeAAAA, netaddr.IP{}, dns.RCodeSuccess},
{"nxdomain", "test3.ipn.dev.", dns.TypeA, netaddr.IP{}, dns.RCodeNameError},
{"foreign domain", "google.com.", dns.TypeA, netaddr.IP{}, dns.RCodeRefused},
{"all", "test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess},
{"mx-ipv4", "test1.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeSuccess},
{"mx-ipv6", "test2.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeSuccess},
{"mx-nxdomain", "test3.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeNameError},
{"ns-nxdomain", "test3.ipn.dev.", dns.TypeNS, netaddr.IP{}, dns.RCodeNameError},
}
for _, tt := range tests {
@@ -593,6 +598,42 @@ var ptrResponse = []byte{
0x05, 0x74, 0x65, 0x73, 0x74, 0x31, 0x03, 0x69, 0x70, 0x6e, 0x03, 0x64, 0x65, 0x76, 0x00,
}
var ptrResponse6 = []byte{
0x00, 0x00, // transaction id: 0
0x84, 0x00, // flags: response, authoritative, no error
0x00, 0x01, // one question
0x00, 0x01, // one answer
0x00, 0x00, 0x00, 0x00, // no authority or additional RRs
// Question: f.0.e.0.d.0.c.0.b.0.a.0.9.0.8.0.7.0.6.0.5.0.4.0.3.0.2.0.1.0.0.0.ip6.arpa
0x01, 0x66, 0x01, 0x30, 0x01, 0x65, 0x01, 0x30,
0x01, 0x64, 0x01, 0x30, 0x01, 0x63, 0x01, 0x30,
0x01, 0x62, 0x01, 0x30, 0x01, 0x61, 0x01, 0x30,
0x01, 0x39, 0x01, 0x30, 0x01, 0x38, 0x01, 0x30,
0x01, 0x37, 0x01, 0x30, 0x01, 0x36, 0x01, 0x30,
0x01, 0x35, 0x01, 0x30, 0x01, 0x34, 0x01, 0x30,
0x01, 0x33, 0x01, 0x30, 0x01, 0x32, 0x01, 0x30,
0x01, 0x31, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30,
0x03, 0x69, 0x70, 0x36,
0x04, 0x61, 0x72, 0x70, 0x61, 0x00,
0x00, 0x0c, 0x00, 0x01, // type PTR, class IN6
// Answer: f.0.e.0.d.0.c.0.b.0.a.0.9.0.8.0.7.0.6.0.5.0.4.0.3.0.2.0.1.0.0.0.ip6.arpa
0x01, 0x66, 0x01, 0x30, 0x01, 0x65, 0x01, 0x30,
0x01, 0x64, 0x01, 0x30, 0x01, 0x63, 0x01, 0x30,
0x01, 0x62, 0x01, 0x30, 0x01, 0x61, 0x01, 0x30,
0x01, 0x39, 0x01, 0x30, 0x01, 0x38, 0x01, 0x30,
0x01, 0x37, 0x01, 0x30, 0x01, 0x36, 0x01, 0x30,
0x01, 0x35, 0x01, 0x30, 0x01, 0x34, 0x01, 0x30,
0x01, 0x33, 0x01, 0x30, 0x01, 0x32, 0x01, 0x30,
0x01, 0x31, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30,
0x03, 0x69, 0x70, 0x36,
0x04, 0x61, 0x72, 0x70, 0x61, 0x00,
0x00, 0x0c, 0x00, 0x01, // type PTR, class IN
0x00, 0x00, 0x02, 0x58, // TTL: 600
0x00, 0x0f, // length: 15 bytes
// PTR: test2.ipn.dev
0x05, 0x74, 0x65, 0x73, 0x74, 0x32, 0x03, 0x69, 0x70, 0x6e, 0x03, 0x64, 0x65, 0x76, 0x00,
}
var nxdomainResponse = []byte{
0x00, 0x00, // transaction id: 0
0x84, 0x03, // flags: response, authoritative, error: nxdomain
@@ -636,6 +677,8 @@ func TestFull(t *testing.T) {
{"no-ipv6", dnspacket("test1.ipn.dev.", dns.TypeAAAA), emptyResponse},
{"upper", dnspacket("TEST1.IPN.DEV.", dns.TypeA), ipv4UppercaseResponse},
{"ptr", dnspacket("4.3.2.1.in-addr.arpa.", dns.TypePTR), ptrResponse},
{"ptr", dnspacket("f.0.e.0.d.0.c.0.b.0.a.0.9.0.8.0.7.0.6.0.5.0.4.0.3.0.2.0.1.0.0.0.ip6.arpa.",
dns.TypePTR), ptrResponse6},
{"nxdomain", dnspacket("test3.ipn.dev.", dns.TypeA), nxdomainResponse},
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2020 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 tstun
import (
"github.com/tailscale/wireguard-go/tun"
"github.com/tailscale/wireguard-go/tun/wintun"
"golang.org/x/sys/windows"
)
func init() {
var err error
tun.WintunPool, err = wintun.MakePool("Tailscale")
if err != nil {
panic(err)
}
guid, err := windows.GUIDFromString("{37217669-42da-4657-a55b-0d995d328250}")
if err != nil {
panic(err)
}
tun.WintunStaticRequestedGUID = &guid
}

View File

@@ -30,6 +30,7 @@ import (
"tailscale.com/control/controlclient"
"tailscale.com/internal/deepprint"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/flowtrack"
"tailscale.com/net/interfaces"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
@@ -118,6 +119,7 @@ type userspaceEngine struct {
endpoints []string
pingers map[wgkey.Key]*pinger // legacy pingers for pre-discovery peers
linkState *interfaces.State
pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go
// Lock ordering: magicsock.Conn.mu, wgLock, then mu.
}
@@ -138,9 +140,19 @@ type EngineConfig struct {
// Fake determines whether this engine is running in fake mode,
// which disables such features as DNS configuration and unrestricted ICMP Echo responses.
Fake bool
// FakeImpl, if non-nil, specifies which type of fake implementation to
// use. Two values are typical: nil, for a basic ping-only fake
// implementation, and netstack.Impl, which brings in gvisor's netstack
// to the binary. The desire to keep that out of some binaries is why
// this func exists, so wgengine need not depend on gvisor.
FakeImpl FakeImplFunc
}
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
// FakeImplFunc is the type used by EngineConfig.FakeImpl. See docs there.
type FakeImplFunc func(logger.Logf, *tstun.TUN, Engine, *magicsock.Conn) error
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16, impl FakeImplFunc) (Engine, error) {
logf("Starting userspace wireguard engine (with fake TUN device)")
conf := EngineConfig{
Logf: logf,
@@ -148,6 +160,7 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error)
RouterGen: router.NewFake,
ListenPort: listenPort,
Fake: true,
FakeImpl: impl,
}
return NewUserspaceEngineAdvanced(conf)
}
@@ -209,12 +222,6 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
e.linkState, _ = getLinkState()
logf("link state: %+v", e.linkState)
// Respond to all pings only in fake mode.
if conf.Fake {
e.tundev.PostFilterIn = echoRespondToAll
}
e.tundev.PreFilterOut = e.handleLocalPackets
mon, err := monitor.New(logf, func() {
e.LinkChange(false)
tshttpproxy.InvalidateCache()
@@ -247,9 +254,39 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
}
e.magicConn.SetNetworkUp(e.linkState.AnyInterfaceUp())
// Respond to all pings only in fake mode.
if conf.Fake {
if impl := conf.FakeImpl; impl != nil {
if err := impl(logf, e.tundev, e, e.magicConn); err != nil {
return nil, err
}
} else {
// Respond to all pings only in fake mode.
e.tundev.PostFilterIn = echoRespondToAll
}
}
e.tundev.PreFilterOut = e.handleLocalPackets
if debugConnectFailures() {
if e.tundev.PreFilterIn != nil {
return nil, errors.New("unexpected PreFilterIn already set")
}
e.tundev.PreFilterIn = e.trackOpenPreFilterIn
if e.tundev.PostFilterOut != nil {
return nil, errors.New("unexpected PostFilterOut already set")
}
e.tundev.PostFilterOut = e.trackOpenPostFilterOut
}
// wireguard-go logs as it starts and stops routines.
// Silence those; there are a lot of them, and they're just noise.
allowLogf := func(s string) bool {
return !strings.HasPrefix(s, "Routine:")
}
filtered := logger.Filtered(logf, allowLogf)
// flags==0 because logf is already nested in another logger.
// The outer one can display the preferred log prefixes, etc.
dlog := logger.StdLogger(logf)
dlog := logger.StdLogger(filtered)
logger := device.Logger{
Debug: dlog,
Info: dlog,
@@ -258,7 +295,7 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
opts := &device.DeviceOptions{
Logger: &logger,
HandshakeDone: func(peerKey wgcfg.Key, peer *device.Peer, deviceAllowedIPs *device.AllowedIPs) {
HandshakeDone: func(peerKey device.NoisePublicKey, peer *device.Peer, deviceAllowedIPs *device.AllowedIPs) {
// Send an unsolicited status event every time a
// handshake completes. This makes sure our UI can
// update quickly as soon as it connects to a peer.
@@ -269,13 +306,14 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
// here.
go e.RequestStatus()
peerWGKey := wgkey.Key(peerKey)
if e.magicConn.PeerHasDiscoKey(tailcfg.NodeKey(peerKey)) {
e.logf("wireguard handshake complete for %v", peerKey.ShortString())
e.logf("wireguard handshake complete for %v", peerWGKey.ShortString())
// This is a modern peer with discovery support. No need to send pings.
return
}
e.logf("wireguard handshake complete for %v; sending legacy pings", peerKey.ShortString())
e.logf("wireguard handshake complete for %v; sending legacy pings", peerWGKey.ShortString())
// Ping every single-IP that peer routes.
// These synthetic packets are used to traverse NATs.
@@ -291,9 +329,9 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
}
}
if len(ips) > 0 {
go e.pinger(wgkey.Key(peerKey), ips)
go e.pinger(peerWGKey, ips)
} else {
logf("[unexpected] peer %s has no single-IP routes: %v", peerKey.ShortString(), allowedIPs)
logf("[unexpected] peer %s has no single-IP routes: %v", peerWGKey.ShortString(), allowedIPs)
}
},
CreateBind: e.magicConn.CreateBind,
@@ -636,10 +674,15 @@ func isTrimmablePeer(p *wgcfg.Peer, numPeers int) bool {
if forceFullWireguardConfig(numPeers) {
return false
}
if len(p.Endpoints) != 1 {
if !isSingleEndpoint(p.Endpoints) {
return false
}
if !strings.HasSuffix(p.Endpoints[0].Host, ".disco.tailscale") {
host, _, err := net.SplitHostPort(p.Endpoints)
if err != nil {
return false
}
if !strings.HasSuffix(host, ".disco.tailscale") {
return false
}
@@ -703,11 +746,14 @@ func (e *userspaceEngine) isActiveSince(dk tailcfg.DiscoKey, ip netaddr.IP, t ti
// Host of form "<64-hex-digits>.disco.tailscale". If invariant is violated,
// we return the zero value.
func discoKeyFromPeer(p *wgcfg.Peer) tailcfg.DiscoKey {
host := p.Endpoints[0].Host
if len(host) < 64 {
if len(p.Endpoints) < 64 {
return tailcfg.DiscoKey{}
}
k, err := key.NewPublicFromHexMem(mem.S(host[:64]))
host, rest := p.Endpoints[:64], p.Endpoints[64:]
if !strings.HasPrefix(rest, ".disco.tailscale") {
return tailcfg.DiscoKey{}
}
k, err := key.NewPublicFromHexMem(mem.S(host))
if err != nil {
return tailcfg.DiscoKey{}
}
@@ -908,21 +954,21 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
// and a second time with it.
discoChanged := make(map[key.Public]bool)
{
prevEP := make(map[key.Public]wgcfg.Endpoint)
prevEP := make(map[key.Public]string)
for i := range e.lastCfgFull.Peers {
if p := &e.lastCfgFull.Peers[i]; len(p.Endpoints) == 1 {
prevEP[key.Public(p.PublicKey)] = p.Endpoints[0]
if p := &e.lastCfgFull.Peers[i]; isSingleEndpoint(p.Endpoints) {
prevEP[key.Public(p.PublicKey)] = p.Endpoints
}
}
for i := range cfg.Peers {
p := &cfg.Peers[i]
if len(p.Endpoints) != 1 {
if !isSingleEndpoint(p.Endpoints) {
continue
}
pub := key.Public(p.PublicKey)
if old, ok := prevEP[pub]; ok && old != p.Endpoints[0] {
if old, ok := prevEP[pub]; ok && old != p.Endpoints {
discoChanged[pub] = true
e.logf("wgengine: Reconfig: %s changed from %s to %s", pub.ShortString(), &old, &p.Endpoints[0])
e.logf("wgengine: Reconfig: %s changed from %q to %q", pub.ShortString(), old, p.Endpoints)
}
}
}
@@ -967,6 +1013,11 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
return nil
}
// isSingleEndpoint reports whether endpoints contains exactly one host:port pair.
func isSingleEndpoint(s string) bool {
return s != "" && !strings.Contains(s, ",")
}
func (e *userspaceEngine) GetFilter() *filter.Filter {
return e.tundev.GetFilter()
}

View File

@@ -84,7 +84,7 @@ func TestNoteReceiveActivity(t *testing.T) {
}
func TestUserspaceEngineReconfig(t *testing.T) {
e, err := NewFakeUserspaceEngine(t.Logf, 0)
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
if err != nil {
t.Fatal(err)
}
@@ -103,12 +103,7 @@ func TestUserspaceEngineReconfig(t *testing.T) {
AllowedIPs: []netaddr.IPPrefix{
{IP: netaddr.IPv4(100, 100, 99, 1), Bits: 32},
},
Endpoints: []wgcfg.Endpoint{
{
Host: discoHex + ".disco.tailscale",
Port: 12345,
},
},
Endpoints: discoHex + ".disco.tailscale:12345",
},
},
}

View File

@@ -17,7 +17,7 @@ func TestWatchdog(t *testing.T) {
t.Run("default watchdog does not fire", func(t *testing.T) {
t.Parallel()
e, err := NewFakeUserspaceEngine(t.Logf, 0)
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
if err != nil {
t.Fatal(err)
}
@@ -35,7 +35,7 @@ func TestWatchdog(t *testing.T) {
t.Run("watchdog fires on blocked getStatus", func(t *testing.T) {
t.Parallel()
e, err := NewFakeUserspaceEngine(t.Logf, 0)
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
if err != nil {
t.Fatal(err)
}

View File

@@ -36,7 +36,7 @@ type PeerStatus struct {
// TODO(bradfitz): remove this, subset of ipnstate? Need to migrate users.
type Status struct {
Peers []PeerStatus
LocalAddrs []string // TODO(crawshaw): []wgcfg.Endpoint?
LocalAddrs []string // the set of possible endpoints for the magic conn
DERPs int // number of active DERP connections
}