Compare commits

...

80 Commits

Author SHA1 Message Date
David Crawshaw
2cf0fdb760 client/tailscale, cmd/tailscale/cli: plumb --socket through
Without this, `tailscale status` ignores the --socket flag on macOS and
always talks to the IPNExtension, even if you wanted it to inspect a
userspace tailscaled.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2021-03-30 09:23:08 -07:00
Brad Fitzpatrick
33bc69cf1f paths: fall back to XDG_DATA_HOME for non-root users' state dir
So peerapi has a default state directory, mostly for netstack mode
testing.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-30 08:21:14 -07:00
Brad Fitzpatrick
3a1eae5b6b cmd/tailscale/cli: factor out filename selection
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-29 22:19:42 -07:00
Brad Fitzpatrick
1e26d4ae19 cmd/tailscale/cli: add push subcommand
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-29 22:06:57 -07:00
Brad Fitzpatrick
eeacf84dae cmd/tailscale/cli: factor out tailscaleIPFromArg from ping command
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-29 21:29:27 -07:00
Brad Fitzpatrick
41e4e02e57 net/{packet,tstun}: send peerapi port in TSMP pongs
For discovery when an explicit hostname/IP is known. We'll still
also send it via control for finding peers by a list.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-29 15:18:23 -07:00
Brad Fitzpatrick
9659ab81e0 ipn/ipnlocal: send peerapi port(s) in Hostinfo.Services
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-29 12:51:19 -07:00
Brad Fitzpatrick
12ae2d73b3 control/controlclient: fix TS_DEBUG_MAP on requests
The concrete type being encoded changed from a value to pointer
earlier and this was never adjusted.

(People don't frequently use TS_DEBUG_MAP to see requests, so it went
unnoticed until now.)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-29 12:51:19 -07:00
David Crawshaw
f0863346c2 cmd/tailscale: add web subcommand
Used as an app frontend UI on Synology.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2021-03-29 12:13:19 -07:00
Brad Fitzpatrick
35596ae5ce ipn/ipnlocal: push down a user-specific root dir to peerapi handler
And add a put handler.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-29 11:33:35 -07:00
Naman Sood
662fbd4a09 wgengine/netstack: Allow userspace networking mode to expose subnets (#1588)
wgengine/netstack: Allow userspace networking mode to expose subnets

Updates #504

Updates #707

Signed-off-by: Naman Sood <mail@nsood.in>
2021-03-29 14:33:05 -04:00
Brad Fitzpatrick
a4c679e646 wgengine/monitor: on wall time jump, synthesize network change event
... to force rebinds of TCP connections

Fixes #1555
Updates tailscale/felicity#4

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-28 21:56:50 -07:00
David Anderson
07bf4eb685 wgengine: rename Fake to RespondToPing.
"Fake" doesn't mean a lot any more, given that many components
of the engine can be faked out, including in valid production
configurations like userspace-networking.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-28 21:45:02 -07:00
David Anderson
0fb738760f wgengine: make Tun optional again, default to fake.
This makes setup more explicit in prod codepaths, without
requiring a bunch of arguments or helpers for tests and
userspace mode.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-28 21:45:02 -07:00
Brad Fitzpatrick
e18c3a7d84 wgengine: optimize isLocalAddr a bit
On macOS/iOS, this removes a map lookup per outgoing packet.

Noticed it while reading code, not from profiles, but can't hurt.

BenchmarkGenLocalAddrFunc
BenchmarkGenLocalAddrFunc/map1
BenchmarkGenLocalAddrFunc/map1-4                16184868                69.78 ns/op
BenchmarkGenLocalAddrFunc/map2
BenchmarkGenLocalAddrFunc/map2-4                16878140                70.73 ns/op
BenchmarkGenLocalAddrFunc/or1
BenchmarkGenLocalAddrFunc/or1-4                 623055721                1.950 ns/op
BenchmarkGenLocalAddrFunc/or2
BenchmarkGenLocalAddrFunc/or2-4                 472493098                2.589 ns/op

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-28 21:23:25 -07:00
David Anderson
95ca86c048 go.mod: update to new wireguard-go version.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-28 19:05:55 -07:00
David Anderson
93a4aa697c wgengine: default Router to a no-op router.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-28 18:59:48 -07:00
David Anderson
440effb21a wgengine: remove Config.TUN argument. 2021-03-28 18:45:17 -07:00
Josh Bleecher Snyder
0807e3e2f7 syncs: disable TestWatchMultipleValues on Windows CI builds
The Windows CI machine experiences significant random execution delays.
For example, in this code from watchdog.go:

done := make(chan bool)
go func() {
	start := time.Now()
	mu.Lock()

There was a 500ms delay from initializing done to locking mu.

This test checks that we receive a sufficient number of events quickly enough.
In the face of random 500ms delays, unsurprisingly, the test fails.

There's not much principled we can do about it.
We could build a system of retries or attempt to detect these random delays,
but that game isn't worth the candle.

Skip the test.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-03-27 13:25:33 -07:00
Josh Bleecher Snyder
4954fbfda6 wgengine: extend TestWatchdog timeout on macOS
This works around the close syscall being slow.
We can revert this if we find a fix or if Apple makes close fast again.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-03-27 09:27:11 -07:00
David Anderson
2df8adef9d wgengine: make the tun.Device required at construction.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-27 00:33:09 -07:00
David Anderson
25e0bb0a4e net/tstun: rename wrap_windows.go to tun_windows.go.
The code has nothing to do with wrapping, it's windows-specific
driver initialization code.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 23:17:59 -07:00
David Anderson
22d53fe784 net/tstun: document exported function.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 23:17:01 -07:00
David Anderson
016de16b2e net/tstun: rename TUN to Wrapper.
The tstun packagen contains both constructors for generic tun
Devices, and a wrapper that provides additional functionality.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 23:15:22 -07:00
David Anderson
82ab7972f4 net/tstun: rename NewFakeTUN to NewFake.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 22:46:47 -07:00
David Anderson
588b70f468 net/tstun: merge in wgengine/tstun.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 22:31:54 -07:00
David Anderson
018200aeba net/tstun: rename from net/tun.
We depend on wireguard-go/tun, identical leaf packages can be
confusing in code.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 22:31:54 -07:00
David Anderson
2b4bfeda1a wgengine: pass in an explicit router.Router, rather than a generator.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 22:01:55 -07:00
David Anderson
9ea5cbf81f cmd/tailscaled: readd tun.Diagnose call, mistakenly lost during refactor.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 22:01:55 -07:00
Brad Fitzpatrick
f26dfd054a ipn/ipnlocal: rename/document peerapi stuff a bit, pass self identity
So handlers can vary based on whether owner of peer matches owner of
local node.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-26 21:36:39 -07:00
David Anderson
44d9929208 wgengine: remove Config.TUNName, require caller to create device.
Also factors out device creation and associated OS workarounds to
net/tun.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 21:08:11 -07:00
David Anderson
0a84aaca0a wgengine/router: remove unused wireguard *Device argument.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-26 19:43:13 -07:00
Brad Fitzpatrick
1642dfdb07 ipn/ipnlocal: get peerapi ~working in macOS/iOS NetworkExtension sandbox
IPv4 and IPv6 both work remotely, but IPv6 doesn't yet work from the
machine itself due to routing mysteries.

Untested yet on iOS, but previous prototype worked on iOS, so should
work the same.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-26 13:46:01 -07:00
Brad Fitzpatrick
bcf571ec97 wgengine/monitor: fix OpenBSD build
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-26 09:16:43 -07:00
Brad Fitzpatrick
7f174e84e6 net/interfaces: remove mutating methods, add EqualFiltered instead
Now callers (wgengine/monitor) don't need to mutate the state to remove
boring interfaces before calling State.Equal. Instead, the methods
to remove boring interfaces from the State are removed, as is
the reflect-using Equal method itself, and in their place is
a new EqualFiltered method that takes a func predicate to match
interfaces to compare.

And then the FilterInteresting predicate is added for use
with EqualFiltered to do the job that that wgengine/monitor
previously wanted.

Now wgengine/monitor can keep the full interface state around,
including the "boring" interfaces, which we'll need for peerapi on
macOS/iOS to bind to the interface index of the utunN device.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-26 09:11:48 -07:00
Brad Fitzpatrick
5a62aa8047 ipn/ipnlocal: pass down interface state to peerapi ListenConfig hook
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-26 09:11:48 -07:00
Brad Fitzpatrick
7dc88e4c1e net/interfaces: track more interface metadata in State
We have it already but threw it away. But macOS/iOS code will
be needing the interface index, so hang on to it.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-26 09:11:48 -07:00
Adrian Dewhurst
04dd6d1dae control/controlclient: sign RegisterRequest (#1549)
control/controlclient: sign RegisterRequest

Some customers wish to verify eligibility for devices to join their
tailnets using machine identity certificates. TLS client certs could
potentially fulfill this role but the initial customer for this feature
has technical requirements that prevent their use. Instead, the
certificate is loaded from the Windows local machine certificate store
and uses its RSA public key to sign the RegisterRequest message.

There is room to improve the flexibility of this feature in future and
it is currently only tested on Windows (although Darwin theoretically
works too), but this offers a reasonable starting place for now.

Updates tailscale/coral#6

Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
2021-03-26 10:01:08 -04:00
David Anderson
672731ac6f many: gofmt.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-25 17:41:51 -07:00
David Anderson
6521f02ff6 Move DNS flush logic to net/dns.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-25 17:40:52 -07:00
David Anderson
9f7f2af008 wgengine/router/dns: move to net/dns.
Preparation for merging the APIs and whatnot.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-25 16:25:30 -07:00
David Anderson
8432999835 Move wgengine/tsdns to net/dns.
Straight move+fixup, no other changes. In prep for merging with
wgengine/router/dns.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-25 16:25:30 -07:00
Brad Fitzpatrick
81143b6d9a ipn/ipnlocal: start of peerapi between nodes
Also some necessary refactoring of the ipn/ipnstate too.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-25 16:00:35 -07:00
Brad Fitzpatrick
dad10fee9c Revert "cmd/tailscaled: split package main into main shim + package"
This reverts commit b81bd8025b.

Not needed. See:

https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
2021-03-25 09:06:00 -07:00
Brad Fitzpatrick
82c4cb765c cmd/tailscaled: split package main into main shim + package
So we can empty import the guts of cmd/tailscaled from another
module for go mod tidy reasons.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-24 21:23:00 -07:00
Josh Bleecher Snyder
28af46fb3b wgengine: pass logger as a separate arg to device.NewDevice
Adapt to minor API changes in wireguard-go.
And factor out device.DeviceOptions variables.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-03-24 10:39:58 -07:00
Brad Fitzpatrick
b7f0e39bf2 cmd/tailscale: add "tailscale ip [-4] [-6]" command
This adds an easy and portable way for us to document how to get
your Tailscale IP address.

$ tailscale ip
100.74.70.3
fd7a:115c:a1e0:ab12:4843:cd96:624a:4603

$ tailscale ip -4
100.74.70.3

$ tailscale ip -6
fd7a:115c:a1e0:ab12:4843:cd96:624a:4603

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-24 09:54:32 -07:00
Brad Fitzpatrick
2384c112c9 net/packet, wgengine/{filter,tstun}: add TSMP ping
Fixes #1467

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-24 09:50:01 -07:00
Josh Bleecher Snyder
4b77eca2de wgengine/magicsock: check returned error in addTestEndpoint
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-03-24 09:46:36 -07:00
Josh Bleecher Snyder
79f02de55f go.sum: add entries for upstream wireguard-go
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-03-24 09:46:36 -07:00
Josh Bleecher Snyder
d31eff8473 tstest/natlab: use net.ErrClosed
We are now on 1.16.
And wgconn.NetErrClosed has been removed upstream.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-03-24 09:46:36 -07:00
Brad Fitzpatrick
c99f260e40 wgengine/magicsock: prefer IPv6 transport if roughly equivalent latency
Fixes #1566

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-23 17:34:01 -07:00
Brad Fitzpatrick
e2b3d9aa5f all: s/Magic DNS/MagicDNS/ for consistency
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-23 14:25:28 -07:00
Brad Fitzpatrick
77ec80538a syncs: add Semaphore
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-23 12:39:28 -07:00
Brad Fitzpatrick
9643d8b34d wgengine/magicsock: add an addrLatency type to combine an IPPort+time.Duration
Updates #1566 (but no behavior changes as of this change)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-23 10:09:10 -07:00
Brad Fitzpatrick
96dfeb2d7f wgengine: log tailscale pings
Fixes #1561

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-22 21:48:05 -07:00
Brad Fitzpatrick
85138d3183 health: track whether any network interface is up
Fixes #1562

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-22 21:42:14 -07:00
Brad Fitzpatrick
0994a9f7c4 wgengine{,/magicsock}: fix, improve "tailscale ping" to default routes and subnets
e.g.

$ tailscale ping 1.1.1.1
exit node found but not enabled

$ tailscale ping 10.2.200.2
node "tsbfvlan2" found, but not using its 10.2.200.0/24 route

$ sudo tailscale  up --accept-routes
$ tailscale ping 10.2.200.2
pong from tsbfvlan2 (100.124.196.94) via 10.2.200.34:41641 in 1ms

$ tailscale ping mon.ts.tailscale.com
pong from monitoring (100.88.178.64) via DERP(sfo) in 83ms
pong from monitoring (100.88.178.64) via DERP(sfo) in 21ms
pong from monitoring (100.88.178.64) via [2604:a880:4:d1::37:d001]:41641 in 22ms

This necessarily moves code up from magicsock to wgengine, so we can
look at the actual wireguard config.

Fixes #1564

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-22 21:29:44 -07:00
Brad Fitzpatrick
7e0d12e7cc wgengine/magicsock: don't update control if only endpoint order changes
Updates #1559

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-22 10:37:04 -07:00
Brad Fitzpatrick
1eb95c7e32 net/packet, wgengine{,/filter}: remove net/packet IPProto forwarding consts
Only use the ones in types/ipproto now.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-20 21:45:47 -07:00
Brad Fitzpatrick
01b90df2fa net/packet, wgengine/filter: support SCTP
Add proto to flowtrack.Tuple.

Add types/ipproto leaf package to break a cycle.

Server-side ACL work remains.

Updates #1516

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-20 21:34:13 -07:00
Brad Fitzpatrick
90a6fb7ffe tailcfg: add FilterRule.IPProto
Updates #1516

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-19 18:08:23 -07:00
Brad Fitzpatrick
32562a82a9 wgengine/magicsock: annotate a few more disco logs as verbose
Fixes #1540

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-19 13:24:29 -07:00
Brad Fitzpatrick
0406a7436a cmd/tailscale/cli: use double hypens, make default usage func more clear
Mash up some code from ffcli and std's flag package to make a default
usage func that's super explicit for those not familiar with the Go
style flags. Only show double hyphens in usage text (but still accept both),
and show default values, and only show the proper usage of boolean flags.

Fixes #1353
Fixes #1529

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-19 13:11:55 -07:00
David Anderson
8c0a0450d9 ipn/ipnlocal: allow client access to exit node's public IPs.
"public IP" is defined as an IP address configured on the exit node
itself that isn't in the list of forbidden ranges (RFC1918, CGNAT,
Tailscale).

Fixes #1522.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-19 11:54:15 -07:00
Brad Fitzpatrick
0a02aaf813 control, ipn, tailcfg: remove golang.org/x/oauth2 dep, add tailcfg.Oauth2Token
golang.org/x/oauth2 pulls in App Engine and grpc module dependencies,
screwing up builds that depend on this module.

Some background on the problem:
https://go.googlesource.com/proposal/+/master/design/36460-lazy-module-loading.md

Fixes tailscale/corp#1471

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-19 10:40:48 -07:00
Aleksandar Pesic
7b57310966 net/interfaces: use windows API to get the default route instead of parsing route print output
Fixes: #1470

Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2021-03-19 14:07:36 +01:00
Brad Fitzpatrick
439d70dce2 cmd/tailscale, ipn/localapi: get daemon version from localapi status
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-18 21:14:10 -07:00
Brad Fitzpatrick
d0dffe33c0 cmd/tailscale, ipn/localapi: use localapi for status, not IPN acrobatics
Yay simpler code.

Tested on Linux, macOS and Windows.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-18 19:51:02 -07:00
Brad Fitzpatrick
0c3e9722cc cmd/tailscale/cli: fix typo in comment
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-18 15:43:04 -07:00
Christine Dodrill
a480b1baa5 logpolicy: set log target on windows based on a registry key (#1542)
Signed-off-by: Christine Dodrill <xe@tailscale.com>
2021-03-18 13:23:56 -04:00
Brad Fitzpatrick
c19ed37b0f wgengine/magicsock: mark some legacy debug log output as verbose
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-18 08:17:59 -07:00
Brad Fitzpatrick
cc508be603 control/controlclient: remove redundant Hostinfo log
The direct client already logs it in JSON form. Then it's immediately
logged again in an unformatted dump, so this removes that unformatted
one.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-18 08:16:33 -07:00
Brad Fitzpatrick
aa79a57f63 wgengine/netstack: use inet.af/netstack, remove 64-bit only limitation
This reverts the revert commit 84aba349d9.

And changes us to use inet.af/netstack.

Updates #1518

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-17 22:46:09 -07:00
Brad Fitzpatrick
a217078f67 go.mod: update golang.org/x/oauth2
go.sum gets a bit wild, but tolerable.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-17 22:46:09 -07:00
Brad Fitzpatrick
ec1b31ea83 go.mod: update golang.org/x/{crypto,sync,sys,term,time}
These ones don't have large dependency trees.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-17 22:46:09 -07:00
Brad Fitzpatrick
a4fa2c5611 go.mod, go.sum: go mod tidy
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-03-17 19:54:01 -07:00
David Anderson
6fb5d4080c net/portmapper: silently handle PCP NOT_AUTHORIZED responses.
Fixes #1525.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-03-17 19:44:35 -07:00
Brad Fitzpatrick
4145bb7148 tailcfg: bump CurrentMapRequestVersion, forgotten earlier
In f45a9e291b (2021-03-04), I tried to bump CurrentMapRequestVersion
to 12 but only documented the meaning of 12 but forgot to actually
increase it from 11.

Mapver 11 was added in ea49b1e811 (2021-03-03).

Fix this in its own commit so we can cherry-pick it to the 1.6 release
branch.
2021-03-17 14:12:35 -07:00
David Anderson
4543e4202f VERSION.txt: this is 1.7.0. 2021-03-16 19:04:55 -07:00
122 changed files with 3805 additions and 1599 deletions

View File

@@ -1 +1 @@
1.5.0
1.7.0

View File

@@ -15,10 +15,15 @@ import (
"net/url"
"strconv"
"tailscale.com/ipn/ipnstate"
"tailscale.com/paths"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
)
// TailscaledSocket is the tailscaled Unix socket.
var TailscaledSocket = paths.DefaultTailscaledSocket()
// tsClient does HTTP requests to the local Tailscale daemon.
var tsClient = &http.Client{
Transport: &http.Transport{
@@ -26,14 +31,16 @@ var tsClient = &http.Client{
if addr != "local-tailscaled.sock:80" {
return nil, fmt.Errorf("unexpected URL address %q", addr)
}
// On macOS, when dialing from non-sandboxed program to sandboxed GUI running
// a TCP server on a random port, find the random port. For HTTP connections,
// we don't send the token. It gets added in an HTTP Basic-Auth header.
if port, _, err := safesocket.LocalTCPPortAndToken(); err == nil {
var d net.Dialer
return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port))
if TailscaledSocket == paths.DefaultTailscaledSocket() {
// On macOS, when dialing from non-sandboxed program to sandboxed GUI running
// a TCP server on a random port, find the random port. For HTTP connections,
// we don't send the token. It gets added in an HTTP Basic-Auth header.
if port, _, err := safesocket.LocalTCPPortAndToken(); err == nil {
var d net.Dialer
return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port))
}
}
return safesocket.ConnectDefault()
return safesocket.Connect(TailscaledSocket, 41112)
},
},
}
@@ -79,6 +86,7 @@ func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, erro
return r, nil
}
// Goroutines returns a dump of the Tailscale daemon's current goroutines.
func Goroutines(ctx context.Context) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/goroutines", nil)
if err != nil {
@@ -98,3 +106,34 @@ func Goroutines(ctx context.Context) ([]byte, error) {
}
return body, nil
}
// Status returns the Tailscale daemon's status.
func Status(ctx context.Context) (*ipnstate.Status, error) {
return status(ctx, "")
}
// StatusWithPeers returns the Tailscale daemon's status, without the peer info.
func StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
return status(ctx, "?peers=false")
}
func status(ctx context.Context, queryString string) (*ipnstate.Status, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/status"+queryString, nil)
if err != nil {
return nil, err
}
res, err := DoLocalRequest(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("HTTP %s: %s", res.Status, body)
}
st := new(ipnstate.Status)
if err := json.NewDecoder(res.Body).Decode(st); err != nil {
return nil, err
}
return st, nil
}

View File

