Compare commits

..

35 Commits

Author SHA1 Message Date
David Anderson
fa4dc33eab VERSION.txt: this is 1.2.7.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-11-16 14:22:03 -08:00
David Anderson
b6e541e2eb wgengine/filter: don't drop GCP DNS.
Manual backport of 3c508a58cc.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-11-16 14:14:26 -08:00
Brad Fitzpatrick
3b75550ad0 wgengine/router: lock goroutine to OS thread before using OLE [windows]
See https://github.com/tailscale/tailscale/issues/921#issuecomment-727526807

Not yet sure whether this is our problem, but it can't hurt at least,
and seems like what we're supposed to do.

Updates #921

(cherry picked from commit fc8bc76e58)
2020-11-16 14:10:31 -08:00
Brad Fitzpatrick
8ae146478c net/netstat: remove some unsafe
Just removing any unnecessary unsafe while auditing unsafe usage for #921.

(cherry picked from commit 7a01cd27ca)
2020-11-16 14:10:14 -08:00
Brad Fitzpatrick
e854b433aa net/netns: remove use of unsafe on Windows
Found while auditing unsafe for #921 via the list at:

https://github.com/tailscale/tailscale/issues/921#issuecomment-727365383

No need for unsafe here, so remove it.

(cherry picked from commit 45d96788b5)
2020-11-16 14:08:49 -08:00
Brad Fitzpatrick
d285b548bf util/endian: add package with const for whether platform is big endian
(cherry picked from commit 000347d4cf)
2020-11-16 14:08:16 -08:00
David Anderson
3f4e6d959a VERSION.txt: this is 1.2.6.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-11-12 22:01:50 -08:00
Brad Fitzpatrick
449cbf5cfb control/controlclient: diagnose zero bytes from control
Updates #921

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit fac2b30eff)
2020-11-12 14:45:20 -08:00
David Anderson
c242540a97 wgengine/router: disable IPv6 if v6 policy routing is unavailable.
Fixes #895.

Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit a664aac877)
2020-11-11 17:22:38 -08:00
Avery Pennarun
e29f92f653 VERSION.txt: this is v1.2.5
Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2020-11-11 02:57:08 -05:00
Avery Pennarun
82dbf148a3 .gitignore: ignore *.tmp files.
This fixes the problem where, while running `redo version-info.sh`, the
repo would always show up as dirty, because redo creates a temp file
named *.tmp. This caused the version code to always have a -dirty tag,
but not when you run version.sh by hand.

Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2020-11-11 01:54:54 -05:00
Avery Pennarun
df38ea4d65 version.sh: keep the short version even if there are patches on top.
Instead of reverting to 0.0.0, keep the same version number (eg. 1.2.4)
but add an extra suffix with the change count,
eg. 1.2.4-6-tb35d95ad7-gcb8be72e6. This avoids the problem where a
small patch causes the code to report a totally different version to
the server, which might change its behaviour based on version code.
(The server might enable various bug workarounds since it thinks
0.0.0 is very old.)

Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2020-11-11 00:10:57 -05:00
Avery Pennarun
28f3136611 version.sh: remove use of git describe --exclude
This option isn't available on slightly older versions of git. We were
no longer using the real describe functionality anyway, so let's just do
something simpler to detect a dirty worktree.

While we're here, fix up a little bit of sh style.

Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2020-11-11 00:10:57 -05:00
Avery Pennarun
1be01ddc6e Reverse earlier "allow tag without 'tag:' prefix" changes.
These accidentally make the tag syntax more flexible than was intended,
which will create forward compatibility problems later. Let's go back
to the old stricter parser.

Revert "cmd/tailscale/cli: fix double tag: prefix in tailscale up"
Revert "cmd/tailscale/cli, tailcfg: allow tag without "tag:" prefix in 'tailscale up'"

This reverts commit a702921620.
This reverts commit cd07437ade.

Affects #861.

Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2020-11-10 22:30:02 -05:00
Brad Fitzpatrick
bddc882f7d net/interfaces: ignore bogus proxy URLs from winhttp [windows]
Updates tailscale/corp#853

(cherry picked from commit d192bd0f86)
2020-11-10 14:42:58 -08:00
Brad Fitzpatrick
33505097c4 ipn, tailcfg: change Windows subnet disabling behavior w/ WPAD
In 1.0, subnet relays were not specially handled when WPAD+PAC was
present on the network.

In 1.2, on Windows, subnet relays were disabled if WPAD+PAC was
present. That was what some users wanted, but not others.

This makes it configurable per domain, reverting back to the 1.0
default state of them not being special. Users who want that behavior
can then enable it.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit d21956436a)
2020-11-10 14:42:53 -08:00
Brad Fitzpatrick
d8a531108d wgengine/magicsock: quiet an IPv6 warning in tests
In tests, we force binding to localhost to avoid OS firewall warning
dialogs.

But for IPv6, we were trying (and failing) to bind to 127.0.0.1.

You'd think we'd just say "localhost", but that's apparently ill
defined. See
https://tools.ietf.org/html/draft-ietf-dnsop-let-localhost-be-localhost
and golang/go#22826. (It's bitten me in the past, but I can't
remember specific bugs.)

So use "::1" explicitly for "udp6", which makes the test quieter.

(cherry picked from commit 450cfedeba)
2020-11-10 14:42:49 -08:00
David Anderson
c73c3001a4 tailscaled.service: also cleanup prior to starting.
Fixes #813.

Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit 7988f75b87)
2020-11-09 20:17:25 -08:00
David Crawshaw
c572d622d7 VERSION.txt: this is v1.2.4 2020-11-08 10:39:42 -05:00
David Crawshaw
fead79a02f version/version.sh: strip wc whitespace on macos
The output of `wc -l` on darwin starts with a tab:

	git rev-list 266f6548611ad0de93e7470eb13731db819f184b..HEAD | wc -l
	       0

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-11-08 10:39:05 -05:00
David Crawshaw
266f654861 VERSION.txt: this is 1.2.3 2020-11-08 10:09:20 -05:00
Brad Fitzpatrick
d91a9131b1 ipn: debug zero bytes in IPN json messages
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit b4e19b95ed)
2020-11-06 13:19:52 -08:00
Brad Fitzpatrick
dced1d6a37 ipn: treat zero-length file state store file as missing
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 8f30fa67aa)
2020-11-06 13:01:29 -08:00
Brad Fitzpatrick
d5bc375b0e wgengine/router: don't double-prefix dns log messages [Windows]
(cherry picked from commit 119101962c)
2020-11-06 13:01:29 -08:00
Brad Fitzpatrick
c1bae7ad64 tailcfg: document FilterRule
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit bda53897b5)
2020-11-06 13:01:29 -08:00
David Anderson
76c2982d88 VERSION.txt: this is 1.2.2. 2020-11-05 12:25:25 -08:00
Brad Fitzpatrick
3d64eef37b control/controlclient: send warning flag in map request when IP forwarding off
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 782e07c0ae)
2020-11-04 14:53:01 -08:00
Brad Fitzpatrick
4f292740b0 ipn: clean up Prefs logging at start
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 4f4e84236a)
2020-11-04 12:06:25 -08:00
Brad Fitzpatrick
e1327154bb ipn: disambiguate how machine key was initialized
Seeing "frontend-provided legacy machine key" was weird (and not quite
accurate) on Linux machines where it comes from the _daemon key's
persist prefs, not the "frontend".

Make the log message distinguish between the cases.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 6bcb466096)
2020-11-04 12:06:16 -08:00
Brad Fitzpatrick
a702921620 cmd/tailscale/cli: fix double tag: prefix in tailscale up
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 696e160cfc)
2020-11-04 08:19:39 -08:00
David Anderson
b9b7fbdd21 build_dist: fix after version refactor.
(cherry picked from commit 39bbb86b09)
2020-11-03 14:50:27 -08:00
Avery Pennarun
9446e5c170 VERSION.txt: this is v1.2.1. 2020-11-03 17:34:05 -05:00
Avery Pennarun
75cd82791e Merge remote-tracking branch 'origin/main' into HEAD
* origin/main:
  wgengine/router/dns: run ipconfig /registerdns async, log timing
  net/tshttpproxy: aggressively rate-limit error logs in Transport.Proxy path
  ipn: only use Prefs, not computed stateKey, to determine server mode
  VERSION: rename to version.txt to work around macOS limitations.
  version: greatly simplify redo nonsense, now that we use VERSION.
  ipn, ipn/ipnserver: add IPN state for server in use, handle explicitly
  version: calculate version info without using git tags.
  version: use -g as the "other" suffix, so that `git show` works.
  ipn/ipnserver: remove "Server mode" from a user-visible error message
  ipn: fix crash generating machine key on new installs
  Change some os.IsNotExist to errors.Is(err, os.ErrNotExist) for non-os errors.
  .github/workflows: use cache to speed up Windows tests
  tsweb: add StatusCodeCounters to HandlerOptions
  tsweb: add StdHandlerOpts that accepts an options struct
  ipn: don't temporarilySetMachineKeyInPersist for Android clients