@@ -9,6 +9,7 @@ package cli
import (
"context"
"flag"
"fmt"
"log"
"net"
"os"
@@ -16,8 +17,10 @@ import (
"runtime"
"strings"
"syscall"
"text/tabwriter"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/paths"
"tailscale.com/safesocket"
@@ -53,9 +56,7 @@ func Run(args []string) error {
ShortUsage: "tailscale [flags] <subcommand> [command flags]",
ShortHelp: "The easiest, most secure way to use WireGuard.",
LongHelp: strings.TrimSpace(`
For help on subcommands, add -help after: "tailscale status -help".
All flags can use single or double hyphen prefixes (-help or --help).
For help on subcommands, add --help after: "tailscale status --help".
This CLI is still under active development. Commands and flags will
change in the future.
@@ -64,12 +65,19 @@ change in the future.
upCmd,
downCmd,
netcheckCmd,
ipCmd,
statusCmd,
pingCmd,
versionCmd,
webCmd,
pushCmd,
},
FlagSet: rootfs,
Exec: func(context.Context, []string) error { return flag.ErrHelp },
FlagSet: rootfs,
Exec: func(context.Context, []string) error { return flag.ErrHelp },
UsageFunc: usageFunc,
}
for _, c := range rootCmd.Subcommands {
c.UsageFunc = usageFunc
}
// Don't advertise the debug command, but it exists.
@@ -81,6 +89,8 @@ change in the future.
return err
}
tailscale.TailscaledSocket = rootArgs.socket
err := rootCmd.Run(context.Background())
if err == flag.ErrHelp {
return nil
@@ -147,3 +157,72 @@ func strSliceContains(ss []string, s string) bool {
}
return false
}
func usageFunc(c *ffcli.Command) string {
var b strings.Builder
fmt.Fprintf(&b, "USAGE\n")
if c.ShortUsage != "" {
fmt.Fprintf(&b, " %s\n", c.ShortUsage)
} else {
fmt.Fprintf(&b, " %s\n", c.Name)
}
fmt.Fprintf(&b, "\n")
if c.LongHelp != "" {
fmt.Fprintf(&b, "%s\n\n", c.LongHelp)
}
if len(c.Subcommands) > 0 {
fmt.Fprintf(&b, "SUBCOMMANDS\n")
tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
for _, subcommand := range c.Subcommands {
fmt.Fprintf(tw, " %s\t%s\n", subcommand.Name, subcommand.ShortHelp)
}
tw.Flush()
fmt.Fprintf(&b, "\n")
}
if countFlags(c.FlagSet) > 0 {
fmt.Fprintf(&b, "FLAGS\n")
tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
c.FlagSet.VisitAll(func(f *flag.Flag) {
var s string
name, usage := flag.UnquoteUsage(f)
if isBoolFlag(f) {
s = fmt.Sprintf(" --%s, --%s=false", f.Name, f.Name)
} else {
s = fmt.Sprintf(" --%s", f.Name) // Two spaces before --; see next two comments.
if len(name) > 0 {
s += " " + name
}
}
// Four spaces before the tab triggers good alignment
// for both 4- and 8-space tab stops.
s += "\n \t"
s += strings.ReplaceAll(usage, "\n", "\n \t")
if f.DefValue != "" {
s += fmt.Sprintf(" (default %s)", f.DefValue)
}
fmt.Fprintln(&b, s)
})
tw.Flush()
fmt.Fprintf(&b, "\n")
}
return strings.TrimSpace(b.String())
}
func isBoolFlag(f *flag.Flag) bool {
bf, ok := f.Value.(interface {
IsBoolFlag() bool
})
return ok && bf.IsBoolFlag()
}
func countFlags(fs *flag.FlagSet) (n int) {
fs.VisitAll(func(*flag.Flag) { n++ })
return n
}

View File

@@ -6,10 +6,12 @@ package cli
import (
"context"
"fmt"
"log"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
)
@@ -26,6 +28,16 @@ func runDown(ctx context.Context, args []string) error {
log.Fatalf("too many non-flag arguments: %q", args)
}
st, err := tailscale.Status(ctx)
if err != nil {
return fmt.Errorf("error fetching current status: %w", err)
}
if st.BackendState == "Stopped" {
log.Printf("already stopped")
return nil
}
log.Printf("was in state %q", st.BackendState)
c, bc, ctx, cancel := connect(ctx)
defer cancel()
@@ -38,17 +50,6 @@ func runDown(ctx context.Context, args []string) error {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if n.Status != nil {
cur := n.Status.BackendState
switch cur {
case "Stopped":
log.Printf("already stopped")
cancel()
default:
log.Printf("was in state %q", cur)
}
return
}
if n.State != nil {
log.Printf("now in state %q", *n.State)
if *n.State == ipn.Stopped {
@@ -58,7 +59,6 @@ func runDown(ctx context.Context, args []string) error {
}
})
bc.RequestStatus()
bc.SetWantRunning(false)
pump(ctx, bc, c)

69
cmd/tailscale/cli/ip.go Normal file
View File

@@ -0,0 +1,69 @@
// 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 cli
import (
"context"
"errors"
"flag"
"fmt"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/client/tailscale"
)
var ipCmd = &ffcli.Command{
Name: "ip",
ShortUsage: "ip [-4] [-6]",
ShortHelp: "Show this machine's current Tailscale IP address(es)",
Exec: runIP,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("ip", flag.ExitOnError)
fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address")
fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address")
return fs
})(),
}
var ipArgs struct {
want4 bool
want6 bool
}
func runIP(ctx context.Context, args []string) error {
if len(args) > 0 {
return errors.New("unknown arguments")
}
v4, v6 := ipArgs.want4, ipArgs.want6
if v4 && v6 {
return errors.New("tailscale up -4 and -6 are mutually exclusive")
}
if !v4 && !v6 {
v4, v6 = true, true
}
st, err := tailscale.Status(ctx)
if err != nil {
return err
}
if len(st.TailscaleIPs) == 0 {
return fmt.Errorf("no current Tailscale IPs; state: %v", st.BackendState)
}
match := false
for _, ip := range st.TailscaleIPs {
if ip.Is4() && v4 || ip.Is6() && v6 {
match = true
fmt.Println(ip)
}
}
if !match {
if ipArgs.want4 {
return errors.New("no Tailscale IPv4 address")
}
if ipArgs.want6 {
return errors.New("no Tailscale IPv6 address")
}
}
return nil
}

View File

@@ -15,6 +15,7 @@ import (
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
)
@@ -47,6 +48,7 @@ relay node.
fs := flag.NewFlagSet("ping", flag.ExitOnError)
fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established")
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through IP + wireguard, but not involving host OS stack)")
fs.IntVar(&pingArgs.num, "c", 10, "max number of pings to send")
fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
return fs
@@ -57,6 +59,7 @@ var pingArgs struct {
num int
untilDirect bool
verbose bool
tsmp bool
timeout time.Duration
}
@@ -69,7 +72,6 @@ func runPing(ctx context.Context, args []string) error {
}
var ip string
prc := make(chan *ipnstate.PingResult, 1)
stc := make(chan *ipnstate.Status, 1)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
@@ -77,46 +79,15 @@ func runPing(ctx context.Context, args []string) error {
if pr := n.PingResult; pr != nil && pr.IP == ip {
prc <- pr
}
if n.Status != nil {
stc <- n.Status
}
})
go pump(ctx, bc, c)
hostOrIP := args[0]
// If the argument is an IP address, use it directly without any resolution.
if net.ParseIP(hostOrIP) != nil {
ip = hostOrIP
ip, err := tailscaleIPFromArg(ctx, hostOrIP)
if err != nil {
return err
}
// Otherwise, try to resolve it first from the network peer list.
if ip == "" {
bc.RequestStatus()
select {
case st := <-stc:
for _, ps := range st.Peer {
if hostOrIP == dnsOrQuoteHostname(st, ps) || hostOrIP == ps.DNSName {
ip = ps.TailAddr
break
}
}
case <-ctx.Done():
return ctx.Err()
}
}
// Finally, use DNS.
if ip == "" {
var res net.Resolver
if addrs, err := res.LookupHost(ctx, hostOrIP); err != nil {
return fmt.Errorf("error looking up IP of %q: %v", hostOrIP, err)
} else if len(addrs) == 0 {
return fmt.Errorf("no IPs found for %q", hostOrIP)
} else {
ip = addrs[0]
}
}
if pingArgs.verbose && ip != hostOrIP {
log.Printf("lookup %q => %q", hostOrIP, ip)
}
@@ -125,7 +96,7 @@ func runPing(ctx context.Context, args []string) error {
anyPong := false
for {
n++
bc.Ping(ip)
bc.Ping(ip, pingArgs.tsmp)
timer := time.NewTimer(pingArgs.timeout)
select {
case <-timer.C:
@@ -140,8 +111,20 @@ func runPing(ctx context.Context, args []string) error {
if pr.DERPRegionID != 0 {
via = fmt.Sprintf("DERP(%s)", pr.DERPRegionCode)
}
if pingArgs.tsmp {
// TODO(bradfitz): populate the rest of ipnstate.PingResult for TSMP queries?
// For now just say it came via TSMP.
via = "TSMP"
}
anyPong = true
fmt.Printf("pong from %s (%s) via %v in %v\n", pr.NodeName, pr.NodeIP, via, latency)
extra := ""
if pr.PeerAPIPort != 0 {
extra = fmt.Sprintf(", %d", pr.PeerAPIPort)
}
fmt.Printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
if pingArgs.tsmp {
return nil
}
if pr.Endpoint != "" && pingArgs.untilDirect {
return nil
}
@@ -157,3 +140,31 @@ func runPing(ctx context.Context, args []string) error {
}
}
}
func tailscaleIPFromArg(ctx context.Context, hostOrIP string) (ip string, err error) {
// If the argument is an IP address, use it directly without any resolution.
if net.ParseIP(hostOrIP) != nil {
return hostOrIP, nil
}
// Otherwise, try to resolve it first from the network peer list.
st, err := tailscale.Status(ctx)
if err != nil {
return "", err
}
for _, ps := range st.Peer {
if hostOrIP == dnsOrQuoteHostname(st, ps) || hostOrIP == ps.DNSName {
return ps.TailAddr, nil
}
}
// Finally, use DNS.
var res net.Resolver
if addrs, err := res.LookupHost(ctx, hostOrIP); err != nil {
return "", fmt.Errorf("error looking up IP of %q: %v", hostOrIP, err)
} else if len(addrs) == 0 {
return "", fmt.Errorf("no IPs found for %q", hostOrIP)
} else {
return addrs[0], nil
}
}

173
cmd/tailscale/cli/push.go Normal file
View File

@@ -0,0 +1,173 @@
// 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 cli
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"io"
"log"
"mime"
"net"
"net/http"
"net/url"
"os"
"time"
"unicode/utf8"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
)
var pushCmd = &ffcli.Command{
Name: "push",
ShortUsage: "push [--flags] <hostname-or-IP> <file>",
ShortHelp: "Push a file to a host",
Exec: runPush,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("push", flag.ExitOnError)
fs.StringVar(&pushArgs.name, "name", "", "alternate filename to use, especially useful when <file> is \"-\" (stdin)")
fs.BoolVar(&pushArgs.verbose, "verbose", false, "verbose output")
return fs
})(),
}
var pushArgs struct {
name string
verbose bool
}
func runPush(ctx context.Context, args []string) error {
if len(args) != 2 || args[0] == "" {
return errors.New("usage: push <hostname-or-IP> <file>")
}
var ip string
hostOrIP, fileArg := args[0], args[1]
ip, err := tailscaleIPFromArg(ctx, hostOrIP)
if err != nil {
return err
}
peerAPIPort, err := discoverPeerAPIPort(ctx, ip)
if err != nil {
return err
}
var fileContents io.Reader
var name = pushArgs.name
if fileArg == "-" {
fileContents = os.Stdin
if name == "" {
name, fileContents, err = pickStdinFilename()
if err != nil {
return err
}
}
} else {
f, err := os.Open(fileArg)
if err != nil {
return err
}
defer f.Close()
fileContents = f
if name == "" {
name = fileArg
}
}
dstURL := "http://" + net.JoinHostPort(ip, fmt.Sprint(peerAPIPort)) + "/v0/put/" + url.PathEscape(name)
req, err := http.NewRequestWithContext(ctx, "PUT", dstURL, fileContents)
if err != nil {
return err
}
if pushArgs.verbose {
log.Printf("sending to %v ...", dstURL)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == 200 {
return nil
}
io.Copy(os.Stdout, res.Body)
return errors.New(res.Status)
}
func discoverPeerAPIPort(ctx context.Context, ip string) (port uint16, err error) {
c, bc, ctx, cancel := connect(ctx)
defer cancel()
prc := make(chan *ipnstate.PingResult, 2)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if pr := n.PingResult; pr != nil && pr.IP == ip {
prc <- pr
}
})
go pump(ctx, bc, c)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
discoPings := 0
timer := time.NewTimer(10 * time.Second)
defer timer.Stop()
sendPings := func() {
bc.Ping(ip, false)
bc.Ping(ip, true)
}
sendPings()
for {
select {
case <-ticker.C:
sendPings()
case <-timer.C:
return 0, fmt.Errorf("timeout contacting %v; it offline?", ip)
case pr := <-prc:
if p := pr.PeerAPIPort; p != 0 {
return p, nil
}
discoPings++
if discoPings == 3 {
return 0, fmt.Errorf("%v is online, but seems to be running an old Tailscale version", ip)
}
case <-ctx.Done():
return 0, ctx.Err()
}
}
}
const maxSniff = 4 << 20
func ext(b []byte) string {
if len(b) < maxSniff && utf8.Valid(b) {
return ".txt"
}
if exts, _ := mime.ExtensionsByType(http.DetectContentType(b)); len(exts) > 0 {
return exts[0]
}
return ""
}
// pickStdinFilename reads a bit of stdin to return a good filename
// for its contents. The returned Reader is the concatenation of the
// read and unread bits.
func pickStdinFilename() (name string, r io.Reader, err error) {
sniff, err := io.ReadAll(io.LimitReader(os.Stdin, maxSniff))
if err != nil {
return "", nil, err
}
return "stdin" + ext(sniff), io.MultiReader(bytes.NewReader(sniff), os.Stdin), nil
}

View File

@@ -10,7 +10,6 @@ import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
@@ -19,6 +18,7 @@ import (
"github.com/peterbourgon/ff/v2/ffcli"
"github.com/toqueteos/webbrowser"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/interfaces"
@@ -27,7 +27,7 @@ import (
var statusCmd = &ffcli.Command{
Name: "status",
ShortUsage: "status [-active] [-web] [-json]",
ShortUsage: "status [--active] [--web] [--json]",
ShortHelp: "Show state of tailscaled and its connections",
Exec: runStatus,
FlagSet: (func() *flag.FlagSet {
@@ -53,47 +53,8 @@ var statusArgs struct {
peers bool // in CLI mode, show status of peer machines
}
func getStatusFromServer(ctx context.Context, c net.Conn, bc *ipn.BackendClient) func() (*ipnstate.Status, error) {
ch := make(chan *ipnstate.Status, 1)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if n.Status != nil {
select {
case ch <- n.Status:
default:
// A status update from somebody else's request.
// Ignoring this matters mostly for "tailscale status -web"
// mode, otherwise the channel send would block forever
// and pump would stop reading from tailscaled, which
// previously caused tailscaled to block (while holding
// a mutex), backing up unrelated clients.
// See https://github.com/tailscale/tailscale/issues/1234
}
}
})
go pump(ctx, bc, c)
return func() (*ipnstate.Status, error) {
bc.RequestStatus()
select {
case st := <-ch:
return st, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
func runStatus(ctx context.Context, args []string) error {
c, bc, ctx, cancel := connect(ctx)
defer cancel()
bc.AllowVersionSkew = true
getStatus := getStatusFromServer(ctx, c, bc)
st, err := getStatus()
st, err := tailscale.Status(ctx)
if err != nil {
return err
}
@@ -131,7 +92,7 @@ func runStatus(ctx context.Context, args []string) error {
http.NotFound(w, r)
return
}
st, err := getStatus()
st, err := tailscale.Status(ctx)
if err != nil {
http.Error(w, err.Error(), 500)
return

View File

@@ -21,6 +21,7 @@ import (
"github.com/peterbourgon/ff/v2/ffcli"
"inet.af/netaddr"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
"tailscale.com/types/preftype"
@@ -48,11 +49,11 @@ specify any flags, options are reset to their default.
upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale IP of the exit node for internet traffic")
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication")
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "ACL tags to request (comma-separated, e.g. eng,montreal,ssh)")
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "ACL tags to request (comma-separated, e.g. \"tag:eng,tag:montreal,tag:ssh\")")
upf.StringVar(&upArgs.authKey, "authkey", "", "node authorization key")
upf.StringVar(&upArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
if runtime.GOOS == "linux" || isBSD(runtime.GOOS) {
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. 10.0.0.0/8,192.168.0.0/24)")
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\")")
upf.BoolVar(&upArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
}
if runtime.GOOS == "linux" {
@@ -253,7 +254,7 @@ func runUp(ctx context.Context, args []string) error {
defer cancel()
if !prefs.ExitNodeIP.IsZero() {
st, err := getStatusFromServer(ctx, c, bc)()
st, err := tailscale.Status(ctx)
if err != nil {
fatalf("can't fetch status from tailscaled: %v", err)
}
@@ -315,7 +316,7 @@ func runUp(ctx context.Context, args []string) error {
// supports server mode, though, the transition to StateStore
// is only half complete. Only server mode uses it, and the
// Windows service (~tailscaled) is the one that computes the
// StateKey based on the connection idenity. So for now, just
// StateKey based on the connection identity. So for now, just
// do as the Windows GUI's always done:
if runtime.GOOS == "windows" {
// The Windows service will set this as needed based

View File

@@ -11,7 +11,7 @@ import (
"log"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/ipn"
"tailscale.com/client/tailscale"
"tailscale.com/version"
)
@@ -42,29 +42,10 @@ func runVersion(ctx context.Context, args []string) error {
fmt.Printf("Client: %s\n", version.String())
c, bc, ctx, cancel := connect(ctx)
defer cancel()
bc.AllowVersionSkew = true
done := make(chan struct{})
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if n.Status != nil {
fmt.Printf("Daemon: %s\n", n.Version)
close(done)
}
})
go pump(ctx, bc, c)
bc.RequestStatus()
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
st, err := tailscale.StatusWithoutPeers(ctx)
if err != nil {
return err
}
fmt.Printf("Daemon: %s\n", st.Version)
return nil
}

212
cmd/tailscale/cli/web.go Normal file
View File

@@ -0,0 +1,212 @@
// 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 cli
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"flag"
"fmt"
"html/template"
"log"
"net/http"
"net/http/cgi"
"os/exec"
"runtime"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/types/preftype"
"tailscale.com/version/distro"
)
//go:embed web.html
var webHTML string
var tmpl = template.Must(template.New("html").Parse(webHTML))
type tmplData struct {
SynologyUser string
Status string
DeviceName string
IP string
}
var webCmd = &ffcli.Command{
Name: "web",
ShortUsage: "web [flags]",
ShortHelp: "Run a web server for controlling Tailscale",
FlagSet: (func() *flag.FlagSet {
webf := flag.NewFlagSet("web", flag.ExitOnError)
webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic")
webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script")
return webf
})(),
Exec: runWeb,
}
var webArgs struct {
listen string
cgi bool
}
func runWeb(ctx context.Context, args []string) error {
if len(args) > 0 {
log.Fatalf("too many non-flag arguments: %q", args)
}
if webArgs.cgi {
return cgi.Serve(http.HandlerFunc(webHandler))
}
return http.ListenAndServe(webArgs.listen, http.HandlerFunc(webHandler))
}
func auth() (string, error) {
if distro.Get() == distro.Synology {
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("auth: %v: %s", err, out)
}
return string(out), nil
}
return "", nil
}
func synoTokenRedirect(w http.ResponseWriter, r *http.Request) bool {
if distro.Get() != distro.Synology {
return false
}
if r.Header.Get("X-Syno-Token") != "" {
return false
}
if r.URL.Query().Get("SynoToken") != "" {
return false
}
if r.Method == "POST" && r.FormValue("SynoToken") != "" {
return false
}
// We need a SynoToken for authenticate.cgi.
// So we tell the client to get one.
serverURL := r.URL.Scheme + "://" + r.URL.Host
fmt.Fprintf(w, synoTokenRedirectHTML, serverURL)
return true
}
const synoTokenRedirectHTML = `<html><body>
Redirecting with session token...
<script>
var serverURL = %q;
var req = new XMLHttpRequest();
req.overrideMimeType("application/json");
req.open("GET", serverURL + "/webman/login.cgi", true);
req.onload = function() {
var jsonResponse = JSON.parse(req.responseText);
var token = jsonResponse["SynoToken"];
document.location.href = serverURL + "/webman/3rdparty/Tailscale/?SynoToken=" + token;
};
req.send(null);
</script>
</body></html>
`
func webHandler(w http.ResponseWriter, r *http.Request) {
if synoTokenRedirect(w, r) {
return
}
user, err := auth()
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if r.Method == "POST" {
type mi map[string]interface{}
w.Header().Set("Content-Type", "application/json")
url, err := tailscaleUp(r.Context())
if err != nil {
json.NewEncoder(w).Encode(mi{"error": err})
return
}
json.NewEncoder(w).Encode(mi{"url": url})
return
}
st, err := tailscale.Status(r.Context())
if err != nil {
http.Error(w, err.Error(), 500)
}
data := tmplData{
SynologyUser: user,
Status: st.BackendState,
DeviceName: st.Self.DNSName,
}
if len(st.TailscaleIPs) != 0 {
data.IP = st.TailscaleIPs[0].String()
}
buf := new(bytes.Buffer)
if err := tmpl.Execute(buf, data); err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(buf.Bytes())
}
// TODO(crawshaw): some of this is very similar to the code in 'tailscale up', can we share anything?
func tailscaleUp(ctx context.Context) (authURL string, retErr error) {
prefs := ipn.NewPrefs()
prefs.ControlURL = "https://login.tailscale.com"
prefs.WantRunning = true
prefs.CorpDNS = true
prefs.AllowSingleHosts = true
prefs.ForceDaemon = (runtime.GOOS == "windows")
if distro.Get() == distro.Synology {
prefs.NetfilterMode = preftype.NetfilterOff
}
c, bc, ctx, cancel := connect(ctx)
defer cancel()
bc.SetPrefs(prefs)
opts := ipn.Options{
StateKey: ipn.GlobalDaemonStateKey,
Notify: func(n ipn.Notify) {
if n.ErrMessage != nil {
msg := *n.ErrMessage
if msg == ipn.ErrMsgPermissionDenied {
switch runtime.GOOS {
case "windows":
msg += " (Tailscale service in use by other user?)"
default:
msg += " (try 'sudo tailscale up [...]')"
}
}
retErr = fmt.Errorf("backend error: %v", msg)
cancel()
} else if url := n.BrowseToURL; url != nil {
authURL = *url
cancel()
}
},
}
bc.Start(opts)
bc.StartLoginInteractive()
pump(ctx, bc, c)
if authURL == "" && retErr == nil {
return "", fmt.Errorf("login failed with no backend error message")
}
return authURL, retErr
}

View File

@@ -0,0 +1,47 @@
<!doctype html>
<html><title>Tailscale Client</title><body>
<h1>Tailscale</h1>
<div style="float:right;">{{.SynologyUser}}</div>
<table>
<tr><th>Status:</th><td>{{.Status}}</td></tr>
<tr><th>Device Name:</th><td>{{.DeviceName}}</td></tr>
<tr><th>Tailscale IP:</th><td>{{.IP}}</td></tr>
</table>
<p><input id="login" type="button" value="Log in…"></p>
<script>
login.onclick = function() {
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get("SynoToken");
var params = new URLSearchParams("up=true");
if (token) {
params.set("SynoToken", token)
}
var req = new XMLHttpRequest();
const url = [location.protocol, '//', location.host, location.pathname, "?", params.toString()].join('');
req.overrideMimeType("application/json");
req.open("POST", url, true);
req.onload = function() {
var jsonResponse = JSON.parse(req.responseText);
const err = jsonResponse["error"];
if (err) {
document.body.innerText = err;
return
}
var url = jsonResponse["url"];
console.log("jsonResponse: ", jsonResponse);
if (url) {
document.location.href = url;
} else {
//location.reload();
}
};
req.send(null);
}
</script>
</body>
</html>

View File

@@ -39,6 +39,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/types/empty from tailscale.com/ipn
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
tailscale.com/types/key from tailscale.com/derp+
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/netmap from tailscale.com/ipn
@@ -50,7 +51,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/wgkey from tailscale.com/types/netmap+
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
W tailscale.com/util/endian from tailscale.com/net/netns
LW tailscale.com/util/lineread from tailscale.com/net/interfaces
L tailscale.com/util/lineread from tailscale.com/net/interfaces
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli
tailscale.com/wgengine/filter from tailscale.com/types/netmap
@@ -65,16 +66,13 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal
golang.org/x/net/dns/dnsmessage from net
golang.org/x/net/http/httpguts from net/http
golang.org/x/net/http/httpguts from net/http+
golang.org/x/net/http/httpproxy from net/http
golang.org/x/net/http2/hpack from net/http
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
golang.org/x/oauth2 from tailscale.com/ipn+
golang.org/x/oauth2/internal from golang.org/x/oauth2
golang.org/x/sync/errgroup from tailscale.com/derp
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
@@ -117,6 +115,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
debug/elf from rsc.io/goversion/version
debug/macho from rsc.io/goversion/version
debug/pe from rsc.io/goversion/version
embed from tailscale.com/cmd/tailscale/cli
encoding from encoding/json
encoding/asn1 from crypto/x509+
encoding/base64 from encoding/json+
@@ -132,20 +131,22 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+
hash/maphash from go4.org/mem
html from tailscale.com/ipn/ipnstate
html from tailscale.com/ipn/ipnstate+
html/template from tailscale.com/cmd/tailscale/cli
io from bufio+
io/fs from crypto/rand+
io/ioutil from golang.org/x/oauth2/internal+
io/ioutil from golang.org/x/sys/cpu+
log from expvar+
math from compress/flate+
math/big from crypto/dsa+
math/bits from compress/flate+
math/rand from math/big+
mime from golang.org/x/oauth2/internal+
mime from mime/multipart+
mime/multipart from net/http
mime/quotedprintable from mime/multipart
net from crypto/tls+
net/http from expvar+
net/http/cgi from tailscale.com/cmd/tailscale/cli
net/http/httptrace from github.com/tcnksm/go-httpstat+
net/http/internal from net/http
net/textproto from golang.org/x/net/http/httpguts+
@@ -156,7 +157,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
path from debug/dwarf+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from rsc.io/goversion/version
regexp from rsc.io/goversion/version+
regexp/syntax from regexp
runtime/debug from golang.org/x/sync/singleflight
sort from compress/flate+
@@ -165,7 +166,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
sync from compress/flate+
sync/atomic from context+
syscall from crypto/rand+
text/tabwriter from github.com/peterbourgon/ff/v2/ffcli
text/tabwriter from github.com/peterbourgon/ff/v2/ffcli+
text/template from html/template
text/template/parse from html/template+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from encoding/asn1+

View File

@@ -9,12 +9,18 @@ package main // import "tailscale.com/cmd/tailscale"
import (
"fmt"
"os"
"path/filepath"
"strings"
"tailscale.com/cmd/tailscale/cli"
)
func main() {
if err := cli.Run(os.Args[1:]); err != nil {
args := os.Args[1:]
if name, _ := os.Executable(); strings.HasSuffix(filepath.Base(name), ".cgi") {
args = []string{"web", "-cgi"}
}
if err := cli.Run(args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

View File

@@ -3,11 +3,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
W 💣 github.com/github/certstore from tailscale.com/control/controlclient
github.com/go-multierror/multierror from tailscale.com/wgengine/router+
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/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
github.com/google/btree from inet.af/netstack/tcpip/header+
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
@@ -19,6 +20,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
W github.com/pkg/errors from github.com/github/certstore
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+
💣 github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device
@@ -34,37 +36,37 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 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/network/ipv6 from tailscale.com/wgengine/netstack
gvisor.dev/gvisor/pkg/tcpip/ports from gvisor.dev/gvisor/pkg/tcpip/stack+
gvisor.dev/gvisor/pkg/tcpip/seqnum from gvisor.dev/gvisor/pkg/tcpip/header+
gvisor.dev/gvisor/pkg/tcpip/stack from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
gvisor.dev/gvisor/pkg/tcpip/transport/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+
💣 inet.af/netstack/gohacks from inet.af/netstack/state/wire+
inet.af/netstack/linewriter from inet.af/netstack/log
inet.af/netstack/log from inet.af/netstack/state+
inet.af/netstack/rand from inet.af/netstack/tcpip/network/hash+
💣 inet.af/netstack/sleep from inet.af/netstack/tcpip/transport/tcp
💣 inet.af/netstack/state from inet.af/netstack/tcpip+
inet.af/netstack/state/wire from inet.af/netstack/state
💣 inet.af/netstack/sync from inet.af/netstack/linewriter+
💣 inet.af/netstack/tcpip from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
💣 inet.af/netstack/tcpip/buffer from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/tcpip/hash/jenkins from inet.af/netstack/tcpip/stack+
inet.af/netstack/tcpip/header from inet.af/netstack/tcpip/header/parse+
inet.af/netstack/tcpip/header/parse from inet.af/netstack/tcpip/network/ipv4+
inet.af/netstack/tcpip/link/channel from tailscale.com/wgengine/netstack
inet.af/netstack/tcpip/network/hash from inet.af/netstack/tcpip/network/ipv4+
inet.af/netstack/tcpip/network/internal/fragmentation from inet.af/netstack/tcpip/network/ipv4+
inet.af/netstack/tcpip/network/internal/ip from inet.af/netstack/tcpip/network/ipv4+
inet.af/netstack/tcpip/network/ipv4 from tailscale.com/wgengine/netstack
inet.af/netstack/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
inet.af/netstack/tcpip/ports from inet.af/netstack/tcpip/stack+
inet.af/netstack/tcpip/seqnum from inet.af/netstack/tcpip/header+
💣 inet.af/netstack/tcpip/stack from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/tcpip/transport/icmp from tailscale.com/wgengine/netstack
inet.af/netstack/tcpip/transport/packet from inet.af/netstack/tcpip/transport/raw
inet.af/netstack/tcpip/transport/raw from inet.af/netstack/tcpip/transport/icmp+
💣 inet.af/netstack/tcpip/transport/tcp from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/tcpip/transport/tcpconntrack from inet.af/netstack/tcpip/stack
inet.af/netstack/tcpip/transport/udp from inet.af/netstack/tcpip/adapters/gonet+
inet.af/netstack/waiter from inet.af/netstack/tcpip+
inet.af/peercred from tailscale.com/ipn/ipnserver
rsc.io/goversion/version from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn+
@@ -88,6 +90,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/logtail/backoff from tailscale.com/control/controlclient+
tailscale.com/logtail/filch from tailscale.com/logpolicy
tailscale.com/metrics from tailscale.com/derp
tailscale.com/net/dns from tailscale.com/ipn/ipnlocal+
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
tailscale.com/net/dnsfallback from tailscale.com/control/controlclient
tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+
@@ -102,6 +105,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
tailscale.com/paths from tailscale.com/cmd/tailscaled+
tailscale.com/portlist from tailscale.com/ipn/ipnlocal
tailscale.com/safesocket from tailscale.com/ipn/ipnserver
@@ -113,6 +117,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/tstime from tailscale.com/wgengine/magicsock
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
tailscale.com/types/key from tailscale.com/derp+
tailscale.com/types/logger from tailscale.com/cmd/tailscaled+
tailscale.com/types/netmap from tailscale.com/control/controlclient+
@@ -124,12 +129,13 @@ 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/wgengine/tsdns+
tailscale.com/util/dnsname from tailscale.com/ipn/ipnstate+
LW tailscale.com/util/endian from tailscale.com/net/netns+
LW tailscale.com/util/lineread from tailscale.com/control/controlclient+
L tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
tailscale.com/util/racebuild from tailscale.com/logpolicy
tailscale.com/util/systemd from tailscale.com/control/controlclient+
tailscale.com/util/winutil from tailscale.com/logpolicy+
tailscale.com/version from tailscale.com/cmd/tailscaled+
tailscale.com/version/distro from tailscale.com/control/controlclient+
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
@@ -138,9 +144,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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/ipnlocal+
tailscale.com/wgengine/tsdns from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/tstun from tailscale.com/wgengine+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
tailscale.com/wgengine/wglog from tailscale.com/wgengine
@@ -158,7 +161,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http
golang.org/x/net/http/httpproxy from net/http
@@ -168,8 +170,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/device+
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
golang.org/x/oauth2 from tailscale.com/control/controlclient+
golang.org/x/oauth2/internal from golang.org/x/oauth2
golang.org/x/sync/errgroup from tailscale.com/derp
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
@@ -188,7 +188,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/heap from inet.af/netstack/tcpip/transport/tcp
container/list from crypto/tls+
context from crypto/tls+
crypto from crypto/ecdsa+
@@ -240,7 +240,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
math/big from crypto/dsa+
math/bits from compress/flate+
math/rand from github.com/mdlayher/netlink+
mime from golang.org/x/oauth2/internal+
mime from mime/multipart+
mime/multipart from net/http
mime/quotedprintable from mime/multipart
net from crypto/tls+

View File

@@ -32,6 +32,7 @@ import (
"tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy"
"tailscale.com/net/socks5"
"tailscale.com/net/tstun"
"tailscale.com/paths"
"tailscale.com/types/flagtype"
"tailscale.com/types/logger"
@@ -43,7 +44,6 @@ import (
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/netstack"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/tstun"
)
// globalStateKey is the ipn.StateKey that tailscaled loads on
@@ -229,7 +229,10 @@ func run() error {
var ns *netstack.Impl
if useNetstack {
tunDev, magicConn := e.(wgengine.InternalsGetter).GetInternals()
tunDev, magicConn, ok := e.(wgengine.InternalsGetter).GetInternals()
if !ok {
log.Fatalf("%T is not a wgengine.InternalsGetter", e)
}
ns, err = netstack.Create(logf, tunDev, e, magicConn)
if err != nil {
log.Fatalf("netstack.Create: %v", err)
@@ -316,18 +319,7 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, is
var errs []error
for _, name := range strings.Split(args.tunname, ",") {
logf("wgengine.NewUserspaceEngine(tun %q) ...", name)
conf := wgengine.Config{
ListenPort: args.port,
LinkMonitor: linkMon,
}
isUserspace = name == "userspace-networking"
if isUserspace {
conf.TUN = tstun.NewFakeTUN()
conf.RouterGen = router.NewFake
} else {
conf.TUNName = name
}
e, err := wgengine.NewUserspaceEngine(logf, conf)
e, isUserspace, err = tryEngine(logf, linkMon, name)
if err == nil {
return e, isUserspace, nil
}
@@ -337,6 +329,33 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, is
return nil, false, multierror.New(errs)
}
func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.Engine, isUserspace bool, err error) {
conf := wgengine.Config{
ListenPort: args.port,
LinkMonitor: linkMon,
}
isUserspace = name == "userspace-networking"
if !isUserspace {
dev, err := tstun.New(logf, name)
if err != nil {
tstun.Diagnose(logf, name)
return nil, false, err
}
conf.Tun = dev
r, err := router.New(logf, dev)
if err != nil {
dev.Close()
return nil, false, err
}
conf.Router = r
}
e, err = wgengine.NewUserspaceEngine(logf, conf)
if err != nil {
return nil, isUserspace, err
}
return e, isUserspace, nil
}
func newDebugMux() *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", pprof.Index)

View File

@@ -30,10 +30,12 @@ import (
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy"
"tailscale.com/net/tstun"
"tailscale.com/tempfork/wireguard-windows/firewall"
"tailscale.com/types/logger"
"tailscale.com/version"
"tailscale.com/wgengine"
"tailscale.com/wgengine/router"
)
const serviceName = "Tailscale"
@@ -159,11 +161,23 @@ func startIPNServer(ctx context.Context, logid string) error {
var err error
getEngine := func() (wgengine.Engine, error) {
dev, err := tstun.New(logf, "Tailscale")
if err != nil {
return nil, err
}
r, err := router.New(logf, dev)
if err != nil {
dev.Close()
return nil, err
}
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
TUNName: "Tailscale",
Tun: dev,
Router: r,
ListenPort: 41641,
})
if err != nil {
r.Close()
dev.Close()
return nil, err
}
return wgengine.NewWatchdog(eng), nil

View File

@@ -17,7 +17,6 @@ import (
"sync"
"time"
"golang.org/x/oauth2"
"tailscale.com/health"
"tailscale.com/logtail/backoff"
"tailscale.com/tailcfg"
@@ -102,10 +101,10 @@ func (s Status) String() string {
type LoginGoal struct {
_ structs.Incomparable
wantLoggedIn bool // true if we *want* to be logged in
token *oauth2.Token // oauth token to use when logging in
flags LoginFlags // flags to use when logging in
url string // auth url that needs to be visited
wantLoggedIn bool // true if we *want* to be logged in
token *tailcfg.Oauth2Token // oauth token to use when logging in
flags LoginFlags // flags to use when logging in
url string // auth url that needs to be visited
}
// Client connects to a tailcontrol server for a node.
@@ -605,7 +604,6 @@ func (c *Client) SetHostinfo(hi *tailcfg.Hostinfo) {
// No changes. Don't log.
return
}
c.logf("Hostinfo: %v", hi)
// Send new Hostinfo to server
c.sendNewMapRequest()
@@ -669,7 +667,7 @@ func (c *Client) sendStatus(who string, err error, url string, nm *netmap.Networ
c.mu.Unlock()
}
func (c *Client) Login(t *oauth2.Token, flags LoginFlags) {
func (c *Client) Login(t *tailcfg.Oauth2Token, flags LoginFlags) {
c.logf("client.Login(%v, %v)", t != nil, flags)
c.mu.Lock()

View File

@@ -31,7 +31,6 @@ import (
"time"
"golang.org/x/crypto/nacl/box"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/health"
"tailscale.com/log/logheap"
@@ -266,7 +265,7 @@ func (c *Direct) TryLogout(ctx context.Context) error {
return nil
}
func (c *Direct) TryLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags) (url string, err error) {
func (c *Direct) TryLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags) (url string, err error) {
c.logf("direct.TryLogin(token=%v, flags=%v)", t != nil, flags)
return c.doLoginOrRegen(ctx, t, flags, false, "")
}
@@ -276,7 +275,7 @@ func (c *Direct) WaitLoginURL(ctx context.Context, url string) (newUrl string, e
return c.doLoginOrRegen(ctx, nil, LoginDefault, false, url)
}
func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags LoginFlags, regen bool, url string) (newUrl string, err error) {
func (c *Direct) doLoginOrRegen(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags, regen bool, url string) (newUrl string, err error) {
mustregen, url, err := c.doLogin(ctx, t, flags, regen, url)
if err != nil {
return url, err
@@ -288,7 +287,7 @@ func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags Logi
return url, err
}
func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags, regen bool, url string) (mustregen bool, newurl string, err error) {
func (c *Direct) doLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags, regen bool, url string) (mustregen bool, newurl string, err error) {
c.mu.Lock()
persist := c.persist
tryingNewKey := c.tryingNewKey
@@ -352,12 +351,14 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
err = errors.New("hostinfo: BackendLogID missing")
return regen, url, err
}
now := time.Now().Round(time.Second)
request := tailcfg.RegisterRequest{
Version: 1,
OldNodeKey: tailcfg.NodeKey(oldNodeKey),
NodeKey: tailcfg.NodeKey(tryingNewKey.Public()),
Hostinfo: hostinfo,
Followup: url,
Timestamp: &now,
}
c.logf("RegisterReq: onode=%v node=%v fup=%v",
request.OldNodeKey.ShortString(),
@@ -366,6 +367,20 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
request.Auth.Provider = persist.Provider
request.Auth.LoginName = persist.LoginName
request.Auth.AuthKey = authKey
err = signRegisterRequest(&request, c.serverURL, c.serverKey, c.machinePrivKey.Public())
if err != nil {
// If signing failed, clear all related fields
request.SignatureType = tailcfg.SignatureNone
request.Timestamp = nil
request.DeviceCert = nil
request.Signature = nil
// Don't log the common error types. Signatures are not usually enabled,
// so these are expected.
if err != errCertificateNotConfigured && err != errNoCertStore {
c.logf("RegisterReq sign error: %v", err)
}
}
bodyData, err := encode(request, &serverKey, &c.machinePrivKey)
if err != nil {
return regen, url, err
@@ -935,7 +950,7 @@ func encode(v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) ([]byte, e
return nil, err
}
if debugMap {
if _, ok := v.(tailcfg.MapRequest); ok {
if _, ok := v.(*tailcfg.MapRequest); ok {
log.Printf("MapRequest: %s", b)
}
}

View File

@@ -0,0 +1,31 @@
// 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 controlclient
import (
"crypto"
"errors"
"fmt"
"time"
"tailscale.com/types/wgkey"
)
var (
errNoCertStore = errors.New("no certificate store")
errCertificateNotConfigured = errors.New("no certificate subject configured")
)
// HashRegisterRequest generates the hash required sign or verify a
// tailcfg.RegisterRequest with tailcfg.SignatureV1.
func HashRegisterRequest(ts time.Time, serverURL string, deviceCert []byte, serverPubKey, machinePubKey wgkey.Key) []byte {
h := crypto.SHA256.New()
// hash.Hash.Write never returns an error, so we don't check for one here.
fmt.Fprintf(h, "%s%s%s%s%s",
ts.UTC().Format(time.RFC3339), serverURL, deviceCert, serverPubKey, machinePubKey)
return h.Sum(nil)
}

View File

@@ -0,0 +1,160 @@
// 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.
// +build windows,cgo
// darwin,cgo is also supported by certstore but machineCertificateSubject will
// need to be loaded by a different mechanism, so this is not currently enabled
// on darwin.
package controlclient
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"errors"
"fmt"
"sync"
"github.com/github/certstore"
"tailscale.com/tailcfg"
"tailscale.com/types/wgkey"
"tailscale.com/util/winutil"
)
var getMachineCertificateSubjectOnce struct {
sync.Once
v string // Subject of machine certificate to search for
}
// getMachineCertificateSubject returns the exact name of a Subject that needs
// to be present in an identity's certificate chain to sign a RegisterRequest,
// formatted as per pkix.Name.String(). The Subject may be that of the identity
// itself, an intermediate CA or the root CA.
//
// If getMachineCertificateSubject() returns "" then no lookup will occur and
// each RegisterRequest will be unsigned.
//
// Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA"
func getMachineCertificateSubject() string {
getMachineCertificateSubjectOnce.Do(func() {
getMachineCertificateSubjectOnce.v = winutil.GetRegString("MachineCertificateSubject", "")
})
return getMachineCertificateSubjectOnce.v
}
var (
errNoMatch = errors.New("no matching certificate")
errBadRequest = errors.New("malformed request")
)
// findIdentity locates an identity from the Windows or Darwin certificate
// store. It returns the first certificate with a matching Subject anywhere in
// its certificate chain, so it is possible to search for the leaf certificate,
// intermediate CA or root CA. If err is nil then the returned identity will
// never be nil (if no identity is found, the error errNoMatch will be
// returned). If an identity is returned then its certificate chain is also
// returned.
func findIdentity(subject string, st certstore.Store) (certstore.Identity, []*x509.Certificate, error) {
ids, err := st.Identities()
if err != nil {
return nil, nil, err
}
var selected certstore.Identity
var chain []*x509.Certificate
for _, id := range ids {
chain, err = id.CertificateChain()
if err != nil {
continue
}
if chain[0].PublicKeyAlgorithm != x509.RSA {
continue
}
for _, c := range chain {
if c.Subject.String() == subject {
selected = id
break
}
}
}
for _, id := range ids {
if id != selected {
id.Close()
}
}
if selected == nil {
return nil, nil, errNoMatch
}
return selected, chain, nil
}
// signRegisterRequest looks for a suitable machine identity from the local
// system certificate store, and if one is found, signs the RegisterRequest
// using that identity's public key. In addition to the signature, the full
// certificate chain is included so that the control server can validate the
// certificate from a copy of the root CA's certificate.
func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey wgkey.Key) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("signRegisterRequest: %w", err)
}
}()
if req.Timestamp == nil {
return errBadRequest
}
machineCertificateSubject := getMachineCertificateSubject()
if machineCertificateSubject == "" {
return errCertificateNotConfigured
}
st, err := certstore.Open(certstore.System)
if err != nil {
return fmt.Errorf("open cert store: %w", err)
}
defer st.Close()
id, chain, err := findIdentity(machineCertificateSubject, st)
if err != nil {
return fmt.Errorf("find identity: %w", err)
}
defer id.Close()
signer, err := id.Signer()
if err != nil {
return fmt.Errorf("create signer: %w", err)
}
cl := 0
for _, c := range chain {
cl += len(c.Raw)
}
req.DeviceCert = make([]byte, 0, cl)
for _, c := range chain {
req.DeviceCert = append(req.DeviceCert, c.Raw...)
}
h := HashRegisterRequest(req.Timestamp.UTC(), serverURL, req.DeviceCert, serverPubKey, machinePubKey)
req.Signature, err = signer.Sign(nil, h, &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: crypto.SHA256,
})
if err != nil {
return fmt.Errorf("sign: %w", err)
}
req.SignatureType = tailcfg.SignatureV1
return nil
}

View File

@@ -0,0 +1,17 @@
// 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.
// +build !windows !cgo
package controlclient
import (
"tailscale.com/tailcfg"
"tailscale.com/types/wgkey"
)
// signRegisterRequest on non-supported platforms always returns errNoCertStore.
func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey wgkey.Key) error {
return errNoCertStore
}

24
go.mod
View File

@@ -5,14 +5,13 @@ go 1.16
require (
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29
github.com/coreos/go-iptables v0.4.5
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/github/certstore v0.1.0
github.com/gliderlabs/ssh v0.2.2
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/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-20210212075122-66c871082f2b
@@ -23,23 +22,26 @@ require (
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/pkg/errors v0.9.1 // indirect
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222
github.com/tailscale/wireguard-go v0.0.0-20210327173134-f6a42a1646a0
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
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
gopkg.in/yaml.v2 v2.2.8 // indirect
honnef.co/go/tools v0.1.0
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
inet.af/peercred v0.0.0-20210302202138-56e694897155
rsc.io/goversion v1.2.0
)
replace github.com/github/certstore => github.com/cyolosecurity/certstore v0.0.0-20200922073901-ece7f1d353c2

392
go.sum
View File

@@ -1,34 +1,7 @@
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=
@@ -36,148 +9,48 @@ github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEK
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29/go.mod h1:JYWahgHer+Z2xbsgHPtaDYVWzeHDminu+YIBWkxpCAY=
github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab h1:CMGzRRCjnD50RjUFSArBLuCxiDvdp7b8YPAcikBEQ+k=
github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab/go.mod h1:nfFtvHn2Hgs9G1u0/J6LHQv//EksNC+7G8vXmd1VTJ8=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
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/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/cyolosecurity/certstore v0.0.0-20200922073901-ece7f1d353c2 h1:TGPWAij+nY2FB7TlyUTqTmYvXJon/AZAfRMYc/76K80=
github.com/cyolosecurity/certstore v0.0.0-20200922073901-ece7f1d353c2/go.mod h1:Sgb3YVYOB2iCO06NJ6We5gjXe7uxxM3zPYoEXjuTKno=
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/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
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/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/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/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
@@ -188,29 +61,18 @@ github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
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/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
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-20201110160827-18ea5e372cdb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
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/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
@@ -231,32 +93,11 @@ 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=
@@ -264,59 +105,34 @@ github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnI
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/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-20210210202228-3cc76ed5f222 h1:VzTS7LIwCH8jlxwrZguU0TsCLV/MDOunoNIDJdFajyM=
github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924=
github.com/tailscale/wireguard-go v0.0.0-20210324165952-2963b66bc23a h1:tQ7Y0ALSe5109GMFB7TVtfNBsVcAuM422hVSJrXWMTE=
github.com/tailscale/wireguard-go v0.0.0-20210324165952-2963b66bc23a/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924=
github.com/tailscale/wireguard-go v0.0.0-20210327173134-f6a42a1646a0 h1:7KFBvUmm3TW/K+bAN22D7M6xSSoY/39s+PajaNBGrLw=
github.com/tailscale/wireguard-go v0.0.0-20210327173134-f6a42a1646a0/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924=
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-20210101010959-7cab76ca296a h1:28p852HIWWaOS019DYK/A3yTmpm1HJaUce63pvll4C8=
go4.org/intern v0.0.0-20210101010959-7cab76ca296a/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 h1:vSug/WNOi2+4jrKdivxayTN/zd8EA1UrStjpWvvo1jk=
@@ -324,61 +140,30 @@ go4.org/mem v0.0.0-20201119185036-c04c5a6ff174/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus
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-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-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-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/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/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -386,58 +171,28 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/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/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -446,49 +201,25 @@ golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b h1:kHlr0tATeLRMEiZJu5CknOw/E8V6h69sXXQFGoPtjcc=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e h1:XNp2Flc/1eWQGk5BLzqTAN7fQIwIbfyVTuVxXxZh73M=
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q=
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/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-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-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=
@@ -498,89 +229,26 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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 v0.0.20201118/go.mod h1:Dz+cq5bnrai9EpgYj4GDof/+qaGzbRWbeaAOs1bUYa0=
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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-20210105212526-648fbc18a69d h1:6f0242aW/6x2enQBOSKgDS8KQNw6Tp7IVR8eG3x0Jc8=
inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d/go.mod h1:jPZo7Jy4nke2cCgISa4fKJKa5T7+EO8k5fWwWghzneg=
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44 h1:p7fX77zWzZMuNdJUhniBsmN1OvFOrW9SOtvgnzqUZX4=
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44/go.mod h1:I2i9ONCXRZDnG1+7O8fSuYzjcPxHQXrIfzD/IkR87x4=
inet.af/peercred v0.0.0-20210216231719-993aa01eacaa h1:6qseJO2iNDHl+MLL2BkO5oURJR4A9pLmRz11Yf7KdGM=
inet.af/peercred v0.0.0-20210216231719-993aa01eacaa/go.mod h1:VZeNdG7cRIUqKl9DWoFX86AHyfYwdb4RextAw1CAEO4=
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22 h1:DNtszwGa6w76qlIr+PbPEnlBJdiRV8SaxeigOy0q1gg=
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22/go.mod h1:GVx+5OZtbG4TVOW5ilmyRZAZXr1cNwfqUEkTOtWK0PM=
inet.af/peercred v0.0.0-20210302202138-56e694897155 h1:KojYNEYqDkZ2O3LdyTstR1l13L3ePKTIEM2h7ONkfkE=
inet.af/peercred v0.0.0-20210302202138-56e694897155/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs=
k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
k8s.io/client-go v0.16.13/go.mod h1:UKvVT4cajC2iN7DCjLgT0KVY/cbY6DGdUCyRiIfws5M=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20200410163147-594e756bea31/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -35,6 +35,7 @@ var (
lastMapRequestHeard time.Time // time we got a 200 from control for a MapRequest
ipnState string
ipnWantRunning bool
anyInterfaceUp = true // until told otherwise
)
// Subsystem is the name of a subsystem whose health can be monitored.
@@ -195,6 +196,14 @@ func SetIPNState(state string, wantRunning bool) {
selfCheckLocked()
}
// SetAnyInterfaceUp sets whether any network interface is up.
func SetAnyInterfaceUp(up bool) {
mu.Lock()
defer mu.Unlock()
anyInterfaceUp = up
selfCheckLocked()
}
func timerSelfCheck() {
mu.Lock()
defer mu.Unlock()
@@ -213,6 +222,9 @@ func selfCheckLocked() {
}
func overallErrorLocked() error {
if !anyInterfaceUp {
return errors.New("network down")
}
if ipnState != "Running" || !ipnWantRunning {
return fmt.Errorf("state=%v, wantRunning=%v", ipnState, ipnWantRunning)
}

View File

@@ -9,8 +9,8 @@ import (
"testing"
"inet.af/netaddr"
"tailscale.com/net/dns"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/router/dns"
"tailscale.com/wgengine/wgcfg"
)

View File

@@ -8,7 +8,6 @@ import (
"net/http"
"time"
"golang.org/x/oauth2"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
"tailscale.com/types/empty"
@@ -28,7 +27,7 @@ const (
Running
)
// GoogleIDToken Type is the oauth2.Token.TokenType for the Google
// GoogleIDToken Type is the tailcfg.Oauth2Token.TokenType for the Google
// ID tokens used by the Android client.
const GoogleIDTokenType = "ts_android_google_login"
@@ -65,7 +64,6 @@ type Notify struct {
Prefs *Prefs // preferences were changed
NetMap *netmap.NetworkMap // new netmap received
Engine *EngineStatus // wireguard engine stats
Status *ipnstate.Status // full status
BrowseToURL *string // UI should open a browser right now
BackendLogID *string // public logtail id used by backend
PingResult *ipnstate.PingResult
@@ -143,7 +141,7 @@ type Backend interface {
// eventually.
StartLoginInteractive()
// Login logs in with an OAuth2 token.
Login(token *oauth2.Token)
Login(token *tailcfg.Oauth2Token)
// Logout terminates the current login session and stops the
// wireguard engine.
Logout()
@@ -159,9 +157,6 @@ type Backend interface {
// counts. Connection events are emitted automatically without
// polling.
RequestEngineStatus()
// RequestStatus requests that a full Status update
// notification is sent.
RequestStatus()
// FakeExpireAfter pretends that the current key is going to
// expire after duration x. This is useful for testing GUIs to
// make sure they react properly with keys that are going to
@@ -170,5 +165,5 @@ type Backend interface {
// Ping attempts to start connecting to the given IP and sends a Notify
// with its PingResult. If the host is down, there might never
// be a PingResult sent. The cmd/tailscale CLI client adds a timeout.
Ping(ip string)
Ping(ip string, useTSMP bool)
}

View File

@@ -8,8 +8,8 @@ import (
"log"
"time"
"golang.org/x/oauth2"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
)
@@ -46,7 +46,7 @@ func (b *FakeBackend) StartLoginInteractive() {
b.login()
}
func (b *FakeBackend) Login(token *oauth2.Token) {
func (b *FakeBackend) Login(token *tailcfg.Oauth2Token) {
b.login()
}
@@ -87,14 +87,10 @@ func (b *FakeBackend) RequestEngineStatus() {
b.notify(Notify{Engine: &EngineStatus{}})
}
func (b *FakeBackend) RequestStatus() {
b.notify(Notify{Status: &ipnstate.Status{}})
}
func (b *FakeBackend) FakeExpireAfter(x time.Duration) {
b.notify(Notify{NetMap: &netmap.NetworkMap{}})
}
func (b *FakeBackend) Ping(ip string) {
func (b *FakeBackend) Ping(ip string, useTSMP bool) {
b.notify(Notify{PingResult: &ipnstate.PingResult{}})
}

View File

@@ -8,8 +8,8 @@ import (
"sync"
"time"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
)
@@ -155,7 +155,7 @@ func (h *Handle) StartLoginInteractive() {
h.b.StartLoginInteractive()
}
func (h *Handle) Login(token *oauth2.Token) {
func (h *Handle) Login(token *tailcfg.Oauth2Token) {
h.b.Login(token)
}
@@ -167,10 +167,6 @@ func (h *Handle) RequestEngineStatus() {
h.b.RequestEngineStatus()
}
func (h *Handle) RequestStatus() {
h.b.RequestStatus()
}
func (h *Handle) FakeExpireAfter(x time.Duration) {
h.b.FakeExpireAfter(x)
}

View File

@@ -9,13 +9,15 @@ import (
"context"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/health"
@@ -23,8 +25,10 @@ import (
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/policy"
"tailscale.com/net/dns"
"tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
"tailscale.com/paths"
"tailscale.com/portlist"
"tailscale.com/tailcfg"
"tailscale.com/types/empty"
@@ -38,8 +42,6 @@ import (
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/router/dns"
"tailscale.com/wgengine/tsdns"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wgcfg/nmcfg"
)
@@ -96,15 +98,16 @@ type LocalBackend struct {
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo
// netMap is not mutated in-place once set.
netMap *netmap.NetworkMap
nodeByAddr map[netaddr.IP]*tailcfg.Node
activeLogin string // last logged LoginName from netMap
engineStatus ipn.EngineStatus
endpoints []string
blocked bool
authURL string
interact bool
prevIfState *interfaces.State
netMap *netmap.NetworkMap
nodeByAddr map[netaddr.IP]*tailcfg.Node
activeLogin string // last logged LoginName from netMap
engineStatus ipn.EngineStatus
endpoints []string
blocked bool
authURL string
interact bool
prevIfState *interfaces.State
peerAPIListeners []*peerAPIListener
// statusLock must be held before calling statusChanged.Wait() or
// statusChanged.Broadcast().
@@ -144,6 +147,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge
b.statusChanged = sync.NewCond(&b.statusLock)
linkMon := e.GetLinkMonitor()
b.prevIfState = linkMon.InterfaceState()
// Call our linkChange code once with the current state, and
// then also whenever it changes:
b.linkChange(false, linkMon.InterfaceState())
@@ -151,6 +155,17 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge
b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange)
wiredPeerAPIPort := false
if ig, ok := e.(wgengine.InternalsGetter); ok {
if tunWrap, _, ok := ig.GetInternals(); ok {
tunWrap.PeerAPIPort = b.getPeerAPIPortForTSMPPing
wiredPeerAPIPort = true
}
}
if !wiredPeerAPIPort {
b.logf("[unexpected] failed to wire up peer API port for engine %T", e)
}
return b, nil
}
@@ -218,52 +233,82 @@ func (b *LocalBackend) Status() *ipnstate.Status {
return sb.Status()
}
// StatusWithoutPeers is like Status but omits any details
// of peers.
func (b *LocalBackend) StatusWithoutPeers() *ipnstate.Status {
sb := new(ipnstate.StatusBuilder)
b.updateStatus(sb, nil)
return sb.Status()
}
// UpdateStatus implements ipnstate.StatusUpdater.
func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
b.e.UpdateStatus(sb)
b.updateStatus(sb, b.populatePeerStatusLocked)
}
// updateStatus populates sb with status.
//
// extraLocked, if non-nil, is called while b.mu is still held.
func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func(*ipnstate.StatusBuilder)) {
b.mu.Lock()
defer b.mu.Unlock()
sb.SetBackendState(b.state.String())
sb.SetAuthURL(b.authURL)
sb.MutateStatus(func(s *ipnstate.Status) {
s.Version = version.Long
s.BackendState = b.state.String()
s.AuthURL = b.authURL
if b.netMap != nil {
s.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
}
})
sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
for _, pln := range b.peerAPIListeners {
ss.PeerAPIURL = append(ss.PeerAPIURL, pln.urlStr)
}
})
// 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)
if extraLocked != nil {
extraLocked(sb)
}
}
func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
if b.netMap == nil {
return
}
for id, up := range b.netMap.UserProfiles {
sb.AddUser(id, up)
}
for _, p := range b.netMap.Peers {
var lastSeen time.Time
if p.LastSeen != nil {
lastSeen = *p.LastSeen
}
for _, p := range b.netMap.Peers {
var lastSeen time.Time
if p.LastSeen != nil {
lastSeen = *p.LastSeen
var tailAddr string
for _, addr := range p.Addresses {
// The peer struct currently only allows a single
// Tailscale IP address. For compatibility with the
// old display, make sure it's the IPv4 address.
if addr.IP.Is4() && addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) {
tailAddr = addr.IP.String()
break
}
var tailAddr string
for _, addr := range p.Addresses {
// The peer struct currently only allows a single
// Tailscale IP address. For compatibility with the
// old display, make sure it's the IPv4 address.
if addr.IP.Is4() && addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) {
tailAddr = addr.IP.String()
break
}
}
sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
InNetworkMap: true,
UserID: p.User,
TailAddr: tailAddr,
HostName: p.Hostinfo.Hostname,
DNSName: p.Name,
OS: p.Hostinfo.OS,
KeepAlive: p.KeepAlive,
Created: p.Created,
LastSeen: lastSeen,
ShareeNode: p.Hostinfo.ShareeNode,
ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID,
})
}
sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
InNetworkMap: true,
UserID: p.User,
TailAddr: tailAddr,
HostName: p.Hostinfo.Hostname,
DNSName: p.Name,
OS: p.Hostinfo.OS,
KeepAlive: p.KeepAlive,
Created: p.Created,
LastSeen: lastSeen,
ShareeNode: p.Hostinfo.ShareeNode,
ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID,
})
}
}
@@ -697,10 +742,16 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("192.168.0.0/16"),
netaddr.MustParseIPPrefix("172.16.0.0/12"),
netaddr.MustParseIPPrefix("10.0.0.0/8"),
// IPv4 link-local
netaddr.MustParseIPPrefix("169.254.0.0/16"),
// IPv4 multicast
netaddr.MustParseIPPrefix("224.0.0.0/4"),
// Tailscale IPv4 range
tsaddr.CGNATRange(),
// IPv6 Link-local addresses
netaddr.MustParseIPPrefix("fe80::/10"),
// IPv6 multicast
netaddr.MustParseIPPrefix("ff00::/8"),
// Tailscale IPv6 range
tsaddr.TailscaleULARange(),
}
@@ -710,6 +761,7 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
var b netaddr.IPSetBuilder
b.AddPrefix(route)
var hostIPs []netaddr.IP
err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP) {
return
@@ -717,11 +769,25 @@ func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
if pfx.IsSingleIP() {
return
}
hostIPs = append(hostIPs, pfx.IP)
b.RemovePrefix(pfx)
})
if err != nil {
return nil, err
}
// Having removed all the LAN subnets, re-add the hosts's own
// IPs. It's fine for clients to connect to an exit node's public
// IP address, just not the attached subnet.
//
// Truly forbidden subnets (in removeFromDefaultRoute) will still
// be stripped back out by the next step.
for _, ip := range hostIPs {
if route.Contains(ip) {
b.Add(ip)
}
}
for _, pfx := range removeFromDefaultRoute {
b.RemovePrefix(pfx)
}
@@ -796,8 +862,8 @@ func (b *LocalBackend) updateDNSMap(netMap *netmap.NetworkMap) {
}
set(netMap.Name, netMap.Addresses)
dnsMap := tsdns.NewMap(nameToIP, magicDNSRootDomains(netMap))
// map diff will be logged in tsdns.Resolver.SetMap.
dnsMap := dns.NewMap(nameToIP, magicDNSRootDomains(netMap))
// map diff will be logged in dns.Resolver.SetMap.
b.e.SetDNSMap(dnsMap)
}
@@ -1078,7 +1144,7 @@ func (b *LocalBackend) getEngineStatus() ipn.EngineStatus {
}
// Login implements Backend.
func (b *LocalBackend) Login(token *oauth2.Token) {
func (b *LocalBackend) Login(token *tailcfg.Oauth2Token) {
b.mu.Lock()
b.assertClientLocked()
c := b.c
@@ -1129,13 +1195,13 @@ func (b *LocalBackend) FakeExpireAfter(x time.Duration) {
b.send(ipn.Notify{NetMap: b.netMap})
}
func (b *LocalBackend) Ping(ipStr string) {
func (b *LocalBackend) Ping(ipStr string, useTSMP bool) {
ip, err := netaddr.ParseIP(ipStr)
if err != nil {
b.logf("ignoring Ping request to invalid IP %q", ipStr)
return
}
b.e.Ping(ip, func(pr *ipnstate.PingResult) {
b.e.Ping(ip, useTSMP, func(pr *ipnstate.PingResult) {
b.send(ipn.Notify{PingResult: pr})
})
}
@@ -1275,25 +1341,58 @@ func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) {
b.send(ipn.Notify{Prefs: newp})
}
func (b *LocalBackend) getPeerAPIPortForTSMPPing(ip netaddr.IP) (port uint16, ok bool) {
b.mu.Lock()
defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners {
if pln.ip.BitLen() == ip.BitLen() {
return uint16(pln.Port()), true
}
}
return 0, false
}
func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) {
for _, pln := range b.peerAPIListeners {
proto := tailcfg.ServiceProto("peerapi4")
if pln.ip.Is6() {
proto = "peerapi6"
}
ret = append(ret, tailcfg.Service{
Proto: proto,
Port: uint16(pln.Port()),
})
}
return ret
}
// doSetHostinfoFilterServices calls SetHostinfo on the controlclient,
// possibly after mangling the given hostinfo.
//
// TODO(danderson): we shouldn't be mangling hostinfo here after
// painstakingly constructing it in twelvety other places.
func (b *LocalBackend) doSetHostinfoFilterServices(hi *tailcfg.Hostinfo) {
hi2 := *hi
b.mu.Lock()
cc := b.c
if cc == nil {
// Control client isn't up yet.
b.mu.Unlock()
return
}
peerAPIServices := b.peerAPIServicesLocked()
b.mu.Unlock()
// Make a shallow copy of hostinfo so we can mutate
// at the Service field.
hi2 := *hi // shallow copy
if !b.shouldUploadServices() {
hi2.Services = []tailcfg.Service{}
}
b.mu.Lock()
cli := b.c
b.mu.Unlock()
// b.c might not be started yet
if cli != nil {
cli.SetHostinfo(&hi2)
}
// Don't mutate hi.Service's underlying array. Append to
// the slice with no free capacity.
c := len(hi2.Services)
hi2.Services = append(hi2.Services[:c:c], peerAPIServices...)
cc.SetHostinfo(&hi2)
}
// NetMap returns the latest cached network map received from
@@ -1382,6 +1481,69 @@ func (b *LocalBackend) authReconfig() {
return
}
b.logf("[v1] authReconfig: ra=%v dns=%v 0x%02x: %v", uc.RouteAll, uc.CorpDNS, flags, err)
b.initPeerAPIListener()
}
func (b *LocalBackend) initPeerAPIListener() {
b.mu.Lock()
defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners {
pln.ln.Close()
}
b.peerAPIListeners = nil
selfNode := b.netMap.SelfNode
if len(b.netMap.Addresses) == 0 || selfNode == nil {
return
}
stateFile := paths.DefaultTailscaledStateFile()
if stateFile == "" {
b.logf("peerapi disabled; no state directory")
return
}
baseDir := fmt.Sprintf("%s-uid-%d",
strings.ReplaceAll(b.activeLogin, "@", "-"),
selfNode.User)
dir := filepath.Join(filepath.Dir(stateFile), "files", baseDir)
if err := os.MkdirAll(dir, 0700); err != nil {
b.logf("peerapi disabled; error making directory: %v", err)
return
}
var tunName string
if ge, ok := b.e.(wgengine.InternalsGetter); ok {
if tunWrap, _, ok := ge.GetInternals(); ok {
tunName, _ = tunWrap.Name()
}
}
ps := &peerAPIServer{
b: b,
rootDir: dir,
tunName: tunName,
selfNode: selfNode,
}
for _, a := range b.netMap.Addresses {
ln, err := ps.listen(a.IP, b.prevIfState)
if err != nil {
b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err)
continue
}
pln := &peerAPIListener{
ps: ps,
ip: a.IP,
ln: ln,
lb: b,
}
pln.urlStr = "http://" + net.JoinHostPort(a.IP.String(), strconv.Itoa(pln.Port()))
go pln.serve()
b.peerAPIListeners = append(b.peerAPIListeners, pln)
}
}
// magicDNSRootDomains returns the subset of nm.DNS.Domains that are the search domains for MagicDNS.
@@ -1617,12 +1779,6 @@ func (b *LocalBackend) RequestEngineStatus() {
b.e.RequestStatus()
}
// RequestStatus implements Backend.
func (b *LocalBackend) RequestStatus() {
st := b.Status()
b.send(ipn.Notify{Status: st})
}
// stateMachine updates the state machine state based on other things
// that have happened. It is invoked from the various callbacks that
// feed events into LocalBackend.

View File

@@ -9,6 +9,7 @@ import (
"testing"
"inet.af/netaddr"
"tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
@@ -122,11 +123,21 @@ func TestNetworkMapCompare(t *testing.T) {
}
}
func inRemove(ip netaddr.IP) bool {
for _, pfx := range removeFromDefaultRoute {
if pfx.Contains(ip) {
return true
}
}
return false
}
func TestShrinkDefaultRoute(t *testing.T) {
tests := []struct {
route string
in []string
out []string
route string
in []string
out []string
localIPFn func(netaddr.IP) bool // true if this machine's local IP address should be "in" after shrinking.
}{
{
route: "0.0.0.0/0",
@@ -139,19 +150,24 @@ func TestShrinkDefaultRoute(t *testing.T) {
"172.16.0.1",
"172.31.255.255",
"100.101.102.103",
"224.0.0.1",
"169.254.169.254",
// Some random IPv6 stuff that shouldn't be in a v4
// default route.
"fe80::",
"2601::1",
},
localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is4() },
},
{
route: "::/0",
in: []string{"::1", "2601::1"},
out: []string{
"fe80::1",
"ff00::1",
tsaddr.TailscaleULARange().IP.String(),
},
localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is6() },
},
}
@@ -171,6 +187,16 @@ func TestShrinkDefaultRoute(t *testing.T) {
t.Errorf("shrink(%q).Contains(%v) = true, want false", test.route, ip)
}
}
ips, _, err := interfaces.LocalAddresses()
if err != nil {
t.Fatal(err)
}
for _, ip := range ips {
want := test.localIPFn(ip)
if gotContains := got.Contains(ip); gotContains != want {
t.Errorf("shrink(%q).Contains(%v) = %v, want %v", test.route, ip, gotContains, want)
}
}
}
}

243
ipn/ipnlocal/peerapi.go Normal file
View File

@@ -0,0 +1,243 @@
// 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 ipnlocal
import (
"context"
"errors"
"fmt"
"hash/crc32"
"html"
"io"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
"inet.af/netaddr"
"tailscale.com/net/interfaces"
"tailscale.com/tailcfg"
)
var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State, string) error
type peerAPIServer struct {
b *LocalBackend
rootDir string
tunName string
selfNode *tailcfg.Node
}
func (s *peerAPIServer) listen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, err error) {
ipStr := ip.String()
var lc net.ListenConfig
if initListenConfig != nil {
// On iOS/macOS, this sets the lc.Control hook to
// setsockopt the interface index to bind to, to get
// out of the network sandbox.
if err := initListenConfig(&lc, ip, ifState, s.tunName); err != nil {
return nil, err
}
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
ipStr = ""
}
}
tcp4or6 := "tcp4"
if ip.Is6() {
tcp4or6 = "tcp6"
}
// Make a best effort to pick a deterministic port number for
// the ip The lower three bytes are the same for IPv4 and IPv6
// Tailscale addresses (at least currently), so we'll usually
// get the same port number on both address families for
// dev/debugging purposes, which is nice. But it's not so
// deterministic that people will bake this into clients.
// We try a few times just in case something's already
// listening on that port (on all interfaces, probably).
for try := uint8(0); try < 5; try++ {
a16 := ip.As16()
hashData := a16[len(a16)-3:]
hashData[0] += try
tryPort := (32 << 10) | uint16(crc32.ChecksumIEEE(hashData))
ln, err = lc.Listen(context.Background(), tcp4or6, net.JoinHostPort(ipStr, strconv.Itoa(int(tryPort))))
if err == nil {
return ln, nil
}
}
// Fall back to random ephemeral port.
return lc.Listen(context.Background(), tcp4or6, net.JoinHostPort(ipStr, "0"))
}
type peerAPIListener struct {
ps *peerAPIServer
ip netaddr.IP
ln net.Listener
lb *LocalBackend
urlStr string
}
func (pln *peerAPIListener) Port() int {
ta, ok := pln.ln.Addr().(*net.TCPAddr)
if !ok {
return 0
}
return ta.Port
}
func (pln *peerAPIListener) serve() {
defer pln.ln.Close()
logf := pln.lb.logf
for {
c, err := pln.ln.Accept()
if errors.Is(err, net.ErrClosed) {
return
}
if err != nil {
logf("peerapi.Accept: %v", err)
return
}
ta, ok := c.RemoteAddr().(*net.TCPAddr)
if !ok {
c.Close()
logf("peerapi: unexpected RemoteAddr %#v", c.RemoteAddr())
continue
}
ipp, ok := netaddr.FromStdAddr(ta.IP, ta.Port, "")
if !ok {
logf("peerapi: bogus TCPAddr %#v", ta)
c.Close()
continue
}
peerNode, peerUser, ok := pln.lb.WhoIs(ipp)
if !ok {
logf("peerapi: unknown peer %v", ipp)
c.Close()
continue
}
h := &peerAPIHandler{
ps: pln.ps,
isSelf: pln.ps.selfNode.User == peerNode.User,
remoteAddr: ipp,
peerNode: peerNode,
peerUser: peerUser,
lb: pln.lb,
}
httpServer := &http.Server{
Handler: h,
}
go httpServer.Serve(&oneConnListener{Listener: pln.ln, conn: c})
}
}
type oneConnListener struct {
net.Listener
conn net.Conn
}
func (l *oneConnListener) Accept() (c net.Conn, err error) {
c = l.conn
if c == nil {
err = io.EOF
return
}
err = nil
l.conn = nil
return
}
func (l *oneConnListener) Close() error { return nil }
// peerAPIHandler serves the Peer API for a source specific client.
type peerAPIHandler struct {
ps *peerAPIServer
remoteAddr netaddr.IPPort
isSelf bool // whether peerNode is owned by same user as this node
peerNode *tailcfg.Node // peerNode is who's making the request
peerUser tailcfg.UserProfile // profile of peerNode
lb *LocalBackend
}
func (h *peerAPIHandler) logf(format string, a ...interface{}) {
h.ps.b.logf("peerapi: "+format, a...)
}
func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/v0/put/") {
h.put(w, r)
return
}
who := h.peerUser.DisplayName
fmt.Fprintf(w, `<html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<h1>Hello, %s (%v)</h1>
This is my Tailscale device. Your device is %v.
`, html.EscapeString(who), h.remoteAddr.IP, html.EscapeString(h.peerNode.ComputedName))
if h.isSelf {
fmt.Fprintf(w, "<p>You are the owner of this node.\n")
}
}
func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
if !h.isSelf {
http.Error(w, "not owner", http.StatusForbidden)
return
}
if r.Method != "PUT" {
http.Error(w, "not method PUT", http.StatusMethodNotAllowed)
return
}
if h.ps.rootDir == "" {
http.Error(w, "no rootdir", http.StatusInternalServerError)
return
}
name := path.Base(r.URL.Path)
if name == "." || name == "/" {
http.Error(w, "bad filename", http.StatusForbidden)
return
}
fileBase := strings.ReplaceAll(url.PathEscape(name), ":", "%3a")
dstFile := filepath.Join(h.ps.rootDir, fileBase)
f, err := os.Create(dstFile)
if err != nil {
h.logf("put Create error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var success bool
defer func() {
if !success {
os.Remove(dstFile)
}
}()
n, err := io.Copy(f, r.Body)
if err != nil {
f.Close()
h.logf("put Copy error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := f.Close(); err != nil {
h.logf("put Close error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
h.logf("put(%q): %d bytes from %v/%v", name, n, h.remoteAddr.IP, h.peerNode.ComputedName)
// TODO: set modtime
// TODO: some real response
success = true
io.WriteString(w, "{}\n")
}

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin,redo ios,redo
package ipnlocal
import (
"fmt"
"log"
"net"
"strings"
"syscall"
"golang.org/x/sys/unix"
"inet.af/netaddr"
"tailscale.com/net/interfaces"
)
func init() {
initListenConfig = initListenConfigNetworkExtension
}
// initListenConfigNetworkExtension configures nc for listening on IP
// through the iOS/macOS Network/System Extension (Packet Tunnel
// Provider) sandbox.
func initListenConfigNetworkExtension(nc *net.ListenConfig, ip netaddr.IP, st *interfaces.State, tunIfName string) error {
tunIf, ok := st.Interface[tunIfName]
if !ok {
return fmt.Errorf("no interface with name %q", tunIfName)
}
nc.Control = func(network, address string, c syscall.RawConn) error {
var sockErr error
err := c.Control(func(fd uintptr) {
v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6
proto := unix.IPPROTO_IP
opt := unix.IP_BOUND_IF
if v6 {
proto = unix.IPPROTO_IPV6
opt = unix.IPV6_BOUND_IF
}
sockErr = unix.SetsockoptInt(int(fd), proto, opt, tunIf.Index)
log.Printf("peerapi: bind(%q, %q) on index %v = %v", network, address, tunIf.Index, sockErr)
})
if err != nil {
return err
}
return sockErr
}
return nil
}

View File

@@ -26,7 +26,14 @@ import (
// Status represents the entire state of the IPN network.
type Status struct {
// Version is the daemon's long version (see version.Long).
Version string
// BackendState is an ipn.State string value:
// "NoState", "NeedsLogin", "NeedsMachineAuth", "Stopped",
// "Starting", "Running".
BackendState string
AuthURL string // current URL provided by control to authorize client
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Self *PeerStatus
@@ -80,6 +87,8 @@ type PeerStatus struct {
KeepAlive bool
ExitNode bool // true if this is the currently selected exit node.
PeerAPIURL []string
// ShareeNode indicates this node exists in the netmap because
// it's owned by a shared-to user and that node might connect
// to us. These nodes should be hidden by "tailscale status"
@@ -105,22 +114,16 @@ type StatusBuilder struct {
st Status
}
func (sb *StatusBuilder) SetBackendState(v string) {
// MutateStatus calls f with the status to mutate.
//
// It may not assume other fields of status are already populated, and
// may not retain or write to the Status after f returns.
//
// MutateStatus acquires a lock so f must not call back into sb.
func (sb *StatusBuilder) MutateStatus(f func(*Status)) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.BackendState = v
}
func (sb *StatusBuilder) SetAuthURL(v string) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.AuthURL = v
}
func (sb *StatusBuilder) SetMagicDNSSuffix(v string) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.MagicDNSSuffix = v
f(&sb.st)
}
func (sb *StatusBuilder) Status() *Status {
@@ -130,11 +133,19 @@ func (sb *StatusBuilder) Status() *Status {
return &sb.st
}
// SetSelfStatus sets the status of the local machine.
func (sb *StatusBuilder) SetSelfStatus(ss *PeerStatus) {
// MutateSelfStatus calls f with the PeerStatus of our own node to mutate.
//
// It may not assume other fields of status are already populated, and
// may not retain or write to the Status after f returns.
//
// MutateStatus acquires a lock so f must not call back into sb.
func (sb *StatusBuilder) MutateSelfStatus(f func(*PeerStatus)) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.Self = ss
if sb.st.Self == nil {
sb.st.Self = new(PeerStatus)
}
f(sb.st.Self)
}
// AddUser adds a user profile to the status.
@@ -394,10 +405,23 @@ type PingResult struct {
Err string
LatencySeconds float64
Endpoint string // ip:port if direct UDP was used
// Endpoint is the ip:port if direct UDP was used.
// It is not currently set for TSMP pings.
Endpoint string
DERPRegionID int // non-zero if DERP was used
DERPRegionCode string // three-letter airport/region code if DERP was used
// DERPRegionID is non-zero DERP region ID if DERP was used.
// It is not currently set for TSMP pings.
DERPRegionID int
// DERPRegionCode is the three-letter region code
// corresponding to DERPRegionID.
// It is not currently set for TSMP pings.
DERPRegionCode string
// PeerAPIPort is set by TSMP ping responses for peers that
// are running a peerapi server. This is the port they're
// running the server on.
PeerAPIPort uint16 `json:",omitempty"`
// TODO(bradfitz): details like whether port mapping was used on either side? (Once supported)
}

View File

@@ -10,9 +10,11 @@ import (
"io"
"net/http"
"runtime"
"strconv"
"inet.af/netaddr"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
)
@@ -56,6 +58,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.serveWhoIs(w, r)
case "/localapi/v0/goroutines":
h.serveGoroutines(w, r)
case "/localapi/v0/status":
h.serveStatus(w, r)
default:
io.WriteString(w, "tailscaled\n")
}
@@ -109,3 +113,31 @@ func (h *Handler) serveGoroutines(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write(buf)
}
func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) {
if !h.PermitRead {
http.Error(w, "status access denied", http.StatusForbidden)
return
}
w.Header().Set("Content-Type", "application/json")
var st *ipnstate.Status
if defBool(r.FormValue("peers"), true) {
st = h.b.Status()
} else {
st = h.b.StatusWithoutPeers()
}
e := json.NewEncoder(w)
e.SetIndent("", "\t")
e.Encode(st)
}
func defBool(a string, def bool) bool {
if a == "" {
return def
}
v, err := strconv.ParseBool(a)
if err != nil {
return def
}
return v
}

View File

@@ -15,7 +15,7 @@ import (
"log"
"time"
"golang.org/x/oauth2"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/structs"
"tailscale.com/version"
@@ -56,7 +56,8 @@ type FakeExpireAfterArgs struct {
}
type PingArgs struct {
IP string
IP string
UseTSMP bool
}
// Command is a command message that is JSON encoded and sent by a
@@ -76,7 +77,7 @@ type Command struct {
Quit *NoArgs
Start *StartArgs
StartLoginInteractive *NoArgs
Login *oauth2.Token
Login *tailcfg.Oauth2Token
Logout *NoArgs
SetPrefs *SetPrefsArgs
SetWantRunning *bool
@@ -173,11 +174,8 @@ func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
if c := cmd.RequestEngineStatus; c != nil {
bs.b.RequestEngineStatus()
return nil
} else if c := cmd.RequestStatus; c != nil {
bs.b.RequestStatus()
return nil
} else if c := cmd.Ping; c != nil {
bs.b.Ping(c.IP)
bs.b.Ping(c.IP, c.UseTSMP)
return nil
}
@@ -299,7 +297,7 @@ func (bc *BackendClient) StartLoginInteractive() {
bc.send(Command{StartLoginInteractive: &NoArgs{}})
}
func (bc *BackendClient) Login(token *oauth2.Token) {
func (bc *BackendClient) Login(token *tailcfg.Oauth2Token) {
bc.send(Command{Login: token})
}
@@ -323,8 +321,11 @@ func (bc *BackendClient) FakeExpireAfter(x time.Duration) {
bc.send(Command{FakeExpireAfter: &FakeExpireAfterArgs{Duration: x}})
}
func (bc *BackendClient) Ping(ip string) {
bc.send(Command{Ping: &PingArgs{IP: ip}})
func (bc *BackendClient) Ping(ip string, useTSMP bool) {
bc.send(Command{Ping: &PingArgs{
IP: ip,
UseTSMP: useTSMP,
}})
}
func (bc *BackendClient) SetWantRunning(v bool) {

View File

@@ -10,7 +10,7 @@ import (
"testing"
"time"
"golang.org/x/oauth2"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
)
@@ -176,7 +176,7 @@ func TestClientServer(t *testing.T) {
h.Logout()
flushUntil(NeedsLogin)
h.Login(&oauth2.Token{
h.Login(&tailcfg.Oauth2Token{
AccessToken: "google_id_token",
TokenType: GoogleIDTokenType,
})

View File

@@ -24,6 +24,7 @@ import (
"runtime"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/term"
@@ -37,9 +38,29 @@ import (
"tailscale.com/smallzstd"
"tailscale.com/types/logger"
"tailscale.com/util/racebuild"
"tailscale.com/util/winutil"
"tailscale.com/version"
)
var getLogTargetOnce struct {
sync.Once
v string // URL of logs server, or empty for default
}
func getLogTarget() string {
getLogTargetOnce.Do(func() {
if val, ok := os.LookupEnv("TS_LOG_TARGET"); ok {
getLogTargetOnce.v = val
} else {
if runtime.GOOS == "windows" {
getLogTargetOnce.v = winutil.GetRegString("LogTarget", "")
}
}
})
return getLogTargetOnce.v
}
// Config represents an instance of logs in a collection.
type Config struct {
Collection string
@@ -400,7 +421,7 @@ func New(collection string) *Policy {
HTTPC: &http.Client{Transport: newLogtailTransport(logtail.DefaultHost)},
}
if val, ok := os.LookupEnv("TS_LOG_TARGET"); ok {
if val := getLogTarget(); val != "" {
log.Println("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.")
c.BaseURL = val
u, _ := url.Parse(val)

View File

@@ -22,8 +22,8 @@ type Config struct {
// Note that Nameservers may still be applied to all queries
// if the manager does not support per-domain settings.
PerDomain bool
// Proxied indicates whether DNS requests are proxied through a tsdns.Resolver.
// This enables Magic DNS.
// Proxied indicates whether DNS requests are proxied through a dns.Resolver.
// This enables MagicDNS.
Proxied bool
}

19
net/dns/flush_windows.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 dns
import (
"fmt"
"os/exec"
)
// Flush clears the local resolver cache.
func Flush() error {
out, err := exec.Command("ipconfig", "/flushdns").CombinedOutput()
if err != nil {
return fmt.Errorf("%v (output: %s)", err, out)
}
return nil
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tsdns
package dns
import (
"bytes"
@@ -316,7 +316,7 @@ func (c *fwdConn) send(packet []byte, dst net.Addr) {
var b *backoff.Backoff // lazily initialized, since it is not needed in the common case
backOff := func(err error) {
if b == nil {
b = backoff.NewBackoff("tsdns-fwdConn-send", c.logf, 30*time.Second)
b = backoff.NewBackoff("dns-fwdConn-send", c.logf, 30*time.Second)
}
b.BackOff(context.Background(), err)
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tsdns
package dns
import (
"sort"

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tsdns
package dns
import (
"fmt"

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tsdns
package dns
import (
"errors"

View File

@@ -4,7 +4,7 @@
// +build !darwin,!windows
package tsdns
package dns
func networkIsDown(err error) bool { return false }
func networkIsUnreachable(err error) bool { return false }

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tsdns
package dns
import (
"net"

View File

@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package tsdns provides a Resolver capable of resolving
// Package dns provides a Resolver capable of resolving
// domains on a Tailscale network.
package tsdns
package dns
import (
"encoding/hex"
@@ -100,7 +100,7 @@ type ResolverConfig struct {
// The root domain must be in canonical form (with a trailing period).
func NewResolver(config ResolverConfig) *Resolver {
r := &Resolver{
logf: logger.WithPrefix(config.Logf, "tsdns: "),
logf: logger.WithPrefix(config.Logf, "dns: "),
linkMon: config.LinkMonitor,
queue: make(chan Packet, queueSize),
responses: make(chan Packet),

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tsdns
package dns
import (
"log"

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tsdns
package dns
import (
"bytes"

View File

@@ -15,16 +15,18 @@ import (
"fmt"
"inet.af/netaddr"
"tailscale.com/types/ipproto"
)
// Tuple is a 4-tuple of source and destination IP and port.
// Tuple is a 5-tuple of proto, source and destination IP and port.
type Tuple struct {
Src netaddr.IPPort
Dst netaddr.IPPort
Proto ipproto.Proto
Src netaddr.IPPort
Dst netaddr.IPPort
}
func (t Tuple) String() string {
return fmt.Sprintf("(%v => %v)", t.Src, t.Dst)
return fmt.Sprintf("(%v %v => %v)", t.Proto, t.Src, t.Dst)
}
// Cache is an LRU cache keyed by Tuple.

View File

@@ -6,10 +6,10 @@
package interfaces
import (
"bytes"
"fmt"
"net"
"net/http"
"reflect"
"runtime"
"sort"
"strings"
@@ -190,6 +190,9 @@ func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
}
}
}
sort.Slice(pfxs, func(i, j int) bool {
return pfxs[i].IP.Less(pfxs[j].IP)
})
fn(Interface{iface}, pfxs)
}
return nil
@@ -204,7 +207,7 @@ type State struct {
// IPPrefix, where the IP is the interface IP address and Bits is
// the subnet mask.
InterfaceIPs map[string][]netaddr.IPPrefix
InterfaceUp map[string]bool
Interface map[string]Interface
// HaveV6Global is whether this machine has an IPv6 global address
// on some non-Tailscale interface that's up.
@@ -235,14 +238,14 @@ type State struct {
func (s *State) String() string {
var sb strings.Builder
fmt.Fprintf(&sb, "interfaces.State{defaultRoute=%v ifs={", s.DefaultRouteInterface)
ifs := make([]string, 0, len(s.InterfaceUp))
for k := range s.InterfaceUp {
ifs := make([]string, 0, len(s.Interface))
for k := range s.Interface {
if anyInterestingIP(s.InterfaceIPs[k]) {
ifs = append(ifs, k)
}
}
sort.Slice(ifs, func(i, j int) bool {
upi, upj := s.InterfaceUp[ifs[i]], s.InterfaceUp[ifs[j]]
upi, upj := s.Interface[ifs[i]].IsUp(), s.Interface[ifs[j]].IsUp()
if upi != upj {
// Up sorts before down.
return upi
@@ -253,7 +256,7 @@ func (s *State) String() string {
if i > 0 {
sb.WriteString(" ")
}
if s.InterfaceUp[ifName] {
if s.Interface[ifName].IsUp() {
fmt.Fprintf(&sb, "%s:[", ifName)
needSpace := false
for _, pfx := range s.InterfaceIPs[ifName] {
@@ -286,10 +289,71 @@ func (s *State) String() string {
return sb.String()
}
func (s *State) Equal(s2 *State) bool {
return reflect.DeepEqual(s, s2)
// EqualFiltered reports whether s and s2 are equal,
// considering only interfaces in s for which filter returns true.
func (s *State) EqualFiltered(s2 *State, filter func(i Interface, ips []netaddr.IPPrefix) bool) bool {
if s == nil && s2 == nil {
return true
}
if s == nil || s2 == nil {
return false
}
if s.HaveV6Global != s2.HaveV6Global ||
s.HaveV4 != s2.HaveV4 ||
s.IsExpensive != s2.IsExpensive ||
s.DefaultRouteInterface != s2.DefaultRouteInterface ||
s.HTTPProxy != s2.HTTPProxy ||
s.PAC != s2.PAC {
return false
}
for iname, i := range s.Interface {
ips := s.InterfaceIPs[iname]
if !filter(i, ips) {
continue
}
i2, ok := s2.Interface[iname]
if !ok {
return false
}
ips2, ok := s2.InterfaceIPs[iname]
if !ok {
return false
}
if !interfacesEqual(i, i2) || !prefixesEqual(ips, ips2) {
return false
}
}
return true
}
func interfacesEqual(a, b Interface) bool {
return a.Index == b.Index &&
a.MTU == b.MTU &&
a.Name == b.Name &&
a.Flags == b.Flags &&
bytes.Equal([]byte(a.HardwareAddr), []byte(b.HardwareAddr))
}
func prefixesEqual(a, b []netaddr.IPPrefix) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if b[i] != v {
return false
}
}
return true
}
// FilterInteresting reports whether i is an interesting non-Tailscale interface.
func FilterInteresting(i Interface, ips []netaddr.IPPrefix) bool {
return !isTailscaleInterface(i.Name, ips) && anyInterestingIP(ips)
}
// FilterAll always returns true, to use EqualFiltered against all interfaces.
func FilterAll(i Interface, ips []netaddr.IPPrefix) bool { return true }
func (s *State) HasPAC() bool { return s != nil && s.PAC != "" }
// AnyInterfaceUp reports whether any interface seems like it has Internet access.
@@ -297,41 +361,6 @@ func (s *State) AnyInterfaceUp() bool {
return s != nil && (s.HaveV4 || s.HaveV6Global)
}
// RemoveUninterestingInterfacesAndAddresses removes uninteresting IPs
// from InterfaceIPs, also removing from both the InterfaceIPs and
// InterfaceUp map any interfaces that don't have any interesting IPs.
func (s *State) RemoveUninterestingInterfacesAndAddresses() {
for ifName := range s.InterfaceUp {
ips := s.InterfaceIPs[ifName]
keep := ips[:0]
for _, pfx := range ips {
if isInterestingIP(pfx.IP) {
keep = append(keep, pfx)
}
}
if len(keep) == 0 {
delete(s.InterfaceUp, ifName)
delete(s.InterfaceIPs, ifName)
continue
}
if len(keep) < len(ips) {
s.InterfaceIPs[ifName] = keep
}
}
}
// RemoveTailscaleInterfaces modifes s to remove any interfaces that
// are owned by this process. (TODO: make this true; currently it
// uses some heuristics)
func (s *State) RemoveTailscaleInterfaces() {
for name, pfxs := range s.InterfaceIPs {
if isTailscaleInterface(name, pfxs) {
delete(s.InterfaceIPs, name)
delete(s.InterfaceUp, name)
}
}
}
func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {
for _, pfx := range pfxs {
if tsaddr.IsTailscaleIP(pfx.IP) {
@@ -364,11 +393,11 @@ var getPAC func() string
func GetState() (*State, error) {
s := &State{
InterfaceIPs: make(map[string][]netaddr.IPPrefix),
InterfaceUp: make(map[string]bool),
Interface: make(map[string]Interface),
}
if err := ForeachInterface(func(ni Interface, pfxs []netaddr.IPPrefix) {
ifUp := ni.IsUp()
s.InterfaceUp[ni.Name] = ifUp
s.Interface[ni.Name] = ni
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
return

View File

@@ -5,6 +5,7 @@
package interfaces
import (
"encoding/json"
"testing"
)
@@ -13,7 +14,11 @@ func TestGetState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
t.Logf("Got: %#v", st)
j, err := json.MarshalIndent(st, "", "\t")
if err != nil {
t.Errorf("JSON: %v", err)
}
t.Logf("Got: %s", j)
t.Logf("As string: %s", st)
st2, err := GetState()
@@ -21,14 +26,13 @@ func TestGetState(t *testing.T) {
t.Fatal(err)
}
if !st.Equal(st2) {
if !st.EqualFiltered(st2, FilterAll) {
// let's assume nobody was changing the system network interfaces between
// the two GetState calls.
t.Fatal("two States back-to-back were not equal")
}
st.RemoveTailscaleInterfaces()
t.Logf("As string without Tailscale:\n\t%s", st)
t.Logf("As string:\n\t%s", st)
}
func TestLikelyHomeRouterIP(t *testing.T) {

View File

@@ -7,17 +7,19 @@ package interfaces
import (
"fmt"
"log"
"net"
"net/url"
"os/exec"
"syscall"
"unsafe"
"go4.org/mem"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"inet.af/netaddr"
"tailscale.com/tsconst"
"tailscale.com/util/lineread"
)
const (
fallbackInterfaceMetric = uint32(0) // Used if we cannot get the actual interface metric
)
func init() {
@@ -25,58 +27,76 @@ func init() {
getPAC = getPACWindows
}
/*
Parse out 10.0.0.1 from:
Z:\>route print -4
===========================================================================
Interface List
15...aa 15 48 ff 1c 72 ......Red Hat VirtIO Ethernet Adapter
5...........................Tailscale Tunnel
1...........................Software Loopback Interface 1
===========================================================================
IPv4 Route Table
===========================================================================
Active Routes:
Network Destination Netmask Gateway Interface Metric
0.0.0.0 0.0.0.0 10.0.0.1 10.0.28.63 5
10.0.0.0 255.255.0.0 On-link 10.0.28.63 261
10.0.28.63 255.255.255.255 On-link 10.0.28.63 261
10.0.42.0 255.255.255.0 100.103.42.106 100.103.42.106 5
10.0.255.255 255.255.255.255 On-link 10.0.28.63 261
34.193.248.174 255.255.255.255 100.103.42.106 100.103.42.106 5
*/
func likelyHomeRouterIPWindows() (ret netaddr.IP, ok bool) {
cmd := exec.Command("route", "print", "-4")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
stdout, err := cmd.StdoutPipe()
rs, err := winipcfg.GetIPForwardTable2(windows.AF_INET)
if err != nil {
log.Printf("routerIP/GetIPForwardTable2 error: %v", err)
return
}
if err := cmd.Start(); err != nil {
return
}
defer cmd.Wait()
var f []mem.RO
lineread.Reader(stdout, func(lineb []byte) error {
line := mem.B(lineb)
if !mem.Contains(line, mem.S("0.0.0.0")) {
return nil
var ifaceMetricCache map[winipcfg.LUID]uint32
getIfaceMetric := func(luid winipcfg.LUID) (metric uint32) {
if ifaceMetricCache == nil {
ifaceMetricCache = make(map[winipcfg.LUID]uint32)
} else if m, ok := ifaceMetricCache[luid]; ok {
return m
}
f = mem.AppendFields(f[:0], line)
if len(f) < 3 || !f[0].EqualString("0.0.0.0") || !f[1].EqualString("0.0.0.0") {
return nil
if iface, err := luid.IPInterface(windows.AF_INET); err == nil {
metric = iface.Metric
} else {
log.Printf("routerIP/luid.IPInterface error: %v", err)
metric = fallbackInterfaceMetric
}
ipm := f[2]
ip, err := netaddr.ParseIP(string(mem.Append(nil, ipm)))
if err == nil && isPrivateIP(ip) {
ifaceMetricCache[luid] = metric
return
}
unspec := net.IPv4(0, 0, 0, 0)
var best *winipcfg.MibIPforwardRow2 // best (lowest metric) found so far, or nil
for i := range rs {
r := &rs[i]
if r.Loopback || r.DestinationPrefix.PrefixLength != 0 || !r.DestinationPrefix.Prefix.IP().Equal(unspec) {
// Not a default route, so skip
continue
}
ip, ok := netaddr.FromStdIP(r.NextHop.IP())
if !ok {
// Not a valid gateway, so skip (won't happen though)
continue
}
if best == nil {
best = r
ret = ip
continue
}
// We can get here only if there are multiple default gateways defined (rare case),
// in which case we need to calculate the effective metric.
// Effective metric is sum of interface metric and route metric offset
if ifaceMetricCache == nil {
// If we're here it means that previous route still isn't updated, so update it
best.Metric += getIfaceMetric(best.InterfaceLUID)
}
r.Metric += getIfaceMetric(r.InterfaceLUID)
if best.Metric > r.Metric || best.Metric == r.Metric && ret.Compare(ip) > 0 {
// Pick the route with lower metric, or lower IP if metrics are equal
best = r
ret = ip
}
return nil
})
}
if !ret.IsZero() && !isPrivateIP(ret) {
// Default route has a non-private gateway
return netaddr.IP{}, false
}
return ret, !ret.IsZero()
}

View File

@@ -10,6 +10,7 @@ import (
)
const tcpHeaderLength = 20
const sctpHeaderLength = 12
// maxPacketLength is the largest length that all headers support.
// IPv4 headers using uint16 for this forces an upper bound of 64KB.

View File

@@ -4,7 +4,11 @@
package packet
import "encoding/binary"
import (
"encoding/binary"
"tailscale.com/types/ipproto"
)
// icmp4HeaderLength is the size of the ICMPv4 packet header, not
// including the outer IP layer or the variable "response data"
@@ -66,7 +70,7 @@ func (h ICMP4Header) Marshal(buf []byte) error {
return errLargePacket
}
// The caller does not need to set this.
h.IPProto = ICMPv4
h.IPProto = ipproto.ICMPv4
buf[20] = uint8(h.Type)
buf[21] = uint8(h.Code)

View File

@@ -9,6 +9,7 @@ import (
"errors"
"inet.af/netaddr"
"tailscale.com/types/ipproto"
)
// ip4HeaderLength is the length of an IPv4 header with no IP options.
@@ -16,7 +17,7 @@ const ip4HeaderLength = 20
// IP4Header represents an IPv4 packet header.
type IP4Header struct {
IPProto IPProto
IPProto ipproto.Proto
IPID uint16
Src netaddr.IP
Dst netaddr.IP

View File

@@ -8,6 +8,7 @@ import (
"encoding/binary"
"inet.af/netaddr"
"tailscale.com/types/ipproto"
)
// ip6HeaderLength is the length of an IPv6 header with no IP options.
@@ -15,7 +16,7 @@ const ip6HeaderLength = 40
// IP6Header represents an IPv6 packet header.
type IP6Header struct {
IPProto IPProto
IPProto ipproto.Proto
IPID uint32 // only lower 20 bits used
Src netaddr.IP
Dst netaddr.IP

View File

@@ -11,9 +11,12 @@ import (
"strings"
"inet.af/netaddr"
"tailscale.com/types/ipproto"
"tailscale.com/types/strbuilder"
)
const unknown = ipproto.Unknown
// RFC1858: prevent overlapping fragment attacks.
const minFrag = 60 + 20 // max IPv4 header + basic TCP header
@@ -44,7 +47,7 @@ type Parsed struct {
// 6), or 0 if the packet doesn't look like IPv4 or IPv6.
IPVersion uint8
// IPProto is the IP subprotocol (UDP, TCP, etc.). Valid iff IPVersion != 0.
IPProto IPProto
IPProto ipproto.Proto
// SrcIP4 is the source address. Family matches IPVersion. Port is
// valid iff IPProto == TCP || IPProto == UDP.
Src netaddr.IPPort
@@ -100,7 +103,7 @@ func (q *Parsed) Decode(b []byte) {
if len(b) < 1 {
q.IPVersion = 0
q.IPProto = Unknown
q.IPProto = unknown
return
}
@@ -112,7 +115,7 @@ func (q *Parsed) Decode(b []byte) {
q.decode6(b)
default:
q.IPVersion = 0
q.IPProto = Unknown
q.IPProto = unknown
}
}
@@ -125,16 +128,16 @@ func (q *Parsed) StuffForTesting(len int) {
func (q *Parsed) decode4(b []byte) {
if len(b) < ip4HeaderLength {
q.IPVersion = 0
q.IPProto = Unknown
q.IPProto = unknown
return
}
// Check that it's IPv4.
q.IPProto = IPProto(b[9])
q.IPProto = ipproto.Proto(b[9])
q.length = int(binary.BigEndian.Uint16(b[2:4]))
if len(b) < q.length {
// Packet was cut off before full IPv4 length.
q.IPProto = Unknown
q.IPProto = unknown
return
}
@@ -145,7 +148,7 @@ func (q *Parsed) decode4(b []byte) {
q.subofs = int((b[0] & 0x0F) << 2)
if q.subofs > q.length {
// next-proto starts beyond end of packet.
q.IPProto = Unknown
q.IPProto = unknown
return
}
sub := b[q.subofs:]
@@ -170,29 +173,29 @@ func (q *Parsed) decode4(b []byte) {
// This is the first fragment
if moreFrags && len(sub) < minFrag {
// Suspiciously short first fragment, dump it.
q.IPProto = Unknown
q.IPProto = unknown
return
}
// otherwise, this is either non-fragmented (the usual case)
// or a big enough initial fragment that we can read the
// whole subprotocol header.
switch q.IPProto {
case ICMPv4:
case ipproto.ICMPv4:
if len(sub) < icmp4HeaderLength {
q.IPProto = Unknown
q.IPProto = unknown
return
}
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp4HeaderLength
return
case IGMP:
case ipproto.IGMP:
// Keep IPProto, but don't parse anything else
// out.
return
case TCP:
case ipproto.TCP:
if len(sub) < tcpHeaderLength {
q.IPProto = Unknown
q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
@@ -201,21 +204,29 @@ func (q *Parsed) decode4(b []byte) {
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
case UDP:
case ipproto.UDP:
if len(sub) < udpHeaderLength {
q.IPProto = Unknown
q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.dataofs = q.subofs + udpHeaderLength
return
case TSMP:
case ipproto.SCTP:
if len(sub) < sctpHeaderLength {
q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
return
case ipproto.TSMP:
// Inter-tailscale messages.
q.dataofs = q.subofs
return
default:
q.IPProto = Unknown
q.IPProto = unknown
return
}
} else {
@@ -223,7 +234,7 @@ func (q *Parsed) decode4(b []byte) {
if fragOfs < minFrag {
// First frag was suspiciously short, so we can't
// trust the followup either.
q.IPProto = Unknown
q.IPProto = unknown
return
}
// otherwise, we have to permit the fragment to slide through.
@@ -232,7 +243,7 @@ func (q *Parsed) decode4(b []byte) {
// but that would require statefulness. Anyway, receivers'
// kernels know to drop fragments where the initial fragment
// doesn't arrive.
q.IPProto = Fragment
q.IPProto = ipproto.Fragment
return
}
}
@@ -240,15 +251,15 @@ func (q *Parsed) decode4(b []byte) {
func (q *Parsed) decode6(b []byte) {
if len(b) < ip6HeaderLength {
q.IPVersion = 0
q.IPProto = Unknown
q.IPProto = unknown
return
}
q.IPProto = IPProto(b[6])
q.IPProto = ipproto.Proto(b[6])
q.length = int(binary.BigEndian.Uint16(b[4:6])) + ip6HeaderLength
if len(b) < q.length {
// Packet was cut off before the full IPv6 length.
q.IPProto = Unknown
q.IPProto = unknown
return
}
@@ -274,17 +285,17 @@ func (q *Parsed) decode6(b []byte) {
sub = sub[:len(sub):len(sub)] // help the compiler do bounds check elimination
switch q.IPProto {
case ICMPv6:
case ipproto.ICMPv6:
if len(sub) < icmp6HeaderLength {
q.IPProto = Unknown
q.IPProto = unknown
return
}
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp6HeaderLength
case TCP:
case ipproto.TCP:
if len(sub) < tcpHeaderLength {
q.IPProto = Unknown
q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
@@ -293,20 +304,28 @@ func (q *Parsed) decode6(b []byte) {
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
case UDP:
case ipproto.UDP:
if len(sub) < udpHeaderLength {
q.IPProto = Unknown
q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.dataofs = q.subofs + udpHeaderLength
case TSMP:
case ipproto.SCTP:
if len(sub) < sctpHeaderLength {
q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
return
case ipproto.TSMP:
// Inter-tailscale messages.
q.dataofs = q.subofs
return
default:
q.IPProto = Unknown
q.IPProto = unknown
return
}
}
@@ -324,6 +343,19 @@ func (q *Parsed) IP4Header() IP4Header {
}
}
func (q *Parsed) IP6Header() IP6Header {
if q.IPVersion != 6 {
panic("IP6Header called on non-IPv6 Parsed")
}
ipid := (binary.BigEndian.Uint32(q.b[:4]) << 12) >> 12
return IP6Header{
IPID: ipid,
IPProto: q.IPProto,
Src: q.Src.IP,
Dst: q.Dst.IP,
}
}
func (q *Parsed) ICMP4Header() ICMP4Header {
if q.IPVersion != 4 {
panic("IP4Header called on non-IPv4 Parsed")
@@ -367,13 +399,13 @@ func (q *Parsed) IsTCPSyn() bool {
// IsError reports whether q is an ICMP "Error" packet.
func (q *Parsed) IsError() bool {
switch q.IPProto {
case ICMPv4:
case ipproto.ICMPv4:
if len(q.b) < q.subofs+8 {
return false
}
t := ICMP4Type(q.b[q.subofs])
return t == ICMP4Unreachable || t == ICMP4TimeExceeded
case ICMPv6:
case ipproto.ICMPv6:
if len(q.b) < q.subofs+8 {
return false
}
@@ -387,9 +419,9 @@ func (q *Parsed) IsError() bool {
// IsEchoRequest reports whether q is an ICMP Echo Request.
func (q *Parsed) IsEchoRequest() bool {
switch q.IPProto {
case ICMPv4:
case ipproto.ICMPv4:
return len(q.b) >= q.subofs+8 && ICMP4Type(q.b[q.subofs]) == ICMP4EchoRequest && ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode
case ICMPv6:
case ipproto.ICMPv6:
return len(q.b) >= q.subofs+8 && ICMP6Type(q.b[q.subofs]) == ICMP6EchoRequest && ICMP6Code(q.b[q.subofs+1]) == ICMP6NoCode
default:
return false
@@ -399,9 +431,9 @@ func (q *Parsed) IsEchoRequest() bool {
// IsEchoRequest reports whether q is an IPv4 ICMP Echo Response.
func (q *Parsed) IsEchoResponse() bool {
switch q.IPProto {
case ICMPv4:
case ipproto.ICMPv4:
return len(q.b) >= q.subofs+8 && ICMP4Type(q.b[q.subofs]) == ICMP4EchoReply && ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode
case ICMPv6:
case ipproto.ICMPv6:
return len(q.b) >= q.subofs+8 && ICMP6Type(q.b[q.subofs]) == ICMP6EchoReply && ICMP6Code(q.b[q.subofs+1]) == ICMP6NoCode
default:
return false

View File

@@ -10,6 +10,19 @@ import (
"testing"
"inet.af/netaddr"
"tailscale.com/types/ipproto"
)
const (
Unknown = ipproto.Unknown
TCP = ipproto.TCP
UDP = ipproto.UDP
SCTP = ipproto.SCTP
IGMP = ipproto.IGMP
ICMPv4 = ipproto.ICMPv4
ICMPv6 = ipproto.ICMPv6
TSMP = ipproto.TSMP
Fragment = ipproto.Fragment
)
func mustIPPort(s string) netaddr.IPPort {
@@ -305,6 +318,39 @@ var ipv4TSMPDecode = Parsed{
Dst: mustIPPort("100.74.70.3:0"),
}
// IPv4 SCTP
var sctpBuffer = []byte{
// IPv4 header:
0x45, 0x00,
0x00, 0x20, // 20 + 12 bytes total
0x00, 0x00, // ID
0x00, 0x00, // Fragment
0x40, // TTL
byte(SCTP),
// Checksum, unchecked:
1, 2,
// source IP:
0x64, 0x5e, 0x0c, 0x0e,
// dest IP:
0x64, 0x4a, 0x46, 0x03,
// Src Port, Dest Port:
0x00, 0x7b, 0x01, 0xc8,
// Verification tag:
1, 2, 3, 4,
// Checksum: (unchecked)
5, 6, 7, 8,
}
var sctpDecode = Parsed{
b: sctpBuffer,
subofs: 20,
length: 20 + 12,
IPVersion: 4,
IPProto: SCTP,
Src: mustIPPort("100.94.12.14:123"),
Dst: mustIPPort("100.74.70.3:456"),
}
func TestParsedString(t *testing.T) {
tests := []struct {
name string
@@ -320,6 +366,7 @@ func TestParsedString(t *testing.T) {
{"igmp", igmpPacketDecode, "IGMP{192.168.1.82:0 > 224.0.0.251:0}"},
{"unknown", unknownPacketDecode, "Unknown{???}"},
{"ipv4_tsmp", ipv4TSMPDecode, "TSMP{100.94.12.14:0 > 100.74.70.3:0}"},
{"sctp", sctpDecode, "SCTP{100.94.12.14:123 > 100.74.70.3:456}"},
}
for _, tt := range tests {
@@ -357,6 +404,7 @@ func TestDecode(t *testing.T) {
{"unknown", unknownPacketBuffer, unknownPacketDecode},
{"invalid4", invalid4RequestBuffer, invalid4RequestDecode},
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
{"ipv4_sctp", sctpBuffer, sctpDecode},
}
for _, tt := range tests {

View File

@@ -17,6 +17,7 @@ import (
"inet.af/netaddr"
"tailscale.com/net/flowtrack"
"tailscale.com/types/ipproto"
)
// TailscaleRejectedHeader is a TSMP message that says that one
@@ -39,7 +40,7 @@ type TailscaleRejectedHeader struct {
IPDst netaddr.IP // IPv4 or IPv6 header's dst IP
Src netaddr.IPPort // rejected flow's src
Dst netaddr.IPPort // rejected flow's dst
Proto IPProto // proto that was rejected (TCP or UDP)
Proto ipproto.Proto // proto that was rejected (TCP or UDP)
Reason TailscaleRejectReason // why the connection was rejected
// MaybeBroken is whether the rejection is non-terminal (the
@@ -57,7 +58,7 @@ type TailscaleRejectedHeader struct {
const rejectFlagBitMaybeBroken = 0x1
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst}
return flowtrack.Tuple{Proto: rh.Proto, Src: rh.Src, Dst: rh.Dst}
}
func (rh TailscaleRejectedHeader) String() string {
@@ -69,6 +70,12 @@ type TSMPType uint8
const (
// TSMPTypeRejectedConn is the type byte for a TailscaleRejectedHeader.
TSMPTypeRejectedConn TSMPType = '!'
// TSMPTypePing is the type byte for a TailscalePingRequest.
TSMPTypePing TSMPType = 'p'
// TSMPTypePong is the type byte for a TailscalePongResponse.
TSMPTypePong TSMPType = 'o'
)
type TailscaleRejectReason byte
@@ -138,7 +145,7 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
}
if h.Src.IP.Is4() {
iph := IP4Header{
IPProto: TSMP,
IPProto: ipproto.TSMP,
Src: h.IPSrc,
Dst: h.IPDst,
}
@@ -146,7 +153,7 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
buf = buf[ip4HeaderLength:]
} else if h.Src.IP.Is6() {
iph := IP6Header{
IPProto: TSMP,
IPProto: ipproto.TSMP,
Src: h.IPSrc,
Dst: h.IPDst,
}
@@ -181,7 +188,7 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
return
}
h = TailscaleRejectedHeader{
Proto: IPProto(p[1]),
Proto: ipproto.Proto(p[1]),
Reason: TailscaleRejectReason(p[2]),
IPSrc: pp.Src.IP,
IPDst: pp.Dst.IP,
@@ -194,3 +201,65 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
}
return h, true
}
// TSMPPingRequest is a TSMP message that's like an ICMP ping request.
//
// On the wire, after the IP header, it's currently 9 bytes:
// * 'p' (TSMPTypePing)
// * 8 opaque ping bytes to copy back in the response
type TSMPPingRequest struct {
Data [8]byte
}
func (pp *Parsed) AsTSMPPing() (h TSMPPingRequest, ok bool) {
if pp.IPProto != ipproto.TSMP {
return
}
p := pp.Payload()
if len(p) < 9 || p[0] != byte(TSMPTypePing) {
return
}
copy(h.Data[:], p[1:])
return h, true
}
type TSMPPongReply struct {
IPHeader Header
Data [8]byte
PeerAPIPort uint16
}
// AsTSMPPong returns pp as a TSMPPongReply and whether it is one.
// The pong.IPHeader field is not populated.
func (pp *Parsed) AsTSMPPong() (pong TSMPPongReply, ok bool) {
if pp.IPProto != ipproto.TSMP {
return
}
p := pp.Payload()
if len(p) < 9 || p[0] != byte(TSMPTypePong) {
return
}
copy(pong.Data[:], p[1:])
if len(p) >= 11 {
pong.PeerAPIPort = binary.BigEndian.Uint16(p[9:])
}
return pong, true
}
func (h TSMPPongReply) Len() int {
return h.IPHeader.Len() + 11
}
func (h TSMPPongReply) Marshal(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if err := h.IPHeader.Marshal(buf); err != nil {
return err
}
buf = buf[h.IPHeader.Len():]
buf[0] = byte(TSMPTypePong)
copy(buf[1:], h.Data[:])
binary.BigEndian.PutUint16(buf[9:11], h.PeerAPIPort)
return nil
}

View File

@@ -4,7 +4,11 @@
package packet
import "encoding/binary"
import (
"encoding/binary"
"tailscale.com/types/ipproto"
)
// udpHeaderLength is the size of the UDP packet header, not including
// the outer IP header.
@@ -31,7 +35,7 @@ func (h UDP4Header) Marshal(buf []byte) error {
return errLargePacket
}
// The caller does not need to set this.
h.IPProto = UDP
h.IPProto = ipproto.UDP
length := len(buf) - h.IP4Header.Len()
binary.BigEndian.PutUint16(buf[20:22], h.SrcPort)

View File

@@ -4,7 +4,11 @@
package packet
import "encoding/binary"
import (
"encoding/binary"
"tailscale.com/types/ipproto"
)
// UDP6Header is an IPv6+UDP header.
type UDP6Header struct {
@@ -27,7 +31,7 @@ func (h UDP6Header) Marshal(buf []byte) error {
return errLargePacket
}
// The caller does not need to set this.
h.IPProto = UDP
h.IPProto = ipproto.UDP
length := len(buf) - h.IP6Header.Len()
binary.BigEndian.PutUint16(buf[40:42], h.SrcPort)

View File

@@ -492,8 +492,9 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
}
buf := make([]byte, 1500)
pcpHeard := false // true when we get any PCP response
for {
if res.PCP && res.PMP && res.UPnP {
if pcpHeard && res.PMP && res.UPnP {
// Nothing more to discover.
return res, nil
}
@@ -515,13 +516,24 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
}
case pcpPort: // same as pmpPort
if pres, ok := parsePCPResponse(buf[:n]); ok {
if pres.OpCode == pcpOpReply|pcpOpAnnounce && pres.ResultCode == pcpCodeOK {
c.logf("Got PCP response: epoch: %v", pres.Epoch)
res.PCP = true
if pres.OpCode == pcpOpReply|pcpOpAnnounce {
pcpHeard = true
c.mu.Lock()
c.pcpSawTime = time.Now()
c.mu.Unlock()
continue
switch pres.ResultCode {
case pcpCodeOK:
c.logf("Got PCP response: epoch: %v", pres.Epoch)
res.PCP = true
continue
case pcpCodeNotAuthorized:
// A PCP service is running, but refuses to
// provide port mapping services.
res.PCP = false
continue
default:
// Fall through to unexpected log line.
}
}
c.logf("unexpected PCP probe response: %+v", pres)
}
@@ -546,7 +558,8 @@ const (
pcpVersion = 2
pcpPort = 5351
pcpCodeOK = 0
pcpCodeOK = 0
pcpCodeNotAuthorized = 2
pcpOpReply = 0x80 // OR'd into request's op code on response
pcpOpAnnounce = 0

View File

@@ -37,7 +37,7 @@ var (
)
// TailscaleServiceIP returns the listen address of services
// provided by Tailscale itself such as the Magic DNS proxy.
// provided by Tailscale itself such as the MagicDNS proxy.
func TailscaleServiceIP() netaddr.IP {
serviceIP.Do(func() { mustIP(&serviceIP.v, "100.100.100.100") })
return serviceIP.v

View File

@@ -16,10 +16,8 @@ type fakeTUN struct {
closechan chan struct{}
}
// NewFakeTUN returns a fake TUN device that does not depend on the
// operating system or any special permissions.
// It primarily exists for testing.
func NewFakeTUN() tun.Device {
// NewFake returns a tun.Device that does nothing.
func NewFake() tun.Device {
return &fakeTUN{
evchan: make(chan tun.Event),
closechan: make(chan struct{}),

View File

@@ -4,7 +4,7 @@
// +build !windows
package wgengine
package tstun
import (
"time"

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wgengine
package tstun
import (
"fmt"

129
net/tstun/tun.go Normal file
View File

@@ -0,0 +1,129 @@
// 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 tun creates a tuntap device, working around OS-specific
// quirks if necessary.
package tstun
import (
"bytes"
"os"
"os/exec"
"runtime"
"time"
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/types/logger"
"tailscale.com/version/distro"
)
// minimalMTU is the MTU we set on tailscale's TUN
// interface. wireguard-go defaults to 1420 bytes, which only works if
// the "outer" MTU is 1500 bytes. This breaks on DSL connections
// (typically 1492 MTU) and on GCE (1460 MTU?!).
//
// 1280 is the smallest MTU allowed for IPv6, which is a sensible
// "probably works everywhere" setting until we develop proper PMTU
// discovery.
const minimalMTU = 1280
// New returns a tun.Device for the requested device name.
func New(logf logger.Logf, tunName string) (tun.Device, error) {
dev, err := tun.CreateTUN(tunName, minimalMTU)
if err != nil {
return nil, err
}
if err := waitInterfaceUp(dev, 90*time.Second, logf); err != nil {
return nil, err
}
return dev, nil
}
// Diagnose tries to explain a tuntap device creation failure.
// It pokes around the system and logs some diagnostic info that might
// help debug why tun creation failed. Because device creation has
// already failed and the program's about to end, log a lot.
func Diagnose(logf logger.Logf, tunName string) {
switch runtime.GOOS {
case "linux":
diagnoseLinuxTUNFailure(tunName, logf)
case "darwin":
diagnoseDarwinTUNFailure(tunName, logf)
default:
logf("no TUN failure diagnostics for OS %q", runtime.GOOS)
}
}
func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf) {
if os.Getuid() != 0 {
logf("failed to create TUN device as non-root user; use 'sudo tailscaled', or run under launchd with 'sudo tailscaled install-system-daemon'")
}
if tunName != "utun" {
logf("failed to create TUN device %q; try using tun device \"utun\" instead for automatic selection", tunName)
}
}
func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf) {
kernel, err := exec.Command("uname", "-r").Output()
kernel = bytes.TrimSpace(kernel)
if err != nil {
logf("no TUN, and failed to look up kernel version: %v", err)
return
}
logf("Linux kernel version: %s", kernel)
modprobeOut, err := exec.Command("/sbin/modprobe", "tun").CombinedOutput()
if err == nil {
logf("'modprobe tun' successful")
// Either tun is currently loaded, or it's statically
// compiled into the kernel (which modprobe checks
// with /lib/modules/$(uname -r)/modules.builtin)
//
// So if there's a problem at this point, it's
// probably because /dev/net/tun doesn't exist.
const dev = "/dev/net/tun"
if fi, err := os.Stat(dev); err != nil {
logf("tun module loaded in kernel, but %s does not exist", dev)
} else {
logf("%s: %v", dev, fi.Mode())
}
// We failed to find why it failed. Just let our
// caller report the error it got from wireguard-go.
return
}
logf("is CONFIG_TUN enabled in your kernel? `modprobe tun` failed with: %s", modprobeOut)
switch distro.Get() {
case distro.Debian:
dpkgOut, err := exec.Command("dpkg", "-S", "kernel/drivers/net/tun.ko").CombinedOutput()
if len(bytes.TrimSpace(dpkgOut)) == 0 || err != nil {
logf("tun module not loaded nor found on disk")
return
}
if !bytes.Contains(dpkgOut, kernel) {
logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", dpkgOut)
}
case distro.Arch:
findOut, err := exec.Command("find", "/lib/modules/", "-path", "*/net/tun.ko*").CombinedOutput()
if len(bytes.TrimSpace(findOut)) == 0 || err != nil {
logf("tun module not loaded nor found on disk")
return
}
if !bytes.Contains(findOut, kernel) {
logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", findOut)
}
case distro.OpenWrt:
out, err := exec.Command("opkg", "list-installed").CombinedOutput()
if err != nil {
logf("error querying OpenWrt installed packages: %s", out)
return
}
for _, pkg := range []string{"kmod-tun", "ca-bundle"} {
if !bytes.Contains(out, []byte(pkg+" - ")) {
logf("Missing required package %s; run: opkg install %s", pkg, pkg)
}
}
}
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/wgengine/filter"
)
@@ -30,11 +31,11 @@ const maxBufferSize = device.MaxMessageSize
const PacketStartOffset = device.MessageTransportHeaderSize
// MaxPacketSize is the maximum size (in bytes)
// of a packet that can be injected into a tstun.TUN.
// of a packet that can be injected into a tstun.Wrapper.
const MaxPacketSize = device.MaxContentSize
var (
// ErrClosed is returned when attempting an operation on a closed TUN.
// ErrClosed is returned when attempting an operation on a closed Wrapper.
ErrClosed = errors.New("device closed")
// ErrFiltered is returned when the acted-on packet is rejected by a filter.
ErrFiltered = errors.New("packet dropped by filter")
@@ -51,17 +52,14 @@ var (
// do not escape through {Pre,Post}Filter{In,Out}.
var parsedPacketPool = sync.Pool{New: func() interface{} { return new(packet.Parsed) }}
// FilterFunc is a packet-filtering function with access to the TUN device.
// FilterFunc is a packet-filtering function with access to the Wrapper device.
// It must not hold onto the packet struct, as its backing storage will be reused.
type FilterFunc func(*packet.Parsed, *TUN) filter.Response
type FilterFunc func(*packet.Parsed, *Wrapper) filter.Response
// TUN wraps a tun.Device from wireguard-go,
// augmenting it with filtering and packet injection.
// All the added work happens in Read and Write:
// the other methods delegate to the underlying tdev.
type TUN struct {
// Wrapper augments a tun.Device with packet filtering and injection.
type Wrapper struct {
logf logger.Logf
// tdev is the underlying TUN device.
// tdev is the underlying Wrapper device.
tdev tun.Device
closeOnce sync.Once
@@ -108,12 +106,19 @@ type TUN struct {
// PostFilterOut is the outbound filter function that runs after the main filter.
PostFilterOut FilterFunc
// OnTSMPPongReceived, if non-nil, is called whenever a TSMP pong arrives.
OnTSMPPongReceived func(packet.TSMPPongReply)
// PeerAPIPort, if non-nil, returns the peerapi port that's
// running for the given IP address.
PeerAPIPort func(netaddr.IP) (port uint16, ok bool)
// disableFilter disables all filtering when set. This should only be used in tests.
disableFilter bool
}
func WrapTUN(logf logger.Logf, tdev tun.Device) *TUN {
tun := &TUN{
func Wrap(logf logger.Logf, tdev tun.Device) *Wrapper {
tun := &Wrapper{
logf: logger.WithPrefix(logf, "tstun: "),
tdev: tdev,
// bufferConsumed is conceptually a condition variable:
@@ -136,12 +141,12 @@ func WrapTUN(logf logger.Logf, tdev tun.Device) *TUN {
// SetDestIPActivityFuncs sets a map of funcs to run per packet
// destination (the map keys).
//
// The map ownership passes to the TUN. It must be non-nil.
func (t *TUN) SetDestIPActivityFuncs(m map[netaddr.IP]func()) {
// The map ownership passes to the Wrapper. It must be non-nil.
func (t *Wrapper) SetDestIPActivityFuncs(m map[netaddr.IP]func()) {
t.destIPActivity.Store(m)
}
func (t *TUN) Close() error {
func (t *Wrapper) Close() error {
var err error
t.closeOnce.Do(func() {
// Other channels need not be closed: poll will exit gracefully after this.
@@ -152,30 +157,30 @@ func (t *TUN) Close() error {
return err
}
func (t *TUN) Events() chan tun.Event {
func (t *Wrapper) Events() chan tun.Event {
return t.tdev.Events()
}
func (t *TUN) File() *os.File {
func (t *Wrapper) File() *os.File {
return t.tdev.File()
}
func (t *TUN) Flush() error {
func (t *Wrapper) Flush() error {
return t.tdev.Flush()
}
func (t *TUN) MTU() (int, error) {
func (t *Wrapper) MTU() (int, error) {
return t.tdev.MTU()
}
func (t *TUN) Name() (string, error) {
func (t *Wrapper) Name() (string, error) {
return t.tdev.Name()
}
// poll polls t.tdev.Read, placing the oldest unconsumed packet into t.buffer.
// This is needed because t.tdev.Read in general may block (it does on Windows),
// so packets may be stuck in t.outbound if t.Read called t.tdev.Read directly.
func (t *TUN) poll() {
func (t *Wrapper) poll() {
for {
select {
case <-t.closed:
@@ -185,7 +190,7 @@ func (t *TUN) poll() {
}
// Read may use memory in t.buffer before PacketStartOffset for mandatory headers.
// This is the rationale behind the tun.TUN.{Read,Write} interfaces
// This is the rationale behind the tun.Wrapper.{Read,Write} interfaces
// and the reason t.buffer has size MaxMessageSize and not MaxContentSize.
n, err := t.tdev.Read(t.buffer[:], PacketStartOffset)
if err != nil {
@@ -217,7 +222,7 @@ func (t *TUN) poll() {
var magicDNSIPPort = netaddr.MustParseIPPort("100.100.100.100:0")
func (t *TUN) filterOut(p *packet.Parsed) filter.Response {
func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response {
// Fake ICMP echo responses to MagicDNS (100.100.100.100).
if p.IsEchoRequest() && p.Dst == magicDNSIPPort {
header := p.ICMP4Header()
@@ -253,7 +258,7 @@ func (t *TUN) filterOut(p *packet.Parsed) filter.Response {
}
// noteActivity records that there was a read or write at the current time.
func (t *TUN) noteActivity() {
func (t *Wrapper) noteActivity() {
atomic.StoreInt64(&t.lastActivityAtomic, time.Now().Unix())
}
@@ -261,12 +266,12 @@ func (t *TUN) noteActivity() {
//
// Its value is only accurate to roughly second granularity.
// If there's never been activity, the duration is since 1970.
func (t *TUN) IdleDuration() time.Duration {
func (t *Wrapper) IdleDuration() time.Duration {
sec := atomic.LoadInt64(&t.lastActivityAtomic)
return time.Since(time.Unix(sec, 0))
}
func (t *TUN) Read(buf []byte, offset int) (int, error) {
func (t *Wrapper) Read(buf []byte, offset int) (int, error) {
var n int
wasInjectedPacket := false
@@ -317,11 +322,23 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) {
return n, nil
}
func (t *TUN) filterIn(buf []byte) filter.Response {
func (t *Wrapper) filterIn(buf []byte) filter.Response {
p := parsedPacketPool.Get().(*packet.Parsed)
defer parsedPacketPool.Put(p)
p.Decode(buf)
if p.IPProto == ipproto.TSMP {
if pingReq, ok := p.AsTSMPPing(); ok {
t.noteActivity()
t.injectOutboundPong(p, pingReq)
return filter.DropSilently
} else if data, ok := p.AsTSMPPong(); ok {
if f := t.OnTSMPPongReceived; f != nil {
f(data)
}
}
}
if t.PreFilterIn != nil {
if res := t.PreFilterIn(p, t); res.IsDrop() {
return res
@@ -340,7 +357,7 @@ func (t *TUN) filterIn(buf []byte) filter.Response {
// Their host networking stack can translate this into ICMP
// or whatnot as required. But notably, their GUI or tailscale CLI
// can show them a rejection history with reasons.
if p.IPVersion == 4 && p.IPProto == packet.TCP && p.TCPFlags&packet.TCPSyn != 0 {
if p.IPVersion == 4 && p.IPProto == ipproto.TCP && p.TCPFlags&packet.TCPSyn != 0 {
rj := packet.TailscaleRejectedHeader{
IPSrc: p.Dst.IP,
IPDst: p.Src.IP,
@@ -372,7 +389,7 @@ func (t *TUN) filterIn(buf []byte) filter.Response {
// Write accepts an incoming packet. The packet begins at buf[offset:],
// like wireguard-go/tun.Device.Write.
func (t *TUN) Write(buf []byte, offset int) (int, error) {
func (t *Wrapper) Write(buf []byte, offset int) (int, error) {
if !t.disableFilter {
res := t.filterIn(buf[offset:])
if res == filter.DropSilently {
@@ -387,16 +404,16 @@ func (t *TUN) Write(buf []byte, offset int) (int, error) {
return t.tdev.Write(buf, offset)
}
func (t *TUN) GetFilter() *filter.Filter {
func (t *Wrapper) GetFilter() *filter.Filter {
filt, _ := t.filter.Load().(*filter.Filter)
return filt
}
func (t *TUN) SetFilter(filt *filter.Filter) {
func (t *Wrapper) SetFilter(filt *filter.Filter) {
t.filter.Store(filt)
}
// InjectInboundDirect makes the TUN device behave as if a packet
// InjectInboundDirect makes the Wrapper device behave as if a packet
// with the given contents was received from the network.
// It blocks and does not take ownership of the packet.
// The injected packet will not pass through inbound filters.
@@ -404,7 +421,7 @@ func (t *TUN) SetFilter(filt *filter.Filter) {
// The packet contents are to start at &buf[offset].
// offset must be greater or equal to PacketStartOffset.
// The space before &buf[offset] will be used by Wireguard.
func (t *TUN) InjectInboundDirect(buf []byte, offset int) error {
func (t *Wrapper) InjectInboundDirect(buf []byte, offset int) error {
if len(buf) > MaxPacketSize {
return errPacketTooBig
}
@@ -423,7 +440,7 @@ func (t *TUN) InjectInboundDirect(buf []byte, offset int) error {
// InjectInboundCopy takes a packet without leading space,
// reallocates it to conform to the InjectInboundDirect interface
// and calls InjectInboundDirect on it. Injecting a nil packet is a no-op.
func (t *TUN) InjectInboundCopy(packet []byte) error {
func (t *Wrapper) InjectInboundCopy(packet []byte) error {
// We duplicate this check from InjectInboundDirect here
// to avoid wasting an allocation on an oversized packet.
if len(packet) > MaxPacketSize {
@@ -439,12 +456,35 @@ func (t *TUN) InjectInboundCopy(packet []byte) error {
return t.InjectInboundDirect(buf, PacketStartOffset)
}
// InjectOutbound makes the TUN device behave as if a packet
func (t *Wrapper) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) {
pong := packet.TSMPPongReply{
Data: req.Data,
}
if t.PeerAPIPort != nil {
pong.PeerAPIPort, _ = t.PeerAPIPort(pp.Dst.IP)
}
switch pp.IPVersion {
case 4:
h4 := pp.IP4Header()
h4.ToResponse()
pong.IPHeader = h4
case 6:
h6 := pp.IP6Header()
h6.ToResponse()
pong.IPHeader = h6
default:
return
}
t.InjectOutbound(packet.Generate(pong, nil))
}
// InjectOutbound makes the Wrapper device behave as if a packet
// with the given contents was sent to the network.
// It does not block, but takes ownership of the packet.
// The injected packet will not pass through outbound filters.
// Injecting an empty packet is a no-op.
func (t *TUN) InjectOutbound(packet []byte) error {
func (t *Wrapper) InjectOutbound(packet []byte) error {
if len(packet) > MaxPacketSize {
return errPacketTooBig
}
@@ -459,7 +499,7 @@ func (t *TUN) InjectOutbound(packet []byte) error {
}
}
// Unwrap returns the underlying TUN device.
func (t *TUN) Unwrap() tun.Device {
// Unwrap returns the underlying tun.Device.
func (t *Wrapper) Unwrap() tun.Device {
return t.tdev
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/tailscale/wireguard-go/tun/tuntest"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/wgengine/filter"
)
@@ -105,19 +106,23 @@ func netports(netPorts ...string) (ret []filter.NetPortRange) {
return ret
}
func setfilter(logf logger.Logf, tun *TUN) {
func setfilter(logf logger.Logf, tun *Wrapper) {
protos := []ipproto.Proto{
ipproto.TCP,
ipproto.UDP,
}
matches := []filter.Match{
{Srcs: nets("5.6.7.8"), Dsts: netports("1.2.3.4:89-90")},
{Srcs: nets("1.2.3.4"), Dsts: netports("5.6.7.8:98")},
{IPProto: protos, Srcs: nets("5.6.7.8"), Dsts: netports("1.2.3.4:89-90")},
{IPProto: protos, Srcs: nets("1.2.3.4"), Dsts: netports("5.6.7.8:98")},
}
var sb netaddr.IPSetBuilder
sb.AddPrefix(netaddr.MustParseIPPrefix("1.2.0.0/16"))
tun.SetFilter(filter.New(matches, sb.IPSet(), sb.IPSet(), nil, logf))
}
func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *TUN) {
func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *Wrapper) {
chtun := tuntest.NewChannelTUN()
tun := WrapTUN(logf, chtun.TUN())
tun := Wrap(logf, chtun.TUN())
if secure {
setfilter(logf, tun)
} else {
@@ -126,9 +131,9 @@ func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *TUN) {
return chtun, tun
}
func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *TUN) {
ftun := NewFakeTUN()
tun := WrapTUN(logf, ftun)
func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *Wrapper) {
ftun := NewFake()
tun := Wrap(logf, ftun)
if secure {
setfilter(logf, tun)
} else {
@@ -269,7 +274,7 @@ func TestFilter(t *testing.T) {
{"good_packet_out", out, false, udp4("1.2.3.4", "5.6.7.8", 98, 98)},
}
// A reader on the other end of the TUN.
// A reader on the other end of the tun.
go func() {
var recvbuf []byte
for {
@@ -372,11 +377,11 @@ func BenchmarkWrite(b *testing.B) {
}
func TestAtomic64Alignment(t *testing.T) {
off := unsafe.Offsetof(TUN{}.lastActivityAtomic)
off := unsafe.Offsetof(Wrapper{}.lastActivityAtomic)
if off%8 != 0 {
t.Errorf("offset %v not 8-byte aligned", off)
}
c := new(TUN)
c := new(Wrapper)
atomic.StoreInt64(&c.lastActivityAtomic, 123)
}

View File

@@ -7,6 +7,7 @@
package paths
import (
"os"
"path/filepath"
"runtime"
@@ -45,8 +46,17 @@ func stateFileUnix() string {
try = filepath.Dir(try)
}
// TODO: try some $HOME/.tailscale or XDG path? But will it
// even work usefully enough as non-root? Probably not. Maybe
// best to require it be explicit in that case.
return ""
if os.Getuid() == 0 {
return ""
}
// For non-root users, fall back to $XDG_DATA_HOME/tailscale/*.
return filepath.Join(xdgDataHome(), "tailscale", "tailscaled.state")
}
func xdgDataHome() string {
if e := os.Getenv("XDG_DATA_HOME"); e != "" {
return e
}
return filepath.Join(os.Getenv("HOME"), ".local/share")
}

View File

@@ -10,8 +10,6 @@ import (
"errors"
"net"
"runtime"
"tailscale.com/paths"
)
type closeable interface {
@@ -31,11 +29,6 @@ func ConnCloseWrite(c net.Conn) error {
return c.(closeable).CloseWrite()
}
// ConnectDefault connects to the local Tailscale daemon.
func ConnectDefault() (net.Conn, error) {
return Connect(paths.DefaultTailscaledSocket(), 41112)
}
// Connect connects to either path (on Unix) or the provided localhost port (on Windows).
func Connect(path string, port uint16) (net.Conn, error) {
return connect(path, port)

View File

@@ -5,7 +5,10 @@
// Package syncs contains additional sync types and functionality.
package syncs
import "sync/atomic"
import (
"context"
"sync/atomic"
)
// ClosedChan returns a channel that's already closed.
func ClosedChan() <-chan struct{} { return closedChan }
@@ -79,3 +82,45 @@ func (b *AtomicBool) Set(v bool) {
func (b *AtomicBool) Get() bool {
return atomic.LoadInt32((*int32)(b)) != 0
}
// Semaphore is a counting semaphore.
//
// Use NewSemaphore to create one.
type Semaphore struct {
c chan struct{}
}
// NewSemaphore returns a semaphore with resource count n.
func NewSemaphore(n int) Semaphore {
return Semaphore{c: make(chan struct{}, n)}
}
// Acquire blocks until a resource is acquired.
func (s Semaphore) Acquire() {
s.c <- struct{}{}
}
// AcquireContext reports whether the resource was acquired before the ctx was done.
func (s Semaphore) AcquireContext(ctx context.Context) bool {
select {
case s.c <- struct{}{}:
return true
case <-ctx.Done():
return false
}
}
// TryAcquire reports, without blocking, whether the resource was acquired.
func (s Semaphore) TryAcquire() bool {
select {
case s.c <- struct{}{}:
return true
default:
return false
}
}
// Release releases a resource.
func (s Semaphore) Release() {
<-s.c
}

View File

@@ -4,7 +4,10 @@
package syncs
import "testing"
import (
"context"
"testing"
)
func TestWaitGroupChan(t *testing.T) {
wg := NewWaitGroupChan()
@@ -48,3 +51,25 @@ func TestClosedChan(t *testing.T) {
}
}
}
func TestSemaphore(t *testing.T) {
s := NewSemaphore(2)
s.Acquire()
if !s.TryAcquire() {
t.Fatal("want true")
}
if s.TryAcquire() {
t.Fatal("want false")
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
if s.AcquireContext(ctx) {
t.Fatal("want false")
}
s.Release()
if !s.AcquireContext(context.Background()) {
t.Fatal("want true")
}
s.Release()
s.Release()
}

View File

@@ -6,9 +6,12 @@ package syncs
import (
"context"
"runtime"
"sync"
"testing"
"time"
"tailscale.com/util/cibuild"
)
// Time-based tests are fundamentally flaky.
@@ -46,6 +49,12 @@ func TestWatchContended(t *testing.T) {
}
func TestWatchMultipleValues(t *testing.T) {
if cibuild.On() && runtime.GOOS == "windows" {
// On the CI machine, it sometimes takes 500ms to start a new goroutine.
// When this happens, we don't get enough events quickly enough.
// Nothing's wrong, and it's not worth working around. Just skip the test.
t.Skip("flaky on Windows CI")
}
mu := new(sync.Mutex)
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // not necessary, but keep vet happy

View File

@@ -15,7 +15,6 @@ import (
"time"
"go4.org/mem"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/types/key"
"tailscale.com/types/opt"
@@ -36,7 +35,8 @@ import (
// 10: 2021-01-17: client understands MapResponse.PeerSeenChange
// 11: 2021-03-03: client understands IPv6, multiple default routes, and goroutine dumping
// 12: 2021-03-04: client understands PingRequest
const CurrentMapRequestVersion = 11
// 13: 2021-03-19: client understands FilterRule.IPProto
const CurrentMapRequestVersion = 13
type StableID string
@@ -539,6 +539,61 @@ func (h *Hostinfo) Equal(h2 *Hostinfo) bool {
return reflect.DeepEqual(h, h2)
}
// SignatureType specifies a scheme for signing RegisterRequest messages. It
// specifies the crypto algorithms to use, the contents of what is signed, and
// any other relevant details. Historically, requests were unsigned so the zero
// value is SignatureNone.
type SignatureType int
const (
// SignatureNone indicates that there is no signature, no Timestamp is
// required (but may be specified if desired), and both DeviceCert and
// Signature should be empty.
SignatureNone = SignatureType(iota)
// SignatureUnknown represents an unknown signature scheme, which should
// be considered an error if seen.
SignatureUnknown
// SignatureV1 is computed as RSA-PSS-Sign(privateKeyForDeviceCert,
// SHA256(Timestamp || ServerIdentity || DeviceCert || ServerPubKey ||
// MachinePubKey)). The PSS salt length is equal to hash length
// (rsa.PSSSaltLengthEqualsHash). Device cert is required.
SignatureV1
)
func (st SignatureType) MarshalText() ([]byte, error) {
return []byte(st.String()), nil
}
func (st *SignatureType) UnmarshalText(b []byte) error {
switch string(b) {
case "signature-none":
*st = SignatureNone
case "signature-v1":
*st = SignatureV1
default:
var val int
if _, err := fmt.Sscanf(string(b), "signature-unknown(%d)", &val); err != nil {
*st = SignatureType(val)
} else {
*st = SignatureUnknown
}
}
return nil
}
func (st SignatureType) String() string {
switch st {
case SignatureNone:
return "signature-none"
case SignatureUnknown:
return "signature-unknown"
case SignatureV1:
return "signature-v1"
default:
return fmt.Sprintf("signature-unknown(%d)", int(st))
}
}
// RegisterRequest is sent by a client to register the key for a node.
// It is encoded to JSON, encrypted with golang.org/x/crypto/nacl/box,
// using the local machine key, and sent to:
@@ -552,12 +607,19 @@ type RegisterRequest struct {
_ structs.Incomparable
// One of Provider/LoginName, Oauth2Token, or AuthKey is set.
Provider, LoginName string
Oauth2Token *oauth2.Token
Oauth2Token *Oauth2Token
AuthKey string
}
Expiry time.Time // requested key expiry, server policy may override
Followup string // response waits until AuthURL is visited
Hostinfo *Hostinfo
// The following fields are not used for SignatureNone and are required for
// SignatureV1:
SignatureType SignatureType `json:",omitempty"`
Timestamp *time.Time `json:",omitempty"` // creation time of request to prevent replay
DeviceCert []byte `json:",omitempty"` // X.509 certificate for client device
Signature []byte `json:",omitempty"` // as described by SignatureType
}
// Clone makes a deep copy of RegisterRequest.
@@ -574,6 +636,8 @@ func (req *RegisterRequest) Clone() *RegisterRequest {
tok := *res.Auth.Oauth2Token
res.Auth.Oauth2Token = &tok
}
res.DeviceCert = append(res.DeviceCert[:0:0], res.DeviceCert...)
res.Signature = append(res.Signature[:0:0], res.Signature...)
return res
}
@@ -694,6 +758,17 @@ type FilterRule struct {
// DstPorts are the port ranges to allow once a source IP
// matches (is in the CIDR described by SrcIPs & SrcBits).
DstPorts []NetPortRange
// IPProto are the IP protocol numbers to match.
//
// As a special case, nil or empty means TCP, UDP, and ICMP.
//
// Numbers outside the uint8 range (below 0 or above 255) are
// reserved for Tailscale's use. Unknown ones are ignored.
//
// Depending on the IPProto values, DstPorts may or may not be
// used.
IPProto []int `json:",omitempty"`
}
var FilterAllowAll = []FilterRule{
@@ -719,8 +794,8 @@ type DNSConfig struct {
// Some OSes and OS configurations don't support per-domain DNS configuration,
// in which case Nameservers applies to all DNS requests regardless of PerDomain's value.
PerDomain bool
// Proxied indicates whether DNS requests are proxied through a tsdns.Resolver.
// This enables Magic DNS. It is togglable independently of PerDomain.
// Proxied indicates whether DNS requests are proxied through a dns.Resolver.
// This enables MagicDNS. It is togglable independently of PerDomain.
Proxied bool
}
@@ -975,3 +1050,28 @@ type WhoIsResponse struct {
Node *Node
UserProfile *UserProfile
}
// Oauth2Token is a copy of golang.org/x/oauth2.Token, to avoid the
// go.mod dependency on App Engine and grpc, which was causing problems.
// All we actually needed was this struct on the client side.
type Oauth2Token struct {
// AccessToken is the token that authorizes and authenticates
// the requests.
AccessToken string `json:"access_token"`
// TokenType is the type of token.
// The Type method returns either this or "Bearer", the default.
TokenType string `json:"token_type,omitempty"`
// RefreshToken is a token that's used by the application
// (as opposed to the user) to refresh the access token
// if it expires.
RefreshToken string `json:"refresh_token,omitempty"`
// Expiry is the optional expiration time of the access token.
//
// If zero, TokenSource implementations will reuse the same
// token forever and RefreshToken or equivalent
// mechanisms for that TokenSource will not be used.
Expiry time.Time `json:"expiry,omitempty"`
}

View File

@@ -26,7 +26,6 @@ import (
"sync"
"time"
wgconn "github.com/tailscale/wireguard-go/conn"
"inet.af/netaddr"
)
@@ -759,8 +758,7 @@ func (c *conn) canRead() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed {
// TODO: when we switch to Go 1.16, replace this with net.ErrClosed
return wgconn.NetErrClosed
return net.ErrClosed
}
if !c.readDeadline.IsZero() && c.readDeadline.Before(time.Now()) {
return errors.New("read deadline exceeded")

View File

@@ -1,28 +1,32 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
// Package ipproto contains IP Protocol constants.
package ipproto
// IPProto is an IP subprotocol as defined by the IANA protocol
import "fmt"
// Proto is an IP subprotocol as defined by the IANA protocol
// numbers list
// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),
// or the special values Unknown or Fragment.
type IPProto uint8
type Proto uint8
const (
// Unknown represents an unknown or unsupported protocol; it's
// deliberately the zero value. Strictly speaking the zero
// value is IPv6 hop-by-hop extensions, but we don't support
// those, so this is still technically correct.
Unknown IPProto = 0x00
Unknown Proto = 0x00
// Values from the IANA registry.
ICMPv4 IPProto = 0x01
IGMP IPProto = 0x02
ICMPv6 IPProto = 0x3a
TCP IPProto = 0x06
UDP IPProto = 0x11
ICMPv4 Proto = 0x01
IGMP Proto = 0x02
ICMPv6 Proto = 0x3a
TCP Proto = 0x06
UDP Proto = 0x11
SCTP Proto = 0x84
// TSMP is the Tailscale Message Protocol (our ICMP-ish
// thing), an IP protocol used only between Tailscale nodes
@@ -33,7 +37,7 @@ const (
// scheme". We never accept these from the host OS stack nor
// send them to the host network stack. It's only used between
// nodes.
TSMP IPProto = 99
TSMP Proto = 99
// Fragment represents any non-first IP fragment, for which we
// don't have the sub-protocol header (and therefore can't
@@ -41,11 +45,13 @@ const (
//
// 0xFF is reserved in the IANA registry, so we steal it for
// internal use.
Fragment IPProto = 0xFF
Fragment Proto = 0xFF
)
func (p IPProto) String() string {
func (p Proto) String() string {
switch p {
case Unknown:
return "Unknown"
case Fragment:
return "Frag"
case ICMPv4:
@@ -58,9 +64,11 @@ func (p IPProto) String() string {
return "UDP"
case TCP:
return "TCP"
case SCTP:
return "SCTP"
case TSMP:
return "TSMP"
default:
return "Unknown"
return fmt.Sprintf("IPProto-%d", int(p))
}
}

View File

@@ -8,9 +8,14 @@
package winutil
import (
"log"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
const RegBase = `SOFTWARE\Tailscale IPN`
// GetDesktopPID searches the PID of the process that's running the
// currently active desktop and whether it was found.
// Usually the PID will be for explorer.exe.
@@ -22,3 +27,26 @@ func GetDesktopPID() (pid uint32, ok bool) {
windows.GetWindowThreadProcessId(hwnd, &pid)
return pid, pid != 0
}
// GetRegString looks up a registry path in our local machine path, or returns
// the given default if it can't.
//
// This function will only work on GOOS=windows. Trying to run it on any other
// OS will always return the default value.
func GetRegString(name, defval string) string {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, RegBase, registry.READ)
if err != nil {
log.Printf("registry.OpenKey(%v): %v", RegBase, err)
return defval
}
defer key.Close()
val, _, err := key.GetStringValue(name)
if err != nil {
if err != registry.ErrNotExist {
log.Printf("registry.GetStringValue(%v): %v", name, err)
}
return defval
}
return val
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package winutil
const RegBase = ``
// GetRegString looks up a registry path in our local machine path, or returns
// the given default if it can't.
//
// This function will only work on GOOS=windows. Trying to run it on any other
// OS will always return the default value.
func GetRegString(name, defval string) string { return defval }

View File

@@ -14,6 +14,7 @@ import (
"inet.af/netaddr"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
)
@@ -182,6 +183,7 @@ func matchesFamily(ms matches, keep func(netaddr.IP) bool) matches {
var ret matches
for _, m := range ms {
var retm Match
retm.IPProto = m.IPProto
for _, src := range m.Srcs {
if keep(src.IP) {
retm.Srcs = append(retm.Srcs, src)
@@ -266,7 +268,7 @@ func (f *Filter) CheckTCP(srcIP, dstIP netaddr.IP, dstPort uint16) Response {
}
pkt.Src.IP = srcIP
pkt.Dst.IP = dstIP
pkt.IPProto = packet.TCP
pkt.IPProto = ipproto.TCP
pkt.TCPFlags = packet.TCPSyn
pkt.Src.Port = 0
pkt.Dst.Port = dstPort
@@ -324,7 +326,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
}
switch q.IPProto {
case packet.ICMPv4:
case ipproto.ICMPv4:
if q.IsEchoResponse() || q.IsError() {
// ICMP responses are allowed.
// TODO(apenwarr): consider using conntrack state.
@@ -336,7 +338,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
// If any port is open to an IP, allow ICMP to it.
return Accept, "icmp ok"
}
case packet.TCP:
case ipproto.TCP:
// For TCP, we want to allow *outgoing* connections,
// which means we want to allow return packets on those
// connections. To make this restriction work, we need to
@@ -351,20 +353,20 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
if f.matches4.match(q) {
return Accept, "tcp ok"
}
case packet.UDP:
t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst}
case ipproto.UDP, ipproto.SCTP:
t := flowtrack.Tuple{Proto: q.IPProto, Src: q.Src, Dst: q.Dst}
f.state.mu.Lock()
_, ok := f.state.lru.Get(t)
f.state.mu.Unlock()
if ok {
return Accept, "udp cached"
return Accept, "cached"
}
if f.matches4.match(q) {
return Accept, "udp ok"
return Accept, "ok"
}
case packet.TSMP:
case ipproto.TSMP:
return Accept, "tsmp ok"
default:
return Drop, "Unknown proto"
@@ -381,7 +383,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
}
switch q.IPProto {
case packet.ICMPv6:
case ipproto.ICMPv6:
if q.IsEchoResponse() || q.IsError() {
// ICMP responses are allowed.
// TODO(apenwarr): consider using conntrack state.
@@ -393,7 +395,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
// If any port is open to an IP, allow ICMP to it.
return Accept, "icmp ok"
}
case packet.TCP:
case ipproto.TCP:
// For TCP, we want to allow *outgoing* connections,
// which means we want to allow return packets on those
// connections. To make this restriction work, we need to
@@ -402,25 +404,27 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
// can't be initiated without first sending a SYN.
// It happens to also be much faster.
// TODO(apenwarr): Skip the rest of decoding in this path?
if q.IPProto == packet.TCP && !q.IsTCPSyn() {
if q.IPProto == ipproto.TCP && !q.IsTCPSyn() {
return Accept, "tcp non-syn"
}
if f.matches6.match(q) {
return Accept, "tcp ok"
}
case packet.UDP:
t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst}
case ipproto.UDP, ipproto.SCTP:
t := flowtrack.Tuple{Proto: q.IPProto, Src: q.Src, Dst: q.Dst}
f.state.mu.Lock()
_, ok := f.state.lru.Get(t)
f.state.mu.Unlock()
if ok {
return Accept, "udp cached"
return Accept, "cached"
}
if f.matches6.match(q) {
return Accept, "udp ok"
return Accept, "ok"
}
case ipproto.TSMP:
return Accept, "tsmp ok"
default:
return Drop, "Unknown proto"
}
@@ -429,15 +433,16 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
// runIn runs the output-specific part of the filter logic.
func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) {
if q.IPProto != packet.UDP {
return Accept, "ok out"
switch q.IPProto {
case ipproto.UDP, ipproto.SCTP:
tuple := flowtrack.Tuple{
Proto: q.IPProto,
Src: q.Dst, Dst: q.Src, // src/dst reversed
}
f.state.mu.Lock()
f.state.lru.Add(tuple, nil)
f.state.mu.Unlock()
}
tuple := flowtrack.Tuple{Src: q.Dst, Dst: q.Src} // src/dst reversed
f.state.mu.Lock()
f.state.lru.Add(tuple, nil)
f.state.mu.Unlock()
return Accept, "ok out"
}
@@ -485,11 +490,11 @@ func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response {
}
switch q.IPProto {
case packet.Unknown:
case ipproto.Unknown:
// Unknown packets are dangerous; always drop them.
f.logRateLimit(rf, q, dir, Drop, "unknown")
return Drop
case packet.Fragment:
case ipproto.Fragment:
// Fragments after the first always need to be passed through.
// Very small fragments are considered Junk by Parsed.
f.logRateLimit(rf, q, dir, Accept, "fragment")
@@ -513,5 +518,5 @@ func omitDropLogging(p *packet.Parsed, dir direction) bool {
return false
}
return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == packet.IGMP
return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == ipproto.IGMP
}

View File

@@ -7,6 +7,7 @@ package filter
import (
"encoding/hex"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
@@ -16,19 +17,32 @@ import (
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
)
func newFilter(logf logger.Logf) *Filter {
m := func(srcs []netaddr.IPPrefix, dsts []NetPortRange, protos ...ipproto.Proto) Match {
if protos == nil {
protos = defaultProtos
}
return Match{
IPProto: protos,
Srcs: srcs,
Dsts: dsts,
}
}
matches := []Match{
{Srcs: nets("8.1.1.1", "8.2.2.2"), Dsts: netports("1.2.3.4:22", "5.6.7.8:23-24")},
{Srcs: nets("8.1.1.1", "8.2.2.2"), Dsts: netports("5.6.7.8:27-28")},
{Srcs: nets("2.2.2.2"), Dsts: netports("8.1.1.1:22")},
{Srcs: nets("0.0.0.0/0"), Dsts: netports("100.122.98.50:*")},
{Srcs: nets("0.0.0.0/0"), Dsts: netports("0.0.0.0/0:443")},
{Srcs: nets("153.1.1.1", "153.1.1.2", "153.3.3.3"), Dsts: netports("1.2.3.4:999")},
{Srcs: nets("::1", "::2"), Dsts: netports("2001::1:22", "2001::2:22")},
{Srcs: nets("::/0"), Dsts: netports("::/0:443")},
m(nets("8.1.1.1", "8.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24")),
m(nets("9.1.1.1", "9.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24"), ipproto.SCTP),
m(nets("8.1.1.1", "8.2.2.2"), netports("5.6.7.8:27-28")),
m(nets("2.2.2.2"), netports("8.1.1.1:22")),
m(nets("0.0.0.0/0"), netports("100.122.98.50:*")),
m(nets("0.0.0.0/0"), netports("0.0.0.0/0:443")),
m(nets("153.1.1.1", "153.1.1.2", "153.3.3.3"), netports("1.2.3.4:999")),
m(nets("::1", "::2"), netports("2001::1:22", "2001::2:22")),
m(nets("::/0"), netports("::/0:443")),
}
// Expects traffic to 100.122.98.50, 1.2.3.4, 5.6.7.8,
@@ -52,43 +66,48 @@ func TestFilter(t *testing.T) {
}
tests := []InOut{
// allow 8.1.1.1 => 1.2.3.4:22
{Accept, parsed(packet.TCP, "8.1.1.1", "1.2.3.4", 999, 22)},
{Accept, parsed(packet.ICMPv4, "8.1.1.1", "1.2.3.4", 0, 0)},
{Drop, parsed(packet.TCP, "8.1.1.1", "1.2.3.4", 0, 0)},
{Accept, parsed(packet.TCP, "8.1.1.1", "1.2.3.4", 0, 22)},
{Drop, parsed(packet.TCP, "8.1.1.1", "1.2.3.4", 0, 21)},
{Accept, parsed(ipproto.TCP, "8.1.1.1", "1.2.3.4", 999, 22)},
{Accept, parsed(ipproto.ICMPv4, "8.1.1.1", "1.2.3.4", 0, 0)},
{Drop, parsed(ipproto.TCP, "8.1.1.1", "1.2.3.4", 0, 0)},
{Accept, parsed(ipproto.TCP, "8.1.1.1", "1.2.3.4", 0, 22)},
{Drop, parsed(ipproto.TCP, "8.1.1.1", "1.2.3.4", 0, 21)},
// allow 8.2.2.2. => 1.2.3.4:22
{Accept, parsed(packet.TCP, "8.2.2.2", "1.2.3.4", 0, 22)},
{Drop, parsed(packet.TCP, "8.2.2.2", "1.2.3.4", 0, 23)},
{Drop, parsed(packet.TCP, "8.3.3.3", "1.2.3.4", 0, 22)},
{Accept, parsed(ipproto.TCP, "8.2.2.2", "1.2.3.4", 0, 22)},
{Drop, parsed(ipproto.TCP, "8.2.2.2", "1.2.3.4", 0, 23)},
{Drop, parsed(ipproto.TCP, "8.3.3.3", "1.2.3.4", 0, 22)},
// allow 8.1.1.1 => 5.6.7.8:23-24
{Accept, parsed(packet.TCP, "8.1.1.1", "5.6.7.8", 0, 23)},
{Accept, parsed(packet.TCP, "8.1.1.1", "5.6.7.8", 0, 24)},
{Drop, parsed(packet.TCP, "8.1.1.3", "5.6.7.8", 0, 24)},
{Drop, parsed(packet.TCP, "8.1.1.1", "5.6.7.8", 0, 22)},
{Accept, parsed(ipproto.TCP, "8.1.1.1", "5.6.7.8", 0, 23)},
{Accept, parsed(ipproto.TCP, "8.1.1.1", "5.6.7.8", 0, 24)},
{Drop, parsed(ipproto.TCP, "8.1.1.3", "5.6.7.8", 0, 24)},
{Drop, parsed(ipproto.TCP, "8.1.1.1", "5.6.7.8", 0, 22)},
// allow * => *:443
{Accept, parsed(packet.TCP, "17.34.51.68", "8.1.34.51", 0, 443)},
{Drop, parsed(packet.TCP, "17.34.51.68", "8.1.34.51", 0, 444)},
{Accept, parsed(ipproto.TCP, "17.34.51.68", "8.1.34.51", 0, 443)},
{Drop, parsed(ipproto.TCP, "17.34.51.68", "8.1.34.51", 0, 444)},
// allow * => 100.122.98.50:*
{Accept, parsed(packet.TCP, "17.34.51.68", "100.122.98.50", 0, 999)},
{Accept, parsed(packet.TCP, "17.34.51.68", "100.122.98.50", 0, 0)},
{Accept, parsed(ipproto.TCP, "17.34.51.68", "100.122.98.50", 0, 999)},
{Accept, parsed(ipproto.TCP, "17.34.51.68", "100.122.98.50", 0, 0)},
// allow ::1, ::2 => [2001::1]:22
{Accept, parsed(packet.TCP, "::1", "2001::1", 0, 22)},
{Accept, parsed(packet.ICMPv6, "::1", "2001::1", 0, 0)},
{Accept, parsed(packet.TCP, "::2", "2001::1", 0, 22)},
{Accept, parsed(packet.TCP, "::2", "2001::2", 0, 22)},
{Drop, parsed(packet.TCP, "::1", "2001::1", 0, 23)},
{Drop, parsed(packet.TCP, "::1", "2001::3", 0, 22)},
{Drop, parsed(packet.TCP, "::3", "2001::1", 0, 22)},
{Accept, parsed(ipproto.TCP, "::1", "2001::1", 0, 22)},
{Accept, parsed(ipproto.ICMPv6, "::1", "2001::1", 0, 0)},
{Accept, parsed(ipproto.TCP, "::2", "2001::1", 0, 22)},
{Accept, parsed(ipproto.TCP, "::2", "2001::2", 0, 22)},
{Drop, parsed(ipproto.TCP, "::1", "2001::1", 0, 23)},
{Drop, parsed(ipproto.TCP, "::1", "2001::3", 0, 22)},
{Drop, parsed(ipproto.TCP, "::3", "2001::1", 0, 22)},
// allow * => *:443
{Accept, parsed(packet.TCP, "::1", "2001::1", 0, 443)},
{Drop, parsed(packet.TCP, "::1", "2001::1", 0, 444)},
{Accept, parsed(ipproto.TCP, "::1", "2001::1", 0, 443)},
{Drop, parsed(ipproto.TCP, "::1", "2001::1", 0, 444)},
// localNets prefilter - accepted by policy filter, but
// unexpected dst IP.
{Drop, parsed(packet.TCP, "8.1.1.1", "16.32.48.64", 0, 443)},
{Drop, parsed(packet.TCP, "1::", "2602::1", 0, 443)},
{Drop, parsed(ipproto.TCP, "8.1.1.1", "16.32.48.64", 0, 443)},
{Drop, parsed(ipproto.TCP, "1::", "2602::1", 0, 443)},
// Don't allow protocols not specified by filter
{Drop, parsed(ipproto.SCTP, "8.1.1.1", "1.2.3.4", 999, 22)},
// But SCTP is allowed for 9.1.1.1
{Accept, parsed(ipproto.SCTP, "9.1.1.1", "1.2.3.4", 999, 22)},
}
for i, test := range tests {
aclFunc := acl.runIn4
@@ -98,7 +117,7 @@ func TestFilter(t *testing.T) {
if got, why := aclFunc(&test.p); test.want != got {
t.Errorf("#%d runIn got=%v want=%v why=%q packet:%v", i, got, test.want, why, test.p)
}
if test.p.IPProto == packet.TCP {
if test.p.IPProto == ipproto.TCP {
var got Response
if test.p.IPVersion == 4 {
got = acl.CheckTCP(test.p.Src.IP, test.p.Dst.IP, test.p.Dst.Port)
@@ -109,7 +128,7 @@ func TestFilter(t *testing.T) {
t.Errorf("#%d CheckTCP got=%v want=%v packet:%v", i, got, test.want, test.p)
}
// TCP and UDP are treated equivalently in the filter - verify that.
test.p.IPProto = packet.UDP
test.p.IPProto = ipproto.UDP
if got, why := aclFunc(&test.p); test.want != got {
t.Errorf("#%d runIn (UDP) got=%v want=%v why=%q packet:%v", i, got, test.want, why, test.p)
}
@@ -123,8 +142,8 @@ func TestUDPState(t *testing.T) {
acl := newFilter(t.Logf)
flags := LogDrops | LogAccepts
a4 := parsed(packet.UDP, "119.119.119.119", "102.102.102.102", 4242, 4343)
b4 := parsed(packet.UDP, "102.102.102.102", "119.119.119.119", 4343, 4242)
a4 := parsed(ipproto.UDP, "119.119.119.119", "102.102.102.102", 4242, 4343)
b4 := parsed(ipproto.UDP, "102.102.102.102", "119.119.119.119", 4343, 4242)
// Unsollicited UDP traffic gets dropped
if got := acl.RunIn(&a4, flags); got != Drop {
@@ -139,8 +158,8 @@ func TestUDPState(t *testing.T) {
t.Fatalf("incoming response packet not accepted, got=%v: %v", got, a4)
}
a6 := parsed(packet.UDP, "2001::2", "2001::1", 4242, 4343)
b6 := parsed(packet.UDP, "2001::1", "2001::2", 4343, 4242)
a6 := parsed(ipproto.UDP, "2001::2", "2001::1", 4242, 4343)
b6 := parsed(ipproto.UDP, "2001::1", "2001::2", 4343, 4242)
// Unsollicited UDP traffic gets dropped
if got := acl.RunIn(&a6, flags); got != Drop {
@@ -159,10 +178,10 @@ func TestUDPState(t *testing.T) {
func TestNoAllocs(t *testing.T) {
acl := newFilter(t.Logf)
tcp4Packet := raw4(packet.TCP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
udp4Packet := raw4(packet.UDP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
tcp6Packet := raw6(packet.TCP, "2001::1", "2001::2", 999, 22, 0)
udp6Packet := raw6(packet.UDP, "2001::1", "2001::2", 999, 22, 0)
tcp4Packet := raw4(ipproto.TCP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
udp4Packet := raw4(ipproto.UDP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
tcp6Packet := raw6(ipproto.TCP, "2001::1", "2001::2", 999, 22, 0)
udp6Packet := raw6(ipproto.UDP, "2001::1", "2001::2", 999, 22, 0)
tests := []struct {
name string
@@ -243,13 +262,13 @@ func TestParseIPSet(t *testing.T) {
}
func BenchmarkFilter(b *testing.B) {
tcp4Packet := raw4(packet.TCP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
udp4Packet := raw4(packet.UDP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
icmp4Packet := raw4(packet.ICMPv4, "8.1.1.1", "1.2.3.4", 0, 0, 0)
tcp4Packet := raw4(ipproto.TCP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
udp4Packet := raw4(ipproto.UDP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
icmp4Packet := raw4(ipproto.ICMPv4, "8.1.1.1", "1.2.3.4", 0, 0, 0)
tcp6Packet := raw6(packet.TCP, "::1", "2001::1", 999, 22, 0)
udp6Packet := raw6(packet.UDP, "::1", "2001::1", 999, 22, 0)
icmp6Packet := raw6(packet.ICMPv6, "::1", "2001::1", 0, 0, 0)
tcp6Packet := raw6(ipproto.TCP, "::1", "2001::1", 999, 22, 0)
udp6Packet := raw6(ipproto.UDP, "::1", "2001::1", 999, 22, 0)
icmp6Packet := raw6(ipproto.ICMPv6, "::1", "2001::1", 0, 0, 0)
benches := []struct {
name string
@@ -296,11 +315,11 @@ func TestPreFilter(t *testing.T) {
}{
{"empty", Accept, []byte{}},
{"short", Drop, []byte("short")},
{"junk", Drop, raw4default(packet.Unknown, 10)},
{"fragment", Accept, raw4default(packet.Fragment, 40)},
{"tcp", noVerdict, raw4default(packet.TCP, 0)},
{"udp", noVerdict, raw4default(packet.UDP, 0)},
{"icmp", noVerdict, raw4default(packet.ICMPv4, 0)},
{"junk", Drop, raw4default(ipproto.Unknown, 10)},
{"fragment", Accept, raw4default(ipproto.Fragment, 40)},
{"tcp", noVerdict, raw4default(ipproto.TCP, 0)},
{"udp", noVerdict, raw4default(ipproto.UDP, 0)},
{"icmp", noVerdict, raw4default(ipproto.ICMPv4, 0)},
}
f := NewAllowNone(t.Logf, &netaddr.IPSet{})
for _, testPacket := range packets {
@@ -322,7 +341,7 @@ func TestOmitDropLogging(t *testing.T) {
}{
{
name: "v4_tcp_out",
pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP},
pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP},
dir: out,
want: false,
},
@@ -420,73 +439,73 @@ func TestLoggingPrivacy(t *testing.T) {
}{
{
name: "ts_to_ts_v4_out",
pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: ts4, Dst: ts4},
pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: ts4, Dst: ts4},
dir: out,
logged: true,
},
{
name: "ts_to_internet_v4_out",
pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: ts4, Dst: internet4},
pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: ts4, Dst: internet4},
dir: out,
logged: false,
},
{
name: "internet_to_ts_v4_out",
pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: internet4, Dst: ts4},
pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: internet4, Dst: ts4},
dir: out,
logged: false,
},
{
name: "ts_to_ts_v4_in",
pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: ts4, Dst: ts4},
pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: ts4, Dst: ts4},
dir: in,
logged: true,
},
{
name: "ts_to_internet_v4_in",
pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: ts4, Dst: internet4},
pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: ts4, Dst: internet4},
dir: in,
logged: false,
},
{
name: "internet_to_ts_v4_in",
pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: internet4, Dst: ts4},
pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: internet4, Dst: ts4},
dir: in,
logged: false,
},
{
name: "ts_to_ts_v6_out",
pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: ts6, Dst: ts6},
pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: ts6, Dst: ts6},
dir: out,
logged: true,
},
{
name: "ts_to_internet_v6_out",
pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: ts6, Dst: internet6},
pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: ts6, Dst: internet6},
dir: out,
logged: false,
},
{
name: "internet_to_ts_v6_out",
pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: internet6, Dst: ts6},
pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: internet6, Dst: ts6},
dir: out,
logged: false,
},
{
name: "ts_to_ts_v6_in",
pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: ts6, Dst: ts6},
pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: ts6, Dst: ts6},
dir: in,
logged: true,
},
{
name: "ts_to_internet_v6_in",
pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: ts6, Dst: internet6},
pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: ts6, Dst: internet6},
dir: in,
logged: false,
},
{
name: "internet_to_ts_v6_in",
pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: internet6, Dst: ts6},
pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: internet6, Dst: ts6},
dir: in,
logged: false,
},
@@ -520,7 +539,7 @@ func mustIP(s string) netaddr.IP {
return ip
}
func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.Parsed {
func parsed(proto ipproto.Proto, src, dst string, sport, dport uint16) packet.Parsed {
sip, dip := mustIP(src), mustIP(dst)
var ret packet.Parsed
@@ -541,7 +560,7 @@ func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.P
return ret
}
func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen int) []byte {
func raw6(proto ipproto.Proto, src, dst string, sport, dport uint16, trimLen int) []byte {
u := packet.UDP6Header{
IP6Header: packet.IP6Header{
Src: mustIP(src),
@@ -570,7 +589,7 @@ func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen in
}
}
func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength int) []byte {
func raw4(proto ipproto.Proto, src, dst string, sport, dport uint16, trimLength int) []byte {
u := packet.UDP4Header{
IP4Header: packet.IP4Header{
Src: mustIP(src),
@@ -588,7 +607,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength
// UDP marshaling clobbers IPProto, so override it here.
switch proto {
case packet.Unknown, packet.Fragment:
case ipproto.Unknown, ipproto.Fragment:
default:
u.IP4Header.IPProto = proto
}
@@ -596,7 +615,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength
panic(err)
}
if proto == packet.Fragment {
if proto == ipproto.Fragment {
// Set some fragment offset. This makes the IP
// checksum wrong, but we don't validate the checksum
// when parsing.
@@ -610,7 +629,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength
}
}
func raw4default(proto packet.IPProto, trimLength int) []byte {
func raw4default(proto ipproto.Proto, trimLength int) []byte {
return raw4(proto, "8.8.8.8", "8.8.8.8", 53, 53, trimLength)
}
@@ -707,3 +726,91 @@ func netports(netPorts ...string) (ret []NetPortRange) {
}
return ret
}
func TestMatchesFromFilterRules(t *testing.T) {
tests := []struct {
name string
in []tailcfg.FilterRule
want []Match
}{
{
name: "empty",
want: []Match{},
},
{
name: "implicit_protos",
in: []tailcfg.FilterRule{
{
SrcIPs: []string{"100.64.1.1"},
DstPorts: []tailcfg.NetPortRange{{
IP: "*",
Ports: tailcfg.PortRange{First: 22, Last: 22},
}},
},
},
want: []Match{
{
IPProto: []ipproto.Proto{
ipproto.TCP,
ipproto.UDP,
ipproto.ICMPv4,
ipproto.ICMPv6,
},
Dsts: []NetPortRange{
{
Net: netaddr.MustParseIPPrefix("0.0.0.0/0"),
Ports: PortRange{22, 22},
},
{
Net: netaddr.MustParseIPPrefix("::0/0"),
Ports: PortRange{22, 22},
},
},
Srcs: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.1/32"),
},
},
},
},
{
name: "explicit_protos",
in: []tailcfg.FilterRule{
{
IPProto: []int{int(ipproto.TCP)},
SrcIPs: []string{"100.64.1.1"},
DstPorts: []tailcfg.NetPortRange{{
IP: "1.2.0.0/16",
Ports: tailcfg.PortRange{First: 22, Last: 22},
}},
},
},
want: []Match{
{
IPProto: []ipproto.Proto{
ipproto.TCP,
},
Dsts: []NetPortRange{
{
Net: netaddr.MustParseIPPrefix("1.2.0.0/16"),
Ports: PortRange{22, 22},
},
},
Srcs: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.1/32"),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := MatchesFromFilterRules(tt.in)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("wrong\n got: %v\nwant: %v\n", got, tt.want)
}
})
}
}

View File

@@ -10,6 +10,7 @@ import (
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/types/ipproto"
)
//go:generate go run tailscale.com/cmd/cloner --type=Match --output=match_clone.go
@@ -47,11 +48,13 @@ func (npr NetPortRange) String() string {
// Match matches packets from any IP address in Srcs to any ip:port in
// Dsts.
type Match struct {
Dsts []NetPortRange
Srcs []netaddr.IPPrefix
IPProto []ipproto.Proto // required set (no default value at this layer)
Dsts []NetPortRange
Srcs []netaddr.IPPrefix
}
func (m Match) String() string {
// TODO(bradfitz): use strings.Builder, add String tests
srcs := []string{}
for _, src := range m.Srcs {
srcs = append(srcs, src.String())
@@ -72,13 +75,16 @@ func (m Match) String() string {
} else {
ds = "[" + strings.Join(dsts, ",") + "]"
}
return fmt.Sprintf("%v=>%v", ss, ds)
return fmt.Sprintf("%v%v=>%v", m.IPProto, ss, ds)
}
type matches []Match
func (ms matches) match(q *packet.Parsed) bool {
for _, m := range ms {
if !protoInList(q.IPProto, m.IPProto) {
continue
}
if !ipInList(q.Src.IP, m.Srcs) {
continue
}
@@ -117,3 +123,12 @@ func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool {
}
return false
}
func protoInList(proto ipproto.Proto, valid []ipproto.Proto) bool {
for _, v := range valid {
if proto == v {
return true
}
}
return false
}

View File

@@ -8,6 +8,7 @@ package filter
import (
"inet.af/netaddr"
"tailscale.com/types/ipproto"
)
// Clone makes a deep copy of Match.
@@ -18,6 +19,7 @@ func (src *Match) Clone() *Match {
}
dst := new(Match)
*dst = *src
dst.IPProto = append(src.IPProto[:0:0], src.IPProto...)
dst.Dsts = append(src.Dsts[:0:0], src.Dsts...)
dst.Srcs = append(src.Srcs[:0:0], src.Srcs...)
return dst
@@ -26,6 +28,7 @@ func (src *Match) Clone() *Match {
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Match
var _MatchNeedsRegeneration = Match(struct {
Dsts []NetPortRange
Srcs []netaddr.IPPrefix
IPProto []ipproto.Proto
Dsts []NetPortRange
Srcs []netaddr.IPPrefix
}{})

View File

@@ -10,8 +10,16 @@ import (
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
)
var defaultProtos = []ipproto.Proto{
ipproto.TCP,
ipproto.UDP,
ipproto.ICMPv4,
ipproto.ICMPv6,
}
// MatchesFromFilterRules converts tailcfg FilterRules into Matches.
// If an error is returned, the Matches result is still valid,
// containing the rules that were successfully converted.
@@ -22,6 +30,17 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) {
for _, r := range pf {
m := Match{}
if len(r.IPProto) == 0 {
m.IPProto = append([]ipproto.Proto(nil), defaultProtos...)
} else {
m.IPProto = make([]ipproto.Proto, 0, len(r.IPProto))
for _, n := range r.IPProto {
if n >= 0 && n <= 0xff {
m.IPProto = append(m.IPProto, ipproto.Proto(n))
}
}
}
for i, s := range r.SrcIPs {
var bits *int
if len(r.SrcBits) > i {

View File

@@ -489,31 +489,31 @@ func (a *addrSet) updateDst(new netaddr.IPPort) error {
switch {
case index == -1:
if a.roamAddr == nil {
a.Logf("magicsock: rx %s from roaming address %s, set as new priority", pk, new)
a.Logf("[v1] magicsock: rx %s from roaming address %s, set as new priority", pk, new)
} else {
a.Logf("magicsock: rx %s from roaming address %s, replaces roaming address %s", pk, new, a.roamAddr)
a.Logf("[v1] magicsock: rx %s from roaming address %s, replaces roaming address %s", pk, new, a.roamAddr)
}
a.roamAddr = &new
case a.roamAddr != nil:
a.Logf("magicsock: rx %s from known %s (%d), replaces roaming address %s", pk, new, index, a.roamAddr)
a.Logf("[v1] magicsock: rx %s from known %s (%d), replaces roaming address %s", pk, new, index, a.roamAddr)
a.roamAddr = nil
a.curAddr = index
a.loggedLogPriMask = 0
case a.curAddr == -1:
a.Logf("magicsock: rx %s from %s (%d/%d), set as new priority", pk, new, index, len(a.ipPorts))
a.Logf("[v1] magicsock: rx %s from %s (%d/%d), set as new priority", pk, new, index, len(a.ipPorts))
a.curAddr = index
a.loggedLogPriMask = 0
case index < a.curAddr:
if 1 <= index && index <= 32 && (a.loggedLogPriMask&1<<(index-1)) == 0 {
a.Logf("magicsock: rx %s from low-pri %s (%d), keeping current %s (%d)", pk, new, index, old, a.curAddr)
a.Logf("[v1] magicsock: rx %s from low-pri %s (%d), keeping current %s (%d)", pk, new, index, old, a.curAddr)
a.loggedLogPriMask |= 1 << (index - 1)
}
default: // index > a.curAddr
a.Logf("magicsock: rx %s from %s (%d/%d), replaces old priority %s", pk, new, index, len(a.ipPorts), old)
a.Logf("[v1] magicsock: rx %s from %s (%d/%d), replaces old priority %s", pk, new, index, len(a.ipPorts), old)
a.curAddr = index
a.loggedLogPriMask = 0
}

View File

@@ -630,7 +630,7 @@ func (c *Conn) setEndpoints(endpoints []string, reasons map[string]string) (chan
delete(c.onEndpointRefreshed, de)
}
if stringsEqual(endpoints, c.lastEndpoints) {
if stringSetsEqual(endpoints, c.lastEndpoints) {
return false
}
c.lastEndpoints = endpoints
@@ -814,46 +814,6 @@ func (c *Conn) SetNetInfoCallback(fn func(*tailcfg.NetInfo)) {
}
}
// peerForIP returns the Node in nm that's responsible for
// handling the given IP address.
func peerForIP(nm *netmap.NetworkMap, ip netaddr.IP) (n *tailcfg.Node, ok bool) {
if nm == nil {
return nil, false
}
// Check for exact matches before looking for subnet matches.
for _, p := range nm.Peers {
for _, a := range p.Addresses {
if a.IP == ip {
return p, true
}
}
}
// 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) {
if best.IsZero() || cidr.Bits > best.Bits {
n = p
best = cidr
}
}
}
}
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 {
@@ -871,21 +831,14 @@ func (c *Conn) LastRecvActivityOfDisco(dk tailcfg.DiscoKey) time.Time {
}
// Ping handles a "tailscale ping" CLI query.
func (c *Conn) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) {
func (c *Conn) Ping(peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
c.mu.Lock()
defer c.mu.Unlock()
res := &ipnstate.PingResult{IP: ip.String()}
if c.privateKey.IsZero() {
res.Err = "local tailscaled stopped"
cb(res)
return
}
peer, ok := peerForIP(c.netMap, ip)
if !ok {
res.Err = "no matching peer"
cb(res)
return
}
if len(peer.Addresses) > 0 {
res.NodeIP = peer.Addresses[0].IP.String()
}
@@ -1111,12 +1064,32 @@ func (c *Conn) determineEndpoints(ctx context.Context) (ipPorts []string, reason
return eps, already, nil
}
func stringsEqual(x, y []string) bool {
if len(x) != len(y) {
return false
// stringSetsEqual reports whether x and y represent the same set of
// strings. The order doesn't matter.
//
// It does not mutate the slices.
func stringSetsEqual(x, y []string) bool {
if len(x) == len(y) {
orderMatches := true
for i := range x {
if x[i] != y[i] {
orderMatches = false
break
}
}
if orderMatches {
return true
}
}
for i := range x {
if x[i] != y[i] {
m := map[string]int{}
for _, v := range x {
m[v] |= 1
}
for _, v := range y {
m[v] |= 2
}
for _, n := range m {
if n != 3 {
return false
}
}
@@ -2034,7 +2007,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) (isDiscoMsg bo
return
}
if de != nil {
c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
c.logf("[v1] magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
c.discoShort, de.discoShort,
de.publicKey.ShortString(), derpStr(src.String()),
len(dm.MyNumber))
@@ -3012,25 +2985,7 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
c.mu.Lock()
defer c.mu.Unlock()
ss := &ipnstate.PeerStatus{
PublicKey: c.privateKey.Public(),
Addrs: c.lastEndpoints,
OS: version.OS(),
}
if c.netMap != nil {
ss.HostName = c.netMap.Hostinfo.Hostname
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]
if ok {
ss.Relay = derpRegion.RegionCode
}
}
var tailAddr string
if c.netMap != nil {
for _, addr := range c.netMap.Addresses {
if !addr.IsSingleIP() {
@@ -3041,11 +2996,30 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
// readability of `tailscale status`, make it the IPv4
// address.
if addr.IP.Is4() {
ss.TailAddr = addr.IP.String()
tailAddr = addr.IP.String()
}
}
}
sb.SetSelfStatus(ss)
sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
ss.PublicKey = c.privateKey.Public()
ss.Addrs = c.lastEndpoints
ss.OS = version.OS()
if c.netMap != nil {
ss.HostName = c.netMap.Hostinfo.Hostname
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]
if ok {
ss.Relay = derpRegion.RegionCode
}
}
ss.TailAddr = tailAddr
})
for dk, n := range c.nodeOfDisco {
ps := &ipnstate.PeerStatus{InMagicSock: true}
@@ -3106,10 +3080,9 @@ type discoEndpoint struct {
lastFullPing time.Time // last time we pinged all endpoints
derpAddr netaddr.IPPort // fallback/bootstrap path, if non-zero (non-zero for well-behaved clients)
bestAddr netaddr.IPPort // best non-DERP path; zero if none
bestAddrLatency time.Duration
bestAddrAt time.Time // time best address re-confirmed
trustBestAddrUntil time.Time // time when bestAddr expires
bestAddr addrLatency // best non-DERP path; zero if none
bestAddrAt time.Time // time best address re-confirmed
trustBestAddrUntil time.Time // time when bestAddr expires
sentPing map[stun.TxID]sentPing
endpointState map[netaddr.IPPort]*endpointState
isCallMeMaybeEP map[netaddr.IPPort]bool
@@ -3214,8 +3187,8 @@ func (st *endpointState) shouldDeleteLocked() bool {
func (de *discoEndpoint) deleteEndpointLocked(ep netaddr.IPPort) {
delete(de.endpointState, ep)
if de.bestAddr == ep {
de.bestAddr = netaddr.IPPort{}
if de.bestAddr.IPPort == ep {
de.bestAddr = addrLatency{}
}
}
@@ -3283,7 +3256,7 @@ func (de *discoEndpoint) DstToBytes() []byte { return packIPPort(de.fakeWGAddr)
//
// de.mu must be held.
func (de *discoEndpoint) addrForSendLocked(now time.Time) (udpAddr, derpAddr netaddr.IPPort) {
udpAddr = de.bestAddr
udpAddr = de.bestAddr.IPPort
if udpAddr.IsZero() || now.After(de.trustBestAddrUntil) {
// We had a bestAddr but it expired so send both to it
// and DERP.
@@ -3336,7 +3309,7 @@ func (de *discoEndpoint) wantFullPingLocked(now time.Time) bool {
if now.After(de.trustBestAddrUntil) {
return true
}
if de.bestAddrLatency <= goodEnoughLatency {
if de.bestAddr.latency <= goodEnoughLatency {
return false
}
if now.Sub(de.lastFullPing) >= upgradeInterval {
@@ -3589,7 +3562,7 @@ func (de *discoEndpoint) addCandidateEndpoint(ep netaddr.IPPort) {
}
// Newly discovered endpoint. Exciting!
de.c.logf("magicsock: disco: adding %v as candidate endpoint for %v (%s)", ep, de.discoShort, de.publicKey.ShortString())
de.c.logf("[v1] magicsock: disco: adding %v as candidate endpoint for %v (%s)", ep, de.discoShort, de.publicKey.ShortString())
de.endpointState[ep] = &endpointState{
lastGotPing: time.Now(),
}
@@ -3602,7 +3575,7 @@ func (de *discoEndpoint) addCandidateEndpoint(ep netaddr.IPPort) {
}
}
size2 := len(de.endpointState)
de.c.logf("magicsock: disco: addCandidateEndpoint pruned %v candidate set from %v to %v entries", size, size2)
de.c.logf("[v1] magicsock: disco: addCandidateEndpoint pruned %v candidate set from %v to %v entries", size, size2)
}
}
@@ -3668,20 +3641,50 @@ func (de *discoEndpoint) handlePongConnLocked(m *disco.Pong, src netaddr.IPPort)
// Promote this pong response to our current best address if it's lower latency.
// TODO(bradfitz): decide how latency vs. preference order affects decision
if !isDerp {
if de.bestAddr.IsZero() || latency < de.bestAddrLatency {
if de.bestAddr != sp.to {
de.c.logf("magicsock: disco: node %v %v now using %v", de.publicKey.ShortString(), de.discoShort, sp.to)
de.bestAddr = sp.to
}
thisPong := addrLatency{sp.to, latency}
if betterAddr(thisPong, de.bestAddr) {
de.c.logf("magicsock: disco: node %v %v now using %v", de.publicKey.ShortString(), de.discoShort, sp.to)
de.bestAddr = thisPong
}
if de.bestAddr == sp.to {
de.bestAddrLatency = latency
if de.bestAddr.IPPort == thisPong.IPPort {
de.bestAddr.latency = latency
de.bestAddrAt = now
de.trustBestAddrUntil = now.Add(trustUDPAddrDuration)
}
}
}
// addrLatency is an IPPort with an associated latency.
type addrLatency struct {
netaddr.IPPort
latency time.Duration
}
// betterAddr reports whether a is a better addr to use than b.
func betterAddr(a, b addrLatency) bool {
if a.IPPort == b.IPPort {
return false
}
if b.IsZero() {
return true
}
if a.IsZero() {
return false
}
if a.IP.Is6() && b.IP.Is4() {
// Prefer IPv6 for being a bit more robust, as long as
// the latencies are roughly equivalent.
if a.latency/10*9 < b.latency {
return true
}
} else if a.IP.Is4() && b.IP.Is6() {
if betterAddr(b, a) {
return false
}
}
return a.latency < b.latency
}
// discoEndpoint.mu must be held.
func (st *endpointState) addPongReplyLocked(r pongReply) {
if n := len(st.recentPongs); n < pongHistoryCount {
@@ -3729,7 +3732,7 @@ func (de *discoEndpoint) handleCallMeMaybe(m *disco.CallMeMaybe) {
}
}
if len(newEPs) > 0 {
de.c.logf("magicsock: disco: call-me-maybe from %v %v added new endpoints: %v",
de.c.logf("[v1] magicsock: disco: call-me-maybe from %v %v added new endpoints: %v",
de.publicKey.ShortString(), de.discoShort,
logger.ArgWriter(func(w *bufio.Writer) {
for i, ep := range newEPs {
@@ -3788,8 +3791,7 @@ func (de *discoEndpoint) stopAndReset() {
// state isn't a mix of before & after two sessions.
de.lastSend = time.Time{}
de.lastFullPing = time.Time{}
de.bestAddr = netaddr.IPPort{}
de.bestAddrLatency = 0
de.bestAddr = addrLatency{}
de.bestAddrAt = time.Time{}
de.trustBestAddrUntil = time.Time{}
for _, es := range de.endpointState {

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