2020-11-03 17:33:23 -05:00
David Anderson
4d5d5f89a3 version: calculate version info without using git tags.
This makes it easier to integrate this version math into a submodule-ful
world. We'll continue to have regular git tags that parallel the information
in VERSION, so that builds out of this repository behave the same.

Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit 437142daa5)
(altered VERSION to reflect correct information for this release branch)
2020-11-02 15:24:36 -08:00
Avery Pennarun
bb058703ee Meaningless change so that v1.2.0 tag doesn't match the "main" branch. 2020-10-30 04:04:50 -04:00
179 changed files with 6620 additions and 9579 deletions

View File

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

6
.gitignore vendored
View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
1.3.0
1.2.7

774
api.md
View File

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

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ import (
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/derp/derpmap"
"tailscale.com/net/dnscache"
"tailscale.com/net/netcheck"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
@@ -43,7 +44,9 @@ var netcheckArgs struct {
}
func runNetcheck(ctx context.Context, args []string) error {
c := &netcheck.Client{}
c := &netcheck.Client{
DNSCache: dnscache.Get(),
}
if netcheckArgs.verbose {
c.Logf = logger.WithPrefix(log.Printf, "netcheck: ")
c.Verbose = true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,9 +9,9 @@ import (
"tailscale.com/wgengine/filter"
)
// Parse a backward-compatible FilterRule used by control's wire
// format, producing the most current filter format.
func (c *Direct) parsePacketFilter(pf []tailcfg.FilterRule) []filter.Match {
// Parse a backward-compatible FilterRule used by control's wire format,
// producing the most current filter.Matches format.
func (c *Direct) parsePacketFilter(pf []tailcfg.FilterRule) filter.Matches {
mm, err := filter.MatchesFromFilterRules(pf)
if err != nil {
c.logf("parsePacketFilter: %s\n", err)

View File

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

View File

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

View File

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

View File

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

View File

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

37
go.mod
View File

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

461
go.sum
View File

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

View File

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

View File

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

View File

@@ -1,49 +0,0 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package ipnserver
import (
"net"
"golang.org/x/sys/unix"
"tailscale.com/types/logger"
)
func isReadonlyConn(c net.Conn, logf logger.Logf) (ro bool) {
ro = true // conservative default for naked returns below
uc, ok := c.(*net.UnixConn)
if !ok {
logf("unexpected connection type %T", c)
return
}
raw, err := uc.SyscallConn()
if err != nil {
logf("SyscallConn: %v", err)
return
}
var cred *unix.Ucred
cerr := raw.Control(func(fd uintptr) {
cred, err = unix.GetsockoptUcred(int(fd),
unix.SOL_SOCKET,
unix.SO_PEERCRED)
})
if cerr != nil {
logf("raw.Control: %v", err)
return
}
if err != nil {
logf("raw.Control: %v", err)
return
}
if cred.Uid == 0 {
// root is not read-only.
return false
}
logf("non-root connection from %v (read-only)", cred.Uid)
return true
}

View File

@@ -1,27 +0,0 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux
package ipnserver
import (
"net"
"tailscale.com/types/logger"
)
func isReadonlyConn(c net.Conn, logf logger.Logf) bool {
// Windows doesn't need/use this mechanism, at least yet. It
// has a different last-user-wins auth model.
// And on Darwin, we're not using it yet, as the Darwin
// tailscaled port isn't yet done, and unix.Ucred and
// unix.GetsockoptUcred aren't in x/sys/unix.
// TODO(bradfitz): OpenBSD and FreeBSD should implement this too.
// But their x/sys/unix package is different than Linux, so
// I didn't include it for now.
return false
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,119 +0,0 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ipn
import (
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/tailcfg"
"testing"
)
func TestNetworkMapCompare(t *testing.T) {
prefix1, err := netaddr.ParseIPPrefix("192.168.0.0/24")
if err != nil {
t.Fatal(err)
}
node1 := &tailcfg.Node{Addresses: []netaddr.IPPrefix{prefix1}}
prefix2, err := netaddr.ParseIPPrefix("10.0.0.0/8")
if err != nil {
t.Fatal(err)
}
node2 := &tailcfg.Node{Addresses: []netaddr.IPPrefix{prefix2}}
tests := []struct {
name string
a, b *controlclient.NetworkMap
want bool
}{
{
"both nil",
nil,
nil,
true,
},
{
"b nil",
&controlclient.NetworkMap{},
nil,
false,
},
{
"a nil",
nil,
&controlclient.NetworkMap{},
false,
},
{
"both default",
&controlclient.NetworkMap{},
&controlclient.NetworkMap{},
true,
},
{
"names identical",
&controlclient.NetworkMap{Name: "map1"},
&controlclient.NetworkMap{Name: "map1"},
true,
},
{
"names differ",
&controlclient.NetworkMap{Name: "map1"},
&controlclient.NetworkMap{Name: "map2"},
false,
},
{
"Peers identical",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{}},
true,
},
{
"Peer list length",
// length of Peers list differs
&controlclient.NetworkMap{Peers: []*tailcfg.Node{{}}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{}},
false,
},
{
"Node names identical",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}},
true,
},
{
"Node names differ",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "B"}}},
false,
},
{
"Node lists identical",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
true,
},
{
"Node lists differ",
&controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node2}},
false,
},
{
"Node Users differ",
// User field is not checked.
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{User: 0}}},
&controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{User: 1}}},
true,
},
}
for _, tt := range tests {
got := dnsMapsEqual(tt.a, tt.b)
if got != tt.want {
t.Errorf("%s: Equal = %v; want %v", tt.name, got, tt.want)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ import (
"strings"
"time"
"golang.org/x/term"
"golang.org/x/crypto/ssh/terminal"
"tailscale.com/atomicfile"
"tailscale.com/logtail"
"tailscale.com/logtail/filch"
@@ -35,7 +35,6 @@ import (
"tailscale.com/paths"
"tailscale.com/smallzstd"
"tailscale.com/types/logger"
"tailscale.com/util/racebuild"
"tailscale.com/version"
)
@@ -49,7 +48,7 @@ type Config struct {
// Policy is a logger and its public ID.
type Policy struct {
// Logtail is the logger.
Logtail *logtail.Logger
Logtail logtail.Logger
// PublicID is the logger's instance identifier.
PublicID logtail.PublicID
}
@@ -311,7 +310,7 @@ func tryFixLogStateLocation(dir, cmdname string) {
// given collection name.
func New(collection string) *Policy {
var lflags int
if term.IsTerminal(2) || runtime.GOOS == "windows" {
if terminal.IsTerminal(2) || runtime.GOOS == "windows" {
lflags = 0
} else {
lflags = log.LstdFlags
@@ -391,13 +390,13 @@ func New(collection string) *Policy {
if filchBuf != nil {
c.Buffer = filchBuf
}
lw := logtail.NewLogger(c, log.Printf)
lw := logtail.Log(c, log.Printf)
log.SetFlags(0) // other logflags are set on console, not here
log.SetOutput(lw)
log.Printf("Program starting: v%v, Go %v: %#v",
version.Long,
goVersion(),
strings.TrimPrefix(runtime.Version(), "go"),
os.Args)
log.Printf("LogID: %v", newc.PublicID)
if filchErr != nil {
@@ -413,15 +412,6 @@ func New(collection string) *Policy {
}
}
// SetVerbosityLevel controls the verbosity level that should be
// written to stderr. 0 is the default (not verbose). Levels 1 or higher
// are increasingly verbose.
//
// It should not be changed concurrently with log writes.
func (p *Policy) SetVerbosityLevel(level int) {
p.Logtail.SetVerbosityLevel(level)
}
// Close immediately shuts down the logger.
func (p *Policy) Close() {
ctx, cancel := context.WithCancel(context.Background())
@@ -489,11 +479,3 @@ func newLogtailTransport(host string) *http.Transport {
return tr
}
func goVersion() string {
v := strings.TrimPrefix(runtime.Version(), "go")
if racebuild.On {
return v + "-race"
}
return v
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,11 +9,8 @@ package dnscache
import (
"context"
"fmt"
"log"
"net"
"os"
"runtime"
"strconv"
"sync"
"time"
@@ -28,7 +25,7 @@ func preferGoResolver() bool {
// There does not appear to be a local resolver running
// on iOS, and NetworkExtension is good at isolating DNS.
// So do not use the Go resolver on macOS/iOS.
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
if runtime.GOOS == "darwin" {
return false
}
@@ -45,6 +42,8 @@ func preferGoResolver() bool {
// Get returns a caching Resolver singleton.
func Get() *Resolver { return single }
const fixedTTL = 10 * time.Minute
// Resolver is a minimal DNS caching resolver.
//
// The TTL is always fixed for now. It's not intended for general use.
@@ -55,15 +54,6 @@ type Resolver struct {
// If nil, net.DefaultResolver is used.
Forward *net.Resolver
// TTL is how long to keep entries cached
//
// If zero, a default (currently 10 minutes) is used.
TTL time.Duration
// UseLastGood controls whether a cached entry older than TTL is used
// if a refresh fails.
UseLastGood bool
sf singleflight.Group
mu sync.Mutex
@@ -71,8 +61,7 @@ type Resolver struct {
}
type ipCacheEntry struct {
ip net.IP // either v4 or v6
ip6 net.IP // nil if no v4 or no v6
ip net.IP
expires time.Time
}
@@ -83,160 +72,74 @@ func (r *Resolver) fwd() *net.Resolver {
return net.DefaultResolver
}
func (r *Resolver) ttl() time.Duration {
if r.TTL > 0 {
return r.TTL
}
return 10 * time.Minute
}
var debug, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_DNS_CACHE"))
// LookupIP returns the host's primary IP address (either IPv4 or
// IPv6, but preferring IPv4) and optionally its IPv6 address, if
// there is both IPv4 and IPv6.
//
// If err is nil, ip will be non-nil. The v6 address may be nil even
// with a nil error.
func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 net.IP, err error) {
// LookupIP returns the first IPv4 address found, otherwise the first IPv6 address.
func (r *Resolver) LookupIP(ctx context.Context, host string) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
return ip4, nil, nil
return ip4, nil
}
if debug {
log.Printf("dnscache: %q is an IP", host)
}
return ip, nil, nil
return ip, nil
}
if ip, ip6, ok := r.lookupIPCache(host); ok {
if debug {
log.Printf("dnscache: %q = %v (cached)", host, ip)
}
return ip, ip6, nil
if ip, ok := r.lookupIPCache(host); ok {
return ip, nil
}
type ipPair struct {
ip, ip6 net.IP
}
ch := r.sf.DoChan(host, func() (interface{}, error) {
ip, ip6, err := r.lookupIP(host)
ip, err := r.lookupIP(host)
if err != nil {
return nil, err
}
return ipPair{ip, ip6}, nil
return ip, nil
})
select {
case res := <-ch:
if res.Err != nil {
if r.UseLastGood {
if ip, ip6, ok := r.lookupIPCacheExpired(host); ok {
if debug {
log.Printf("dnscache: %q using %v after error", host, ip)
}
return ip, ip6, nil
}
}
if debug {
log.Printf("dnscache: error resolving %q: %v", host, res.Err)
}
return nil, nil, res.Err
return nil, res.Err
}
pair := res.Val.(ipPair)
return pair.ip, pair.ip6, nil
return res.Val.(net.IP), nil
case <-ctx.Done():
if debug {
log.Printf("dnscache: context done while resolving %q: %v", host, ctx.Err())
}
return nil, nil, ctx.Err()
return nil, ctx.Err()
}
}
func (r *Resolver) lookupIPCache(host string) (ip, ip6 net.IP, ok bool) {
func (r *Resolver) lookupIPCache(host string) (ip net.IP, ok bool) {
r.mu.Lock()
defer r.mu.Unlock()
if ent, ok := r.ipCache[host]; ok && ent.expires.After(time.Now()) {
return ent.ip, ent.ip6, true
return ent.ip, true
}
return nil, nil, false
return nil, false
}
func (r *Resolver) lookupIPCacheExpired(host string) (ip, ip6 net.IP, ok bool) {
r.mu.Lock()
defer r.mu.Unlock()
if ent, ok := r.ipCache[host]; ok {
return ent.ip, ent.ip6, true
}
return nil, nil, false
}
func (r *Resolver) lookupTimeoutForHost(host string) time.Duration {
if r.UseLastGood {
if _, _, ok := r.lookupIPCacheExpired(host); ok {
// If we have some previous good value for this host,
// don't give this DNS lookup much time. If we're in a
// situation where the user's DNS server is unreachable
// (e.g. their corp DNS server is behind a subnet router
// that can't come up due to Tailscale needing to
// connect to itself), then we want to fail fast and let
// our caller (who set UseLastGood) fall back to using
// the last-known-good IP address.
return 3 * time.Second
}
}
return 10 * time.Second
}
func (r *Resolver) lookupIP(host string) (ip, ip6 net.IP, err error) {
if ip, ip6, ok := r.lookupIPCache(host); ok {
if debug {
log.Printf("dnscache: %q found in cache as %v", host, ip)
}
return ip, ip6, nil
func (r *Resolver) lookupIP(host string) (net.IP, error) {
if ip, ok := r.lookupIPCache(host); ok {
return ip, nil
}
ctx, cancel := context.WithTimeout(context.Background(), r.lookupTimeoutForHost(host))
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
ips, err := r.fwd().LookupIPAddr(ctx, host)
if err != nil {
return nil, nil, err
return nil, err
}
if len(ips) == 0 {
return nil, nil, fmt.Errorf("no IPs for %q found", host)
return nil, fmt.Errorf("no IPs for %q found", host)
}
have4 := false
for _, ipa := range ips {
if ip4 := ipa.IP.To4(); ip4 != nil {
if !have4 {
ip6 = ip
ip = ip4
have4 = true
}
} else {
if have4 {
ip6 = ipa.IP
} else {
ip = ipa.IP
}
return r.addIPCache(host, ip4, fixedTTL), nil
}
}
r.addIPCache(host, ip, ip6, r.ttl())
return ip, ip6, nil
return r.addIPCache(host, ips[0].IP, fixedTTL), nil
}
func (r *Resolver) addIPCache(host string, ip, ip6 net.IP, d time.Duration) {
func (r *Resolver) addIPCache(host string, ip net.IP, d time.Duration) net.IP {
if isPrivateIP(ip) {
// Don't cache obviously wrong entries from captive portals.
// TODO: use DoH or DoT for the forwarding resolver?
if debug {
log.Printf("dnscache: %q resolved to private IP %v; using but not caching", host, ip)
}
return
}
if debug {
log.Printf("dnscache: %q resolved to IP %v; caching", host, ip)
return ip
}
r.mu.Lock()
@@ -244,7 +147,8 @@ func (r *Resolver) addIPCache(host string, ip, ip6 net.IP, d time.Duration) {
if r.ipCache == nil {
r.ipCache = make(map[string]ipCacheEntry)
}
r.ipCache[host] = ipCacheEntry{ip: ip, ip6: ip6, expires: time.Now().Add(d)}
r.ipCache[host] = ipCacheEntry{ip: ip, expires: time.Now().Add(d)}
return ip
}
func mustCIDR(s string) *net.IPNet {
@@ -264,39 +168,3 @@ var (
private2 = mustCIDR("172.16.0.0/12")
private3 = mustCIDR("192.168.0.0/16")
)
type DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error)
// Dialer returns a wrapped DialContext func that uses the provided dnsCache.
func Dialer(fwd DialContextFunc, dnsCache *Resolver) DialContextFunc {
return func(ctx context.Context, network, address string) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
// Bogus. But just let the real dialer return an error rather than
// inventing a similar one.
return fwd(ctx, network, address)
}
ip, ip6, err := dnsCache.LookupIP(ctx, host)
if err != nil {
return nil, fmt.Errorf("failed to resolve %q: %w", host, err)
}
dst := net.JoinHostPort(ip.String(), port)
if debug {
log.Printf("dnscache: dialing %s, %s for %s", network, dst, address)
}
c, err := fwd(ctx, network, dst)
if err == nil || ctx.Err() != nil || ip6 == nil {
return c, err
}
// Fall back to trying IPv6.
// TODO(bradfitz): this is a primarily for IPv6-only
// hosts; it's not supposed to be a real Happy
// Eyeballs implementation. We should use the net
// package's implementation of that by plumbing this
// dnscache impl into net.Dialer.Resolver.Dial and
// unmarshal/marshal DNS queries/responses to the net
// package. This works for v6-only hosts for now.
dst = net.JoinHostPort(ip6.String(), port)
return fwd(ctx, network, dst)
}
}

View File

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

View File

@@ -1,82 +0,0 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flowtrack
import (
"testing"
"inet.af/netaddr"
)
func TestCache(t *testing.T) {
c := &Cache{MaxEntries: 2}
k1 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("1.1.1.1:1")}
k2 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("2.2.2.2:2")}
k3 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("3.3.3.3:3")}
k4 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("4.4.4.4:4")}
wantLen := func(want int) {
t.Helper()
if got := c.Len(); got != want {
t.Fatalf("Len = %d; want %d", got, want)
}
}
wantVal := func(key Tuple, want interface{}) {
t.Helper()
got, ok := c.Get(key)
if !ok {
t.Fatalf("Get(%q) failed; want value %v", key, want)
}
if got != want {
t.Fatalf("Get(%q) = %v; want %v", key, got, want)
}
}
wantMissing := func(key Tuple) {
t.Helper()
if got, ok := c.Get(key); ok {
t.Fatalf("Get(%q) = %v; want absent from cache", key, got)
}
}
wantLen(0)
c.RemoveOldest() // shouldn't panic
c.Remove(k4) // shouldn't panic
c.Add(k1, 1)
wantLen(1)
c.Add(k2, 2)
wantLen(2)
c.Add(k3, 3)
wantLen(2) // hit the max
wantMissing(k1)
c.Remove(k1)
wantLen(2) // no change; k1 should've been the deleted one per LRU
wantVal(k3, 3)
wantVal(k2, 2)
c.Remove(k2)
wantLen(1)
wantMissing(k2)
c.Add(k3, 30)
wantVal(k3, 30)
wantLen(1)
allocs := int(testing.AllocsPerRun(1000, func() {
got, ok := c.Get(k3)
if !ok {
t.Fatal("missing k3")
}
if got != 30 {
t.Fatalf("got = %d; want 30", got)
}
}))
if allocs != 0 {
t.Errorf("allocs = %v; want 0", allocs)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -93,7 +93,7 @@ func (t *Table) addEntries(fam int) error {
}
buf = buf[:size]
numEntries := endian.Native.Uint32(buf[:4])
numEntries := *(*uint32)(unsafe.Pointer(&buf[0]))
buf = buf[4:]
var recSize int

View File

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

View File

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

View File

@@ -1,16 +0,0 @@
// 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 packet contains packet parsing and marshaling utilities.
//
// Parsed provides allocation-free minimal packet header decoding, for
// use in packet filtering. The other types in the package are for
// constructing and marshaling packets into []bytes.
//
// To support allocation-free parsing, this package defines IPv4 and
// IPv6 address types. You should prefer to use netaddr's types,
// except where you absolutely need allocation-free IP handling
// (i.e. in the tunnel datapath) and are willing to implement all
// codepaths and data structures twice, once per IP family.
package packet

View File

@@ -1,90 +0,0 @@
// 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 packet
import "encoding/binary"
// icmp4HeaderLength is the size of the ICMPv4 packet header, not
// including the outer IP layer or the variable "response data"
// trailer.
const icmp4HeaderLength = 4
// ICMP4Type is an ICMPv4 type, as specified in
// https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml
type ICMP4Type uint8
const (
ICMP4EchoReply ICMP4Type = 0x00
ICMP4EchoRequest ICMP4Type = 0x08
ICMP4Unreachable ICMP4Type = 0x03
ICMP4TimeExceeded ICMP4Type = 0x0b
)
func (t ICMP4Type) String() string {
switch t {
case ICMP4EchoReply:
return "EchoReply"
case ICMP4EchoRequest:
return "EchoRequest"
case ICMP4Unreachable:
return "Unreachable"
case ICMP4TimeExceeded:
return "TimeExceeded"
default:
return "Unknown"
}
}
// ICMP4Code is an ICMPv4 code, as specified in
// https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml
type ICMP4Code uint8
const (
ICMP4NoCode ICMP4Code = 0
)
// ICMP4Header is an IPv4+ICMPv4 header.
type ICMP4Header struct {
IP4Header
Type ICMP4Type
Code ICMP4Code
}
// Len implements Header.
func (h ICMP4Header) Len() int {
return h.IP4Header.Len() + icmp4HeaderLength
}
// Marshal implements Header.
func (h ICMP4Header) Marshal(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if len(buf) > maxPacketLength {
return errLargePacket
}
// The caller does not need to set this.
h.IPProto = ICMPv4
buf[20] = uint8(h.Type)
buf[21] = uint8(h.Code)
h.IP4Header.Marshal(buf)
binary.BigEndian.PutUint16(buf[22:24], ip4Checksum(buf))
return nil
}
// ToResponse implements Header. TODO: it doesn't implement it
// correctly, instead it statically generates an ICMP Echo Reply
// packet.
func (h *ICMP4Header) ToResponse() {
// TODO: this doesn't implement ToResponse correctly, as it
// assumes the ICMP request type.
h.Type = ICMP4EchoReply
h.Code = ICMP4NoCode
h.IP4Header.ToResponse()
}

View File

@@ -1,44 +0,0 @@
// 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 packet
// icmp6HeaderLength is the size of the ICMPv6 packet header, not
// including the outer IP layer or the variable "response data"
// trailer.
const icmp6HeaderLength = 4
// ICMP6Type is an ICMPv6 type, as specified in
// https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml
type ICMP6Type uint8
const (
ICMP6Unreachable ICMP6Type = 1
ICMP6TimeExceeded ICMP6Type = 3
ICMP6EchoRequest ICMP6Type = 128
ICMP6EchoReply ICMP6Type = 129
)
func (t ICMP6Type) String() string {
switch t {
case ICMP6Unreachable:
return "Unreachable"
case ICMP6TimeExceeded:
return "TimeExceeded"
case ICMP6EchoRequest:
return "EchoRequest"
case ICMP6EchoReply:
return "EchoReply"
default:
return "Unknown"
}
}
// ICMP6Code is an ICMPv6 code, as specified in
// https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml
type ICMP6Code uint8
const (
ICMP6NoCode ICMP6Code = 0
)

View File

@@ -1,66 +0,0 @@
// 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 packet
// IPProto 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
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
// Values from the IANA registry.
ICMPv4 IPProto = 0x01
IGMP IPProto = 0x02
ICMPv6 IPProto = 0x3a
TCP IPProto = 0x06
UDP IPProto = 0x11
// TSMP is the Tailscale Message Protocol (our ICMP-ish
// thing), an IP protocol used only between Tailscale nodes
// (still encrypted by WireGuard) that communicates why things
// failed, etc.
//
// Proto number 99 is reserved for "any private encryption
// scheme". We never accept these from the host OS stack nor
// send them to the host network stack. It's only used between
// nodes.
TSMP IPProto = 99
// Fragment represents any non-first IP fragment, for which we
// don't have the sub-protocol header (and therefore can't
// figure out what the sub-protocol is).
//
// 0xFF is reserved in the IANA registry, so we steal it for
// internal use.
Fragment IPProto = 0xFF
)
func (p IPProto) String() string {
switch p {
case Fragment:
return "Frag"
case ICMPv4:
return "ICMPv4"
case IGMP:
return "IGMP"
case ICMPv6:
return "ICMPv6"
case UDP:
return "UDP"
case TCP:
return "TCP"
case TSMP:
return "TSMP"
default:
return "Unknown"
}
}

View File

@@ -1,116 +0,0 @@
// 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 packet
import (
"encoding/binary"
"errors"
"inet.af/netaddr"
)
// ip4HeaderLength is the length of an IPv4 header with no IP options.
const ip4HeaderLength = 20
// IP4Header represents an IPv4 packet header.
type IP4Header struct {
IPProto IPProto
IPID uint16
Src netaddr.IP
Dst netaddr.IP
}
// Len implements Header.
func (h IP4Header) Len() int {
return ip4HeaderLength
}
var errWrongFamily = errors.New("wrong address family for src/dst IP")
// Marshal implements Header.
func (h IP4Header) Marshal(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if len(buf) > maxPacketLength {
return errLargePacket
}
if !h.Src.Is4() || !h.Dst.Is4() {
return errWrongFamily
}
buf[0] = 0x40 | (byte(h.Len() >> 2)) // IPv4 + IHL
buf[1] = 0x00 // DSCP + ECN
binary.BigEndian.PutUint16(buf[2:4], uint16(len(buf))) // Total length
binary.BigEndian.PutUint16(buf[4:6], h.IPID) // ID
binary.BigEndian.PutUint16(buf[6:8], 0) // Flags + fragment offset
buf[8] = 64 // TTL
buf[9] = uint8(h.IPProto) // Inner protocol
// Blank checksum. This is necessary even though we overwrite
// it later, because the checksum computation runs over these
// bytes and expects them to be zero.
binary.BigEndian.PutUint16(buf[10:12], 0)
src := h.Src.As4()
dst := h.Dst.As4()
copy(buf[12:16], src[:])
copy(buf[16:20], dst[:])
binary.BigEndian.PutUint16(buf[10:12], ip4Checksum(buf[0:20])) // Checksum
return nil
}
// ToResponse implements Header.
func (h *IP4Header) ToResponse() {
h.Src, h.Dst = h.Dst, h.Src
// Flip the bits in the IPID. If incoming IPIDs are distinct, so are these.
h.IPID = ^h.IPID
}
// ip4Checksum computes an IPv4 checksum, as specified in
// https://tools.ietf.org/html/rfc1071
func ip4Checksum(b []byte) uint16 {
var ac uint32
i := 0
n := len(b)
for n >= 2 {
ac += uint32(binary.BigEndian.Uint16(b[i : i+2]))
n -= 2
i += 2
}
if n == 1 {
ac += uint32(b[i]) << 8
}
for (ac >> 16) > 0 {
ac = (ac >> 16) + (ac & 0xffff)
}
return uint16(^ac)
}
// ip4PseudoHeaderOffset is the number of bytes by which the IPv4 UDP
// pseudo-header is smaller than the real IPv4 header.
const ip4PseudoHeaderOffset = 8
// marshalPseudo serializes h into buf in the "pseudo-header" form
// required when calculating UDP checksums. The pseudo-header starts
// at buf[ip4PseudoHeaderOffset] so as to abut the following UDP
// header, while leaving enough space in buf for a full IPv4 header.
func (h IP4Header) marshalPseudo(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if len(buf) > maxPacketLength {
return errLargePacket
}
length := len(buf) - h.Len()
src, dst := h.Src.As4(), h.Dst.As4()
copy(buf[8:12], src[:])
copy(buf[12:16], dst[:])
buf[16] = 0x0
buf[17] = uint8(h.IPProto)
binary.BigEndian.PutUint16(buf[18:20], uint16(length))
return nil
}

View File

@@ -1,76 +0,0 @@
// 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 packet
import (
"encoding/binary"
"inet.af/netaddr"
)
// ip6HeaderLength is the length of an IPv6 header with no IP options.
const ip6HeaderLength = 40
// IP6Header represents an IPv6 packet header.
type IP6Header struct {
IPProto IPProto
IPID uint32 // only lower 20 bits used
Src netaddr.IP
Dst netaddr.IP
}
// Len implements Header.
func (h IP6Header) Len() int {
return ip6HeaderLength
}
// Marshal implements Header.
func (h IP6Header) Marshal(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if len(buf) > maxPacketLength {
return errLargePacket
}
binary.BigEndian.PutUint32(buf[:4], h.IPID&0x000FFFFF)
buf[0] = 0x60
binary.BigEndian.PutUint16(buf[4:6], uint16(len(buf)-ip6HeaderLength)) // Total length
buf[6] = uint8(h.IPProto) // Inner protocol
buf[7] = 64 // TTL
src, dst := h.Src.As16(), h.Dst.As16()
copy(buf[8:24], src[:])
copy(buf[24:40], dst[:])
return nil
}
// ToResponse implements Header.
func (h *IP6Header) ToResponse() {
h.Src, h.Dst = h.Dst, h.Src
// Flip the bits in the IPID. If incoming IPIDs are distinct, so are these.
h.IPID = (^h.IPID) & 0x000FFFFF
}
// marshalPseudo serializes h into buf in the "pseudo-header" form
// required when calculating UDP checksums.
func (h IP6Header) marshalPseudo(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if len(buf) > maxPacketLength {
return errLargePacket
}
src, dst := h.Src.As16(), h.Dst.As16()
copy(buf[:16], src[:])
copy(buf[16:32], dst[:])
binary.BigEndian.PutUint32(buf[32:36], uint32(len(buf)-h.Len()))
buf[36] = 0
buf[37] = 0
buf[38] = 0
buf[39] = 17 // NextProto
return nil
}

View File

@@ -1,435 +0,0 @@
// 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 packet
import (
"encoding/binary"
"fmt"
"net"
"strings"
"inet.af/netaddr"
"tailscale.com/types/strbuilder"
)
// RFC1858: prevent overlapping fragment attacks.
const minFrag = 60 + 20 // max IPv4 header + basic TCP header
type TCPFlag uint8
const (
TCPFin TCPFlag = 0x01
TCPSyn TCPFlag = 0x02
TCPRst TCPFlag = 0x04
TCPPsh TCPFlag = 0x08
TCPAck TCPFlag = 0x10
TCPSynAck TCPFlag = TCPSyn | TCPAck
)
// Parsed is a minimal decoding of a packet suitable for use in filters.
type Parsed struct {
// b is the byte buffer that this decodes.
b []byte
// subofs is the offset of IP subprotocol.
subofs int
// dataofs is the offset of IP subprotocol payload.
dataofs int
// length is the total length of the packet.
// This is not the same as len(b) because b can have trailing zeros.
length int
// IPVersion is the IP protocol version of the packet (4 or
// 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
// SrcIP4 is the source address. Family matches IPVersion. Port is
// valid iff IPProto == TCP || IPProto == UDP.
Src netaddr.IPPort
// DstIP4 is the destination address. Family matches IPVersion.
Dst netaddr.IPPort
// TCPFlags is the packet's TCP flag bigs. Valid iff IPProto == TCP.
TCPFlags TCPFlag
}
func (p *Parsed) String() string {
if p.IPVersion != 4 && p.IPVersion != 6 {
return "Unknown{???}"
}
sb := strbuilder.Get()
sb.WriteString(p.IPProto.String())
sb.WriteByte('{')
writeIPPort(sb, p.Src)
sb.WriteString(" > ")
writeIPPort(sb, p.Dst)
sb.WriteByte('}')
return sb.String()
}
// writeIPPort writes ipp.String() into sb, with fewer allocations.
//
// TODO: make netaddr more efficient in this area, and retire this func.
func writeIPPort(sb *strbuilder.Builder, ipp netaddr.IPPort) {
if ipp.IP.Is4() {
raw := ipp.IP.As4()
sb.WriteUint(uint64(raw[0]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[1]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[2]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[3]))
sb.WriteByte(':')
} else {
sb.WriteByte('[')
sb.WriteString(ipp.IP.String()) // TODO: faster?
sb.WriteString("]:")
}
sb.WriteUint(uint64(ipp.Port))
}
// Decode extracts data from the packet in b into q.
// It performs extremely simple packet decoding for basic IPv4 packet types.
// It extracts only the subprotocol id, IP addresses, and (if any) ports,
// and shouldn't need any memory allocation.
func (q *Parsed) Decode(b []byte) {
q.b = b
if len(b) < 1 {
q.IPVersion = 0
q.IPProto = Unknown
return
}
q.IPVersion = b[0] >> 4
switch q.IPVersion {
case 4:
q.decode4(b)
case 6:
q.decode6(b)
default:
q.IPVersion = 0
q.IPProto = Unknown
}
}
func (q *Parsed) decode4(b []byte) {
if len(b) < ip4HeaderLength {
q.IPVersion = 0
q.IPProto = Unknown
return
}
// Check that it's IPv4.
q.IPProto = IPProto(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
return
}
// If it's valid IPv4, then the IP addresses are valid
q.Src.IP = netaddr.IPv4(b[12], b[13], b[14], b[15])
q.Dst.IP = netaddr.IPv4(b[16], b[17], b[18], b[19])
q.subofs = int((b[0] & 0x0F) << 2)
if q.subofs > q.length {
// next-proto starts beyond end of packet.
q.IPProto = Unknown
return
}
sub := b[q.subofs:]
sub = sub[:len(sub):len(sub)] // help the compiler do bounds check elimination
// We don't care much about IP fragmentation, except insofar as it's
// used for firewall bypass attacks. The trick is make the first
// fragment of a TCP or UDP packet so short that it doesn't fit
// the TCP or UDP header, so we can't read the port, in hope that
// it'll sneak past. Then subsequent fragments fill it in, but we're
// missing the first part of the header, so we can't read that either.
//
// A "perfectly correct" implementation would have to reassemble
// fragments before deciding what to do. But the truth is there's
// zero reason to send such a short first fragment, so we can treat
// it as Unknown. We can also treat any subsequent fragment that starts
// at such a low offset as Unknown.
fragFlags := binary.BigEndian.Uint16(b[6:8])
moreFrags := (fragFlags & 0x20) != 0
fragOfs := fragFlags & 0x1FFF
if fragOfs == 0 {
// This is the first fragment
if moreFrags && len(sub) < minFrag {
// Suspiciously short first fragment, dump it.
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:
if len(sub) < icmp4HeaderLength {
q.IPProto = Unknown
return
}
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp4HeaderLength
return
case IGMP:
// Keep IPProto, but don't parse anything else
// out.
return
case TCP:
if len(sub) < tcpHeaderLength {
q.IPProto = Unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = TCPFlag(sub[13]) & 0x3F
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
case UDP:
if len(sub) < udpHeaderLength {
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:
// Inter-tailscale messages.
q.dataofs = q.subofs
return
default:
q.IPProto = Unknown
return
}
} else {
// This is a fragment other than the first one.
if fragOfs < minFrag {
// First frag was suspiciously short, so we can't
// trust the followup either.
q.IPProto = Unknown
return
}
// otherwise, we have to permit the fragment to slide through.
// Second and later fragments don't have sub-headers.
// Ideally, we would drop fragments that we can't identify,
// but that would require statefulness. Anyway, receivers'
// kernels know to drop fragments where the initial fragment
// doesn't arrive.
q.IPProto = Fragment
return
}
}
func (q *Parsed) decode6(b []byte) {
if len(b) < ip6HeaderLength {
q.IPVersion = 0
q.IPProto = Unknown
return
}
q.IPProto = IPProto(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
return
}
// okay to ignore `ok` here, because IPs pulled from packets are
// always well-formed stdlib IPs.
q.Src.IP, _ = netaddr.FromStdIP(net.IP(b[8:24]))
q.Dst.IP, _ = netaddr.FromStdIP(net.IP(b[24:40]))
// We don't support any IPv6 extension headers. Don't try to
// be clever. Therefore, the IP subprotocol always starts at
// byte 40.
//
// Note that this means we don't support fragmentation in
// IPv6. This is fine, because IPv6 strongly mandates that you
// should not fragment, which makes fragmentation on the open
// internet extremely uncommon.
//
// This also means we don't support IPSec headers (AH/ESP), or
// IPv6 jumbo frames. Those will get marked Unknown and
// dropped.
q.subofs = 40
sub := b[q.subofs:]
sub = sub[:len(sub):len(sub)] // help the compiler do bounds check elimination
switch q.IPProto {
case ICMPv6:
if len(sub) < icmp6HeaderLength {
q.IPProto = Unknown
return
}
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp6HeaderLength
case TCP:
if len(sub) < tcpHeaderLength {
q.IPProto = Unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = TCPFlag(sub[13]) & 0x3F
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
case UDP:
if len(sub) < udpHeaderLength {
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:
// Inter-tailscale messages.
q.dataofs = q.subofs
return
default:
q.IPProto = Unknown
return
}
}
func (q *Parsed) IP4Header() IP4Header {
if q.IPVersion != 4 {
panic("IP4Header called on non-IPv4 Parsed")
}
ipid := binary.BigEndian.Uint16(q.b[4:6])
return IP4Header{
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")
}
return ICMP4Header{
IP4Header: q.IP4Header(),
Type: ICMP4Type(q.b[q.subofs+0]),
Code: ICMP4Code(q.b[q.subofs+1]),
}
}
func (q *Parsed) UDP4Header() UDP4Header {
if q.IPVersion != 4 {
panic("IP4Header called on non-IPv4 Parsed")
}
return UDP4Header{
IP4Header: q.IP4Header(),
SrcPort: q.Src.Port,
DstPort: q.Dst.Port,
}
}
// Buffer returns the entire packet buffer.
// This is a read-only view; that is, q retains the ownership of the buffer.
func (q *Parsed) Buffer() []byte {
return q.b
}
// Payload returns the payload of the IP subprotocol section.
// This is a read-only view; that is, q retains the ownership of the buffer.
func (q *Parsed) Payload() []byte {
return q.b[q.dataofs:q.length]
}
// IsTCPSyn reports whether q is a TCP SYN packet
// (i.e. the first packet in a new connection).
func (q *Parsed) IsTCPSyn() bool {
return (q.TCPFlags & TCPSynAck) == TCPSyn
}
// IsError reports whether q is an ICMP "Error" packet.
func (q *Parsed) IsError() bool {
switch q.IPProto {
case ICMPv4:
if len(q.b) < q.subofs+8 {
return false
}
t := ICMP4Type(q.b[q.subofs])
return t == ICMP4Unreachable || t == ICMP4TimeExceeded
case ICMPv6:
if len(q.b) < q.subofs+8 {
return false
}
t := ICMP6Type(q.b[q.subofs])
return t == ICMP6Unreachable || t == ICMP6TimeExceeded
default:
return false
}
}
// IsEchoRequest reports whether q is an ICMP Echo Request.
func (q *Parsed) IsEchoRequest() bool {
switch q.IPProto {
case ICMPv4:
return len(q.b) >= q.subofs+8 && ICMP4Type(q.b[q.subofs]) == ICMP4EchoRequest && ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode
case ICMPv6:
return len(q.b) >= q.subofs+8 && ICMP6Type(q.b[q.subofs]) == ICMP6EchoRequest && ICMP6Code(q.b[q.subofs+1]) == ICMP6NoCode
default:
return false
}
}
// IsEchoRequest reports whether q is an IPv4 ICMP Echo Response.
func (q *Parsed) IsEchoResponse() bool {
switch q.IPProto {
case ICMPv4:
return len(q.b) >= q.subofs+8 && ICMP4Type(q.b[q.subofs]) == ICMP4EchoReply && ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode
case ICMPv6:
return len(q.b) >= q.subofs+8 && ICMP6Type(q.b[q.subofs]) == ICMP6EchoReply && ICMP6Code(q.b[q.subofs+1]) == ICMP6NoCode
default:
return false
}
}
func Hexdump(b []byte) string {
out := new(strings.Builder)
for i := 0; i < len(b); i += 16 {
if i > 0 {
fmt.Fprintf(out, "\n")
}
fmt.Fprintf(out, " %04x ", i)
j := 0
for ; j < 16 && i+j < len(b); j++ {
if j == 8 {
fmt.Fprintf(out, " ")
}
fmt.Fprintf(out, "%02x ", b[i+j])
}
for ; j < 16; j++ {
if j == 8 {
fmt.Fprintf(out, " ")
}
fmt.Fprintf(out, " ")
}
fmt.Fprintf(out, " ")
for j = 0; j < 16 && i+j < len(b); j++ {
if b[i+j] >= 32 && b[i+j] < 128 {
fmt.Fprintf(out, "%c", b[i+j])
} else {
fmt.Fprintf(out, ".")
}
}
}
return out.String()
}

View File

@@ -1,486 +0,0 @@
// 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 packet
import (
"bytes"
"reflect"
"testing"
"inet.af/netaddr"
)
func mustIPPort(s string) netaddr.IPPort {
ipp, err := netaddr.ParseIPPort(s)
if err != nil {
panic(err)
}
return ipp
}
var icmp4RequestBuffer = []byte{
// IP header up to checksum
0x45, 0x00, 0x00, 0x27, 0xde, 0xad, 0x00, 0x00, 0x40, 0x01, 0x8c, 0x15,
// source ip
0x01, 0x02, 0x03, 0x04,
// destination ip
0x05, 0x06, 0x07, 0x08,
// ICMP header
0x08, 0x00, 0x7d, 0x22,
// "request_payload"
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
}
var icmp4RequestDecode = Parsed{
b: icmp4RequestBuffer,
subofs: 20,
dataofs: 24,
length: len(icmp4RequestBuffer),
IPVersion: 4,
IPProto: ICMPv4,
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
var icmp4ReplyBuffer = []byte{
0x45, 0x00, 0x00, 0x25, 0x21, 0x52, 0x00, 0x00, 0x40, 0x01, 0x49, 0x73,
// source ip
0x05, 0x06, 0x07, 0x08,
// destination ip
0x01, 0x02, 0x03, 0x04,
// ICMP header
0x00, 0x00, 0xe6, 0x9e,
// "reply_payload"
0x72, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
}
var icmp4ReplyDecode = Parsed{
b: icmp4ReplyBuffer,
subofs: 20,
dataofs: 24,
length: len(icmp4ReplyBuffer),
IPVersion: 4,
IPProto: ICMPv4,
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
// ICMPv6 Router Solicitation
var icmp6PacketBuffer = []byte{
0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3a, 0xff,
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xfb, 0x57, 0x1d, 0xea, 0x9c, 0x39, 0x8f, 0xb7,
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x85, 0x00, 0x38, 0x04, 0x00, 0x00, 0x00, 0x00,
}
var icmp6PacketDecode = Parsed{
b: icmp6PacketBuffer,
subofs: 40,
dataofs: 44,
length: len(icmp6PacketBuffer),
IPVersion: 6,
IPProto: ICMPv6,
Src: mustIPPort("[fe80::fb57:1dea:9c39:8fb7]:0"),
Dst: mustIPPort("[ff02::2]:0"),
}
// This is a malformed IPv4 packet.
// Namely, the string "tcp_payload" follows the first byte of the IPv4 header.
var unknownPacketBuffer = []byte{
0x45, 0x74, 0x63, 0x70, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
}
var unknownPacketDecode = Parsed{
b: unknownPacketBuffer,
IPVersion: 0,
IPProto: Unknown,
}
var tcp4PacketBuffer = []byte{
// IP header up to checksum
0x45, 0x00, 0x00, 0x37, 0xde, 0xad, 0x00, 0x00, 0x40, 0x06, 0x49, 0x5f,
// source ip
0x01, 0x02, 0x03, 0x04,
// destination ip
0x05, 0x06, 0x07, 0x08,
// TCP header with SYN, ACK set
0x00, 0x7b, 0x02, 0x37, 0x00, 0x00, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00,
0x50, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
// "request_payload"
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
}
var tcp4PacketDecode = Parsed{
b: tcp4PacketBuffer,
subofs: 20,
dataofs: 40,
length: len(tcp4PacketBuffer),
IPVersion: 4,
IPProto: TCP,
Src: mustIPPort("1.2.3.4:123"),
Dst: mustIPPort("5.6.7.8:567"),
TCPFlags: TCPSynAck,
}
var tcp6RequestBuffer = []byte{
// IPv6 header up to hop limit
0x60, 0x06, 0xef, 0xcc, 0x00, 0x28, 0x06, 0x40,
// Src addr
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
// Dst addr
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
// TCP SYN segment, no payload
0xa4, 0x60, 0x00, 0x50, 0xf3, 0x82, 0xa1, 0x25, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, 0xfd, 0x20,
0xb1, 0xc6, 0x00, 0x00, 0x02, 0x04, 0x05, 0xa0, 0x04, 0x02, 0x08, 0x0a, 0xca, 0x76, 0xa6, 0x8e,
0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07,
}
var tcp6RequestDecode = Parsed{
b: tcp6RequestBuffer,
subofs: 40,
dataofs: len(tcp6RequestBuffer),
length: len(tcp6RequestBuffer),
IPVersion: 6,
IPProto: TCP,
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:42080"),
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:80"),
TCPFlags: TCPSyn,
}
var udp4RequestBuffer = []byte{
// IP header up to checksum
0x45, 0x00, 0x00, 0x2b, 0xde, 0xad, 0x00, 0x00, 0x40, 0x11, 0x8c, 0x01,
// source ip
0x01, 0x02, 0x03, 0x04,
// destination ip
0x05, 0x06, 0x07, 0x08,
// UDP header
0x00, 0x7b, 0x02, 0x37, 0x00, 0x17, 0x72, 0x1d,
// "request_payload"
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
}
var udp4RequestDecode = Parsed{
b: udp4RequestBuffer,
subofs: 20,
dataofs: 28,
length: len(udp4RequestBuffer),
IPVersion: 4,
IPProto: UDP,
Src: mustIPPort("1.2.3.4:123"),
Dst: mustIPPort("5.6.7.8:567"),
}
var invalid4RequestBuffer = []byte{
// IP header up to checksum. IHL field points beyond end of packet.
0x4a, 0x00, 0x00, 0x14, 0xde, 0xad, 0x00, 0x00, 0x40, 0x11, 0x8c, 0x01,
// source ip
0x01, 0x02, 0x03, 0x04,
// destination ip
0x05, 0x06, 0x07, 0x08,
}
// Regression check for the IHL field pointing beyond the end of the
// packet.
var invalid4RequestDecode = Parsed{
b: invalid4RequestBuffer,
subofs: 40,
length: len(invalid4RequestBuffer),
IPVersion: 4,
IPProto: Unknown,
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
var udp6RequestBuffer = []byte{
// IPv6 header up to hop limit
0x60, 0x0e, 0xc9, 0x67, 0x00, 0x29, 0x11, 0x40,
// Src addr
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
// Dst addr
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
// UDP header
0xd4, 0x04, 0x01, 0xbb, 0x00, 0x29, 0x96, 0x84,
// Payload
0x5c, 0x06, 0xae, 0x85, 0x02, 0xf5, 0xdb, 0x90, 0xe0, 0xe0, 0x93, 0xed, 0x9a, 0xd9, 0x92, 0x69, 0xbe, 0x36, 0x8a, 0x7d, 0xd7, 0xce, 0xd0, 0x8a, 0xf2, 0x51, 0x95, 0xff, 0xb6, 0x92, 0x70, 0x10, 0xd7,
}
var udp6RequestDecode = Parsed{
b: udp6RequestBuffer,
subofs: 40,
dataofs: 48,
length: len(udp6RequestBuffer),
IPVersion: 6,
IPProto: UDP,
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:54276"),
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:443"),
}
var udp4ReplyBuffer = []byte{
// IP header up to checksum
0x45, 0x00, 0x00, 0x29, 0x21, 0x52, 0x00, 0x00, 0x40, 0x11, 0x49, 0x5f,
// source ip
0x05, 0x06, 0x07, 0x08,
// destination ip
0x01, 0x02, 0x03, 0x04,
// UDP header
0x02, 0x37, 0x00, 0x7b, 0x00, 0x15, 0xd3, 0x9d,
// "reply_payload"
0x72, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
}
var udp4ReplyDecode = Parsed{
b: udp4ReplyBuffer,
subofs: 20,
dataofs: 28,
length: len(udp4ReplyBuffer),
IPProto: UDP,
Src: mustIPPort("1.2.3.4:567"),
Dst: mustIPPort("5.6.7.8:123"),
}
var igmpPacketBuffer = []byte{
// IP header up to checksum
0x46, 0xc0, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x01, 0x02, 0x41, 0x22,
// source IP
0xc0, 0xa8, 0x01, 0x52,
// destination IP
0xe0, 0x00, 0x00, 0xfb,
// IGMP Membership Report
0x94, 0x04, 0x00, 0x00, 0x16, 0x00, 0x09, 0x04, 0xe0, 0x00, 0x00, 0xfb,
//0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
var igmpPacketDecode = Parsed{
b: igmpPacketBuffer,
subofs: 24,
length: len(igmpPacketBuffer),
IPVersion: 4,
IPProto: IGMP,
Src: mustIPPort("192.168.1.82:0"),
Dst: mustIPPort("224.0.0.251:0"),
}
var ipv4TSMPBuffer = []byte{
// IPv4 header:
0x45, 0x00,
0x00, 0x1b, // 20 + 7 bytes total
0x00, 0x00, // ID
0x00, 0x00, // Fragment
0x40, // TTL
byte(TSMP),
0x5f, 0xc3, // header checksum (wrong here)
// source IP:
0x64, 0x5e, 0x0c, 0x0e,
// dest IP:
0x64, 0x4a, 0x46, 0x03,
byte(TSMPTypeRejectedConn),
byte(TCP),
byte(RejectedDueToACLs),
0x00, 123, // src port
0x00, 80, // dst port
}
var ipv4TSMPDecode = Parsed{
b: ipv4TSMPBuffer,
subofs: 20,
dataofs: 20,
length: 27,
IPVersion: 4,
IPProto: TSMP,
Src: mustIPPort("100.94.12.14:0"),
Dst: mustIPPort("100.74.70.3:0"),
}
func TestParsedString(t *testing.T) {
tests := []struct {
name string
qdecode Parsed
want string
}{
{"tcp4", tcp4PacketDecode, "TCP{1.2.3.4:123 > 5.6.7.8:567}"},
{"tcp6", tcp6RequestDecode, "TCP{[2001:559:bc13:5400:1749:4628:3934:e1b]:42080 > [2607:f8b0:400a:809::200e]:80}"},
{"udp4", udp4RequestDecode, "UDP{1.2.3.4:123 > 5.6.7.8:567}"},
{"udp6", udp6RequestDecode, "UDP{[2001:559:bc13:5400:1749:4628:3934:e1b]:54276 > [2607:f8b0:400a:809::200e]:443}"},
{"icmp4", icmp4RequestDecode, "ICMPv4{1.2.3.4:0 > 5.6.7.8:0}"},
{"icmp6", icmp6PacketDecode, "ICMPv6{[fe80::fb57:1dea:9c39:8fb7]:0 > [ff02::2]:0}"},
{"igmp", igmpPacketDecode, "IGMP{192.168.1.82:0 > 224.0.0.251:0}"},
{"unknown", unknownPacketDecode, "Unknown{???}"},
{"ipv4_tsmp", ipv4TSMPDecode, "TSMP{100.94.12.14:0 > 100.74.70.3:0}"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.qdecode.String()
if got != tt.want {
t.Errorf("got %q; want %q", got, tt.want)
}
})
}
var sink string
allocs := testing.AllocsPerRun(1000, func() {
sink = tests[0].qdecode.String()
})
_ = sink
if allocs != 1 {
t.Errorf("allocs = %v; want 1", allocs)
}
}
func TestDecode(t *testing.T) {
tests := []struct {
name string
buf []byte
want Parsed
}{
{"icmp4", icmp4RequestBuffer, icmp4RequestDecode},
{"icmp6", icmp6PacketBuffer, icmp6PacketDecode},
{"tcp4", tcp4PacketBuffer, tcp4PacketDecode},
{"tcp6", tcp6RequestBuffer, tcp6RequestDecode},
{"udp4", udp4RequestBuffer, udp4RequestDecode},
{"udp6", udp6RequestBuffer, udp6RequestDecode},
{"igmp", igmpPacketBuffer, igmpPacketDecode},
{"unknown", unknownPacketBuffer, unknownPacketDecode},
{"invalid4", invalid4RequestBuffer, invalid4RequestDecode},
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got Parsed
got.Decode(tt.buf)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("mismatch\n got: %s %#v\nwant: %s %#v", got.String(), got, tt.want.String(), tt.want)
}
})
}
allocs := testing.AllocsPerRun(1000, func() {
var got Parsed
got.Decode(tests[0].buf)
})
if allocs != 0 {
t.Errorf("allocs = %v; want 0", allocs)
}
}
func BenchmarkDecode(b *testing.B) {
benches := []struct {
name string
buf []byte
}{
{"tcp4", tcp4PacketBuffer},
{"tcp6", tcp6RequestBuffer},
{"udp4", udp4RequestBuffer},
{"udp6", udp6RequestBuffer},
{"icmp4", icmp4RequestBuffer},
{"icmp6", icmp6PacketBuffer},
{"igmp", igmpPacketBuffer},
{"unknown", unknownPacketBuffer},
}
for _, bench := range benches {
b.Run(bench.name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var p Parsed
p.Decode(bench.buf)
}
})
}
}
func TestMarshalRequest(t *testing.T) {
// Too small to hold our packets, but only barely.
var small [20]byte
var large [64]byte
icmpHeader := icmp4RequestDecode.ICMP4Header()
udpHeader := udp4RequestDecode.UDP4Header()
tests := []struct {
name string
header Header
want []byte
}{
{"icmp", &icmpHeader, icmp4RequestBuffer},
{"udp", &udpHeader, udp4RequestBuffer},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.header.Marshal(small[:])
if err != errSmallBuffer {
t.Errorf("got err: nil; want: %s", errSmallBuffer)
}
dataOffset := tt.header.Len()
dataLength := copy(large[dataOffset:], []byte("request_payload"))
end := dataOffset + dataLength
err = tt.header.Marshal(large[:end])
if err != nil {
t.Errorf("got err: %s; want nil", err)
}
if !bytes.Equal(large[:end], tt.want) {
t.Errorf("got %x; want %x", large[:end], tt.want)
}
})
}
}
func TestMarshalResponse(t *testing.T) {
var buf [64]byte
icmpHeader := icmp4RequestDecode.ICMP4Header()
udpHeader := udp4RequestDecode.UDP4Header()
type HeaderToResponser interface {
Header
// ToResponse transforms the header into one for a response packet.
// For instance, this swaps the source and destination IPs.
ToResponse()
}
tests := []struct {
name string
header HeaderToResponser
want []byte
}{
{"icmp", &icmpHeader, icmp4ReplyBuffer},
{"udp", &udpHeader, udp4ReplyBuffer},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.header.ToResponse()
dataOffset := tt.header.Len()
dataLength := copy(buf[dataOffset:], []byte("reply_payload"))
end := dataOffset + dataLength
err := tt.header.Marshal(buf[:end])
if err != nil {
t.Errorf("got err: %s; want nil", err)
}
if !bytes.Equal(buf[:end], tt.want) {
t.Errorf("got %x; want %x", buf[:end], tt.want)
}
})
}
}

View File

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

View File

@@ -1,63 +0,0 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"testing"
"inet.af/netaddr"
)
func TestTailscaleRejectedHeader(t *testing.T) {
tests := []struct {
h TailscaleRejectedHeader
wantStr string
}{
{
h: TailscaleRejectedHeader{
IPSrc: netaddr.MustParseIP("5.5.5.5"),
IPDst: netaddr.MustParseIP("1.2.3.4"),
Src: netaddr.MustParseIPPort("1.2.3.4:567"),
Dst: netaddr.MustParseIPPort("5.5.5.5:443"),
Proto: TCP,
Reason: RejectedDueToACLs,
},
wantStr: "TSMP-reject-flow{TCP 1.2.3.4:567 > 5.5.5.5:443}: acl",
},
{
h: TailscaleRejectedHeader{
IPSrc: netaddr.MustParseIP("2::2"),
IPDst: netaddr.MustParseIP("1::1"),
Src: netaddr.MustParseIPPort("[1::1]:567"),
Dst: netaddr.MustParseIPPort("[2::2]:443"),
Proto: UDP,
Reason: RejectedDueToShieldsUp,
},
wantStr: "TSMP-reject-flow{UDP [1::1]:567 > [2::2]:443}: shields",
},
}
for i, tt := range tests {
gotStr := tt.h.String()
if gotStr != tt.wantStr {
t.Errorf("%v. String = %q; want %q", i, gotStr, tt.wantStr)
continue
}
pkt := make([]byte, tt.h.Len())
tt.h.Marshal(pkt)
var p Parsed
p.Decode(pkt)
t.Logf("Parsed: %+v", p)
t.Logf("Parsed: %s", p.String())
back, ok := p.AsTailscaleRejectedHeader()
if !ok {
t.Errorf("%v. %q (%02x) didn't parse back", i, gotStr, pkt)
continue
}
if back != tt.h {
t.Errorf("%v. %q parsed back as %q", i, tt.h, back)
}
}
}

View File

@@ -1,55 +0,0 @@
// 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 packet
import "encoding/binary"
// udpHeaderLength is the size of the UDP packet header, not including
// the outer IP header.
const udpHeaderLength = 8
// UDP4Header is an IPv4+UDP header.
type UDP4Header struct {
IP4Header
SrcPort uint16
DstPort uint16
}
// Len implements Header.
func (h UDP4Header) Len() int {
return h.IP4Header.Len() + udpHeaderLength
}
// Marshal implements Header.
func (h UDP4Header) Marshal(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if len(buf) > maxPacketLength {
return errLargePacket
}
// The caller does not need to set this.
h.IPProto = UDP
length := len(buf) - h.IP4Header.Len()
binary.BigEndian.PutUint16(buf[20:22], h.SrcPort)
binary.BigEndian.PutUint16(buf[22:24], h.DstPort)
binary.BigEndian.PutUint16(buf[24:26], uint16(length))
binary.BigEndian.PutUint16(buf[26:28], 0) // blank checksum
// UDP checksum with IP pseudo header.
h.IP4Header.marshalPseudo(buf)
binary.BigEndian.PutUint16(buf[26:28], ip4Checksum(buf[ip4PseudoHeaderOffset:]))
h.IP4Header.Marshal(buf)
return nil
}
// ToResponse implements Header.
func (h *UDP4Header) ToResponse() {
h.SrcPort, h.DstPort = h.DstPort, h.SrcPort
h.IP4Header.ToResponse()
}

View File

@@ -1,51 +0,0 @@
// 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 packet
import "encoding/binary"
// UDP6Header is an IPv6+UDP header.
type UDP6Header struct {
IP6Header
SrcPort uint16
DstPort uint16
}
// Len implements Header.
func (h UDP6Header) Len() int {
return h.IP6Header.Len() + udpHeaderLength
}
// Marshal implements Header.
func (h UDP6Header) Marshal(buf []byte) error {
if len(buf) < h.Len() {
return errSmallBuffer
}
if len(buf) > maxPacketLength {
return errLargePacket
}
// The caller does not need to set this.
h.IPProto = UDP
length := len(buf) - h.IP6Header.Len()
binary.BigEndian.PutUint16(buf[40:42], h.SrcPort)
binary.BigEndian.PutUint16(buf[42:44], h.DstPort)
binary.BigEndian.PutUint16(buf[44:46], uint16(length))
binary.BigEndian.PutUint16(buf[46:48], 0) // blank checksum
// UDP checksum with IP pseudo header.
h.IP6Header.marshalPseudo(buf)
binary.BigEndian.PutUint16(buf[46:48], ip4Checksum(buf[:]))
h.IP6Header.Marshal(buf)
return nil
}
// ToResponse implements Header.
func (h *UDP6Header) ToResponse() {
h.SrcPort, h.DstPort = h.DstPort, h.SrcPort
h.IP6Header.ToResponse()
}

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ package portlist
import (
"context"
"errors"
"runtime"
"time"
"tailscale.com/version"
@@ -33,7 +34,7 @@ type Poller struct {
// NewPoller returns a new portlist Poller. It returns an error
// if the portlist couldn't be obtained.
func NewPoller() (*Poller, error) {
if version.OS() == "iOS" {
if runtime.GOOS == "darwin" && version.IsMobile() {
return nil, errors.New("not available on iOS")
}
p := &Poller{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

47
tempfork/osexec/README.md Normal file
View File

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

View File

@@ -0,0 +1,23 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package exec
import (
"testing"
)
func BenchmarkExecHostname(b *testing.B) {
b.ReportAllocs()
path, err := LookPath("hostname")
if err != nil {
b.Fatalf("could not find hostname: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := Command(path).Run(); err != nil {
b.Fatalf("hostname: %v", err)
}
}
}

View File

@@ -0,0 +1,39 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package exec
import (
"reflect"
"testing"
)
func TestDedupEnv(t *testing.T) {
tests := []struct {
noCase bool
in []string
want []string
}{
{
noCase: true,
in: []string{"k1=v1", "k2=v2", "K1=v3"},
want: []string{"K1=v3", "k2=v2"},
},
{
noCase: false,
in: []string{"k1=v1", "K1=V2", "k1=v3"},
want: []string{"k1=v3", "K1=V2"},
},
{
in: []string{"=a", "=b", "foo", "bar"},
want: []string{"=b", "foo", "bar"},
},
}
for _, tt := range tests {
got := dedupEnvCase(tt.noCase, tt.in)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Dedup(%v, %q) = %q; want %q", tt.noCase, tt.in, got, tt.want)
}
}
}

View File

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

797
tempfork/osexec/exec.go Normal file
View File

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

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package exec
import (
"io"
"testing"
)
func TestPrefixSuffixSaver(t *testing.T) {
tests := []struct {
N int
writes []string
want string
}{
{
N: 2,
writes: nil,
want: "",
},
{
N: 2,
writes: []string{"a"},
want: "a",
},
{
N: 2,
writes: []string{"abc", "d"},
want: "abcd",
},
{
N: 2,
writes: []string{"abc", "d", "e"},
want: "ab\n... omitting 1 bytes ...\nde",
},
{
N: 2,
writes: []string{"ab______________________yz"},
want: "ab\n... omitting 22 bytes ...\nyz",
},
{
N: 2,
writes: []string{"ab_______________________y", "z"},
want: "ab\n... omitting 23 bytes ...\nyz",
},
}
for i, tt := range tests {
w := &prefixSuffixSaver{N: tt.N}
for _, s := range tt.writes {
n, err := io.WriteString(w, s)
if err != nil || n != len(s) {
t.Errorf("%d. WriteString(%q) = %v, %v; want %v, %v", i, s, n, err, len(s), nil)
}
}
if got := string(w.Bytes()); got != tt.want {
t.Errorf("%d. Bytes = %q; want %q", i, got, tt.want)
}
}
}

23
tempfork/osexec/lp_js.go Normal file
View File

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

View File

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

View File

@@ -0,0 +1,33 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package exec
import (
"testing"
)
var nonExistentPaths = []string{
"some-non-existent-path",
"non-existent-path/slashed",
}
func TestLookPathNotFound(t *testing.T) {
for _, name := range nonExistentPaths {
path, err := LookPath(name)
if err == nil {
t.Fatalf("LookPath found %q in $PATH", name)
}
if path != "" {
t.Fatalf("LookPath path == %q when err != nil", path)
}
perr, ok := err.(*Error)
if !ok {
t.Fatal("LookPath error is not an exec.Error")
}
if perr.Name != name {
t.Fatalf("want Error name %q, got %q", name, perr.Name)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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