Compare commits
125 Commits
raggi/gofu
...
marwan/noc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17c3f5c7d5 | ||
|
|
e32e5c0d0c | ||
|
|
3d180a16c3 | ||
|
|
4e86857313 | ||
|
|
745ee97973 | ||
|
|
a4fd4fd845 | ||
|
|
e3cb982139 | ||
|
|
5ae786988c | ||
|
|
0ca8bf1e26 | ||
|
|
03e848e3b5 | ||
|
|
7c88eeba86 | ||
|
|
f0ee03dfaf | ||
|
|
4664318be2 | ||
|
|
678bb92bb8 | ||
|
|
9b6e48658f | ||
|
|
85215ed58a | ||
|
|
b69059334b | ||
|
|
84c99fe0d9 | ||
|
|
da90fab899 | ||
|
|
ca49b29582 | ||
|
|
cb2fd5be92 | ||
|
|
d27a6e1c53 | ||
|
|
4f454f4122 | ||
|
|
ea84fc9ad2 | ||
|
|
1ce08256c0 | ||
|
|
827abbeeaa | ||
|
|
d1ecb1f43b | ||
|
|
a743b66f9d | ||
|
|
58ab66ec51 | ||
|
|
e8b06b2232 | ||
|
|
df8b1b2179 | ||
|
|
4d730e154c | ||
|
|
b9fb8ac702 | ||
|
|
5c38f0979e | ||
|
|
024d48d9c1 | ||
|
|
29ded8f9f9 | ||
|
|
68307c1411 | ||
|
|
2804327074 | ||
|
|
8d3d48e000 | ||
|
|
8864112a0c | ||
|
|
9ed3a061c3 | ||
|
|
6e967446e4 | ||
|
|
0d7303b798 | ||
|
|
d1ce7a9b5e | ||
|
|
5def4f4a1c | ||
|
|
1c6ff310ae | ||
|
|
48605226dd | ||
|
|
f46c1aede0 | ||
|
|
73d128238e | ||
|
|
787fc41fa4 | ||
|
|
5783adcc6f | ||
|
|
503b6dd8be | ||
|
|
9e9ea6e974 | ||
|
|
459744c9ea | ||
|
|
7675d323fa | ||
|
|
270942094f | ||
|
|
be190e990f | ||
|
|
4d7927047c | ||
|
|
ddb4040aa0 | ||
|
|
c1e6888fc7 | ||
|
|
3ae7140690 | ||
|
|
bcf7b63d7e | ||
|
|
c5bf868940 | ||
|
|
42fd964090 | ||
|
|
979d29b5f5 | ||
|
|
1f4a34588b | ||
|
|
a82f275619 | ||
|
|
b3c3a9f174 | ||
|
|
042f82ea32 | ||
|
|
633d08bd7b | ||
|
|
d35ce1add9 | ||
|
|
c3ab36cb9d | ||
|
|
8032b966a1 | ||
|
|
d78b334964 | ||
|
|
161d1d281a | ||
|
|
1145b9751d | ||
|
|
1e876a3c1d | ||
|
|
a8f10c23b2 | ||
|
|
b2b5379348 | ||
|
|
13de36303d | ||
|
|
095d3edd33 | ||
|
|
43819309e1 | ||
|
|
1b8a0dfe5e | ||
|
|
018a382729 | ||
|
|
2e07245384 | ||
|
|
aa87e999dc | ||
|
|
f58751eb2b | ||
|
|
ce11c82d51 | ||
|
|
90ba26cea1 | ||
|
|
7778d708a6 | ||
|
|
f66ddb544c | ||
|
|
e3b2250e26 | ||
|
|
6f521c138d | ||
|
|
04a3118d45 | ||
|
|
c791e64881 | ||
|
|
7330aa593e | ||
|
|
7f17e04a5a | ||
|
|
4722f7e322 | ||
|
|
3ede3aafe4 | ||
|
|
f844791e15 | ||
|
|
cd35a79136 | ||
|
|
f85dc6f97c | ||
|
|
5acc7c4b1e | ||
|
|
c328770184 | ||
|
|
588a234fdc | ||
|
|
c3ef6fb4ee | ||
|
|
85de580455 | ||
|
|
d0906cda97 | ||
|
|
7c386ca6d2 | ||
|
|
7f057d7489 | ||
|
|
c7cea825ae | ||
|
|
280255acae | ||
|
|
ff1b35ec6c | ||
|
|
9a655a1d58 | ||
|
|
28cb1221ba | ||
|
|
d5a870b4dc | ||
|
|
162488a775 | ||
|
|
c5150eae67 | ||
|
|
80b138f0df | ||
|
|
4b49ca4a12 | ||
|
|
10f1c90f4d | ||
|
|
29f7df9d8f | ||
|
|
83c41f3697 | ||
|
|
20f17d6e7b | ||
|
|
bd0c32ca21 |
6
.github/workflows/go-licenses.yml
vendored
6
.github/workflows/go-licenses.yml
vendored
@@ -50,11 +50,11 @@ jobs:
|
||||
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Send pull request
|
||||
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 #v4.2.4
|
||||
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
author: License Updater <noreply@tailscale.com>
|
||||
committer: License Updater <noreply@tailscale.com>
|
||||
author: License Updater <noreply+license-updater@tailscale.com>
|
||||
committer: License Updater <noreply+license-updater@tailscale.com>
|
||||
branch: licenses/cli
|
||||
commit-message: "licenses: update tailscale{,d} licenses"
|
||||
title: "licenses: update tailscale{,d} licenses"
|
||||
|
||||
40
.github/workflows/golangci-lint.yml
vendored
Normal file
40
.github/workflows/golangci-lint.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
# For now, only lint pull requests, not the main branches.
|
||||
pull_request:
|
||||
|
||||
# TODO(andrew): enable for main branch after an initial waiting period.
|
||||
#push:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: golangci-lint
|
||||
# Note: this is the 'v3' tag as of 2023-04-17
|
||||
uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5
|
||||
with:
|
||||
version: v1.52.2
|
||||
|
||||
# Show only new issues if it's a pull request.
|
||||
only-new-issues: true
|
||||
102
.github/workflows/installer.yml
vendored
Normal file
102
.github/workflows/installer.yml
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
name: test installer.sh
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- scripts/installer.sh
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
paths:
|
||||
- scripts/installer.sh
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
# Don't abort the entire matrix if one element fails.
|
||||
fail-fast: false
|
||||
# Don't start all of these at once, which could saturate Github workers.
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
image:
|
||||
# This is a list of Docker images against which we test our installer.
|
||||
# If you find that some of these no longer exist, please feel free
|
||||
# to remove them from the list.
|
||||
# When adding new images, please only use official ones.
|
||||
- "debian:oldstable-slim"
|
||||
- "debian:stable-slim"
|
||||
- "debian:testing-slim"
|
||||
- "debian:sid-slim"
|
||||
- "ubuntu:18.04"
|
||||
- "ubuntu:20.04"
|
||||
- "ubuntu:22.04"
|
||||
- "ubuntu:22.10"
|
||||
- "ubuntu:23.04"
|
||||
- "elementary/docker:stable"
|
||||
- "elementary/docker:unstable"
|
||||
- "parrotsec/core:lts-amd64"
|
||||
- "parrotsec/core:latest"
|
||||
- "kalilinux/kali-rolling"
|
||||
- "kalilinux/kali-dev"
|
||||
- "oraclelinux:9"
|
||||
- "oraclelinux:8"
|
||||
- "fedora:latest"
|
||||
- "rockylinux:8.7"
|
||||
- "rockylinux:9"
|
||||
- "amazonlinux:latest"
|
||||
- "opensuse/leap:latest"
|
||||
- "opensuse/tumbleweed:latest"
|
||||
- "archlinux:latest"
|
||||
- "alpine:3.14"
|
||||
- "alpine:latest"
|
||||
- "alpine:edge"
|
||||
deps:
|
||||
# Run all images installing curl as a dependency.
|
||||
- curl
|
||||
include:
|
||||
# Check a few images with wget rather than curl.
|
||||
- { image: "debian:oldstable-slim", deps: "wget" }
|
||||
- { image: "debian:sid-slim", deps: "wget" }
|
||||
- { image: "ubuntu:23.04", deps: "wget" }
|
||||
# Ubuntu 16.04 also needs apt-transport-https installed.
|
||||
- { image: "ubuntu:16.04", deps: "curl apt-transport-https" }
|
||||
- { image: "ubuntu:16.04", deps: "wget apt-transport-https" }
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
options: --user root
|
||||
steps:
|
||||
- name: install dependencies (yum)
|
||||
# tar and gzip are needed by the actions/checkout below.
|
||||
run: yum install -y --allowerasing tar gzip ${{ matrix.deps }}
|
||||
if: |
|
||||
contains(matrix.image, 'centos')
|
||||
|| contains(matrix.image, 'oraclelinux')
|
||||
|| contains(matrix.image, 'fedora')
|
||||
|| contains(matrix.image, 'amazonlinux')
|
||||
- name: install dependencies (zypper)
|
||||
# tar and gzip are needed by the actions/checkout below.
|
||||
run: zypper --non-interactive install tar gzip
|
||||
if: contains(matrix.image, 'opensuse')
|
||||
- name: install dependencies (apt-get)
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y ${{ matrix.deps }}
|
||||
if: |
|
||||
contains(matrix.image, 'debian')
|
||||
|| contains(matrix.image, 'ubuntu')
|
||||
|| contains(matrix.image, 'elementary')
|
||||
|| contains(matrix.image, 'parrotsec')
|
||||
|| contains(matrix.image, 'kalilinux')
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: run installer
|
||||
run: scripts/installer.sh
|
||||
# Package installation can fail in docker because systemd is not running
|
||||
# as PID 1, so ignore errors at this step. The real check is the
|
||||
# `tailscale --version` command below.
|
||||
continue-on-error: true
|
||||
- name: check tailscale version
|
||||
run: tailscale --version
|
||||
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
@@ -65,8 +65,9 @@ jobs:
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-
|
||||
- name: build all
|
||||
run: ./tool/go build ${{matrix.buildflags}} ./...
|
||||
@@ -89,7 +90,11 @@ jobs:
|
||||
- name: build test wrapper
|
||||
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
|
||||
- name: test all
|
||||
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper -bench=. -benchtime=1x ./...
|
||||
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper
|
||||
env:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
- name: bench all
|
||||
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper -test.bench=. -test.benchtime=1x -test.run=^$
|
||||
env:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
- name: check that no tracked files changed
|
||||
@@ -113,7 +118,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
@@ -131,8 +136,9 @@ jobs:
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
${{ github.job }}-${{ runner.os }}-go-2-
|
||||
- name: test
|
||||
# Don't use -bench=. -benchtime=1x.
|
||||
@@ -206,8 +212,9 @@ jobs:
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-
|
||||
- name: build all
|
||||
run: ./tool/go build ./cmd/...
|
||||
@@ -271,8 +278,9 @@ jobs:
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
${{ github.job }}-${{ runner.os }}-go-2-
|
||||
- name: build tsconnect client
|
||||
run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli
|
||||
@@ -450,7 +458,7 @@ jobs:
|
||||
# By having the job always run, but skipping its only step as needed, we
|
||||
# let the CI output collapse nicely in PRs.
|
||||
if: failure() && github.event_name == 'push'
|
||||
uses: ruby/action-slack@v3.0.0
|
||||
uses: ruby/action-slack@v3.2.1
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
|
||||
6
.github/workflows/update-flake.yml
vendored
6
.github/workflows/update-flake.yml
vendored
@@ -35,11 +35,11 @@ jobs:
|
||||
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Send pull request
|
||||
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 #v4.2.4
|
||||
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
author: Flakes Updater <noreply@tailscale.com>
|
||||
committer: Flakes Updater <noreply@tailscale.com>
|
||||
author: Flakes Updater <noreply+flakes-updater@tailscale.com>
|
||||
committer: Flakes Updater <noreply+flakes-updater@tailscale.com>
|
||||
branch: flakes
|
||||
commit-message: "go.mod.sri: update SRI hash for go.mod changes"
|
||||
title: "go.mod.sri: update SRI hash for go.mod changes"
|
||||
|
||||
61
.golangci.yml
Normal file
61
.golangci.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
linters:
|
||||
# Don't enable any linters by default; just the ones that we explicitly
|
||||
# enable in the list below.
|
||||
disable-all: true
|
||||
enable:
|
||||
- bidichk
|
||||
- gofmt
|
||||
- goimports
|
||||
- misspell
|
||||
- revive
|
||||
|
||||
# Configuration for how we run golangci-lint
|
||||
run:
|
||||
timeout: 5m
|
||||
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
# These are forks of an upstream package and thus are exempt from stylistic
|
||||
# changes that would make pulling in upstream changes harder.
|
||||
- path: tempfork/.*\.go
|
||||
text: "File is not `gofmt`-ed with `-s` `-r 'interface{} -> any'`"
|
||||
- path: util/singleflight/.*\.go
|
||||
text: "File is not `gofmt`-ed with `-s` `-r 'interface{} -> any'`"
|
||||
|
||||
# Per-linter settings are contained in this top-level key
|
||||
linters-settings:
|
||||
# Enable all rules by default; we don't use invisible unicode runes.
|
||||
bidichk:
|
||||
|
||||
gofmt:
|
||||
rewrite-rules:
|
||||
- pattern: 'interface{}'
|
||||
replacement: 'any'
|
||||
|
||||
goimports:
|
||||
|
||||
misspell:
|
||||
|
||||
revive:
|
||||
enable-all-rules: false
|
||||
ignore-generated-header: true
|
||||
rules:
|
||||
- name: atomic
|
||||
- name: context-keys-type
|
||||
- name: defer
|
||||
arguments: [[
|
||||
# Calling 'recover' at the time a defer is registered (i.e. "defer recover()") has no effect.
|
||||
"immediate-recover",
|
||||
# Calling 'recover' outside of a deferred function has no effect
|
||||
"recover",
|
||||
# Returning values from a deferred function has no effect
|
||||
"return",
|
||||
]]
|
||||
- name: duplicated-imports
|
||||
- name: errorf
|
||||
- name: string-of-int
|
||||
- name: time-equal
|
||||
- name: unconditional-recursion
|
||||
- name: useless-break
|
||||
- name: waitgroup-by-value
|
||||
@@ -1 +1 @@
|
||||
1.39.0
|
||||
1.43.0
|
||||
|
||||
5
api.md
5
api.md
@@ -503,7 +503,8 @@ Returns the enabled and advertised subnet routes for a device.
|
||||
POST /api/v2/device/{deviceID}/authorized
|
||||
```
|
||||
|
||||
Authorize a device. This call marks a device as authorized for tailnets where device authorization is required.
|
||||
Authorize a device.
|
||||
This call marks a device as authorized or revokes its authorization for tailnets where device authorization is required, according to the `authorized` field in the payload.
|
||||
|
||||
This returns a successful 2xx response with an empty JSON object in the response body.
|
||||
|
||||
@@ -515,7 +516,7 @@ The ID of the device.
|
||||
|
||||
#### `authorized` (required in `POST` body)
|
||||
|
||||
Specify whether the device is authorized. Only 'true' is currently supported.
|
||||
Specify whether the device is authorized.
|
||||
|
||||
``` jsonc
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestDoesNotOverwriteIrregularFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
// The least troublesome thing to make that is not a file is a unix socket.
|
||||
// Making a null device sadly requries root.
|
||||
// Making a null device sadly requires root.
|
||||
l, err := net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -16,7 +16,7 @@ if [ -n "${TS_USE_TOOLCHAIN:-}" ]; then
|
||||
go="./tool/go"
|
||||
fi
|
||||
|
||||
eval `GOOS=$($go env GOHOSTOS) GOARCH=$($go env GOHOSTARCH) $go run ./cmd/mkversion`
|
||||
eval `CGO_ENABLED=0 GOOS=$($go env GOHOSTOS) GOARCH=$($go env GOHOSTARCH) $go run ./cmd/mkversion`
|
||||
|
||||
if [ "$1" = "shellvars" ]; then
|
||||
cat <<EOF
|
||||
|
||||
@@ -436,7 +436,7 @@ func (c *Client) ValidateACLJSON(ctx context.Context, source, dest string) (test
|
||||
}
|
||||
}()
|
||||
|
||||
tests := []ACLTest{ACLTest{User: source, Allow: []string{dest}}}
|
||||
tests := []ACLTest{{User: source, Allow: []string{dest}}}
|
||||
postData, err := json.Marshal(tests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -63,7 +63,7 @@ func (c *Client) dnsGETRequest(ctx context.Context, endpoint string) ([]byte, er
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (c *Client) dnsPOSTRequest(ctx context.Context, endpoint string, postData interface{}) ([]byte, error) {
|
||||
func (c *Client) dnsPOSTRequest(ctx context.Context, endpoint string, postData any) ([]byte, error) {
|
||||
path := fmt.Sprintf("%s/api/v2/tailnet/%s/dns/%s", c.baseURL(), c.tailnet, endpoint)
|
||||
data, err := json.Marshal(&postData)
|
||||
if err != nil {
|
||||
|
||||
@@ -68,12 +68,32 @@ func (c *Client) Keys(ctx context.Context) ([]string, error) {
|
||||
}
|
||||
|
||||
// CreateKey creates a new key for the current user. Currently, only auth keys
|
||||
// can be created. Returns the key itself, which cannot be retrieved again
|
||||
// can be created. It returns the secret key itself, which cannot be retrieved again
|
||||
// later, and the key metadata.
|
||||
func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (string, *Key, error) {
|
||||
//
|
||||
// To create a key with a specific expiry, use CreateKeyWithExpiry.
|
||||
func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (keySecret string, keyMeta *Key, _ error) {
|
||||
return c.CreateKeyWithExpiry(ctx, caps, 0)
|
||||
}
|
||||
|
||||
// CreateKeyWithExpiry is like CreateKey, but allows specifying a expiration time.
|
||||
//
|
||||
// The time is truncated to a whole number of seconds. If zero, that means no expiration.
|
||||
func (c *Client) CreateKeyWithExpiry(ctx context.Context, caps KeyCapabilities, expiry time.Duration) (keySecret string, keyMeta *Key, _ error) {
|
||||
|
||||
// convert expirySeconds to an int64 (seconds)
|
||||
expirySeconds := int64(expiry.Seconds())
|
||||
if expirySeconds < 0 {
|
||||
return "", nil, fmt.Errorf("expiry must be positive")
|
||||
}
|
||||
if expirySeconds == 0 && expiry != 0 {
|
||||
return "", nil, fmt.Errorf("non-zero expiry must be at least one second")
|
||||
}
|
||||
|
||||
keyRequest := struct {
|
||||
Capabilities KeyCapabilities `json:"capabilities"`
|
||||
}{caps}
|
||||
Capabilities KeyCapabilities `json:"capabilities"`
|
||||
ExpirySeconds int64 `json:"expirySeconds,omitempty"`
|
||||
}{caps, int64(expirySeconds)}
|
||||
bs, err := json.Marshal(keyRequest)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
|
||||
@@ -81,7 +81,7 @@ func (m *manualCertManager) TLSConfig() *tls.Config {
|
||||
return &tls.Config{
|
||||
Certificates: nil,
|
||||
NextProtos: []string{
|
||||
"h2", "http/1.1", // enable HTTP/2
|
||||
"http/1.1",
|
||||
},
|
||||
GetCertificate: m.getCertificate,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
|
||||
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
|
||||
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
|
||||
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
|
||||
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
@@ -13,10 +15,9 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil+
|
||||
github.com/golang/protobuf/ptypes/timestamp from github.com/prometheus/client_model/go
|
||||
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
github.com/matttproud/golang_protobuf_extensions/pbutil from github.com/prometheus/common/expfmt
|
||||
@@ -64,7 +65,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
google.golang.org/protobuf/runtime/protoiface from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/runtime/protoimpl from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/types/descriptorpb from google.golang.org/protobuf/reflect/protodesc
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/golang/protobuf/ptypes/timestamp+
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
||||
nhooyr.io/websocket from tailscale.com/cmd/derper+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
@@ -86,6 +87,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
💣 tailscale.com/net/interfaces from tailscale.com/net/netns+
|
||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||
tailscale.com/net/netknob from tailscale.com/net/netns
|
||||
tailscale.com/net/netmon from tailscale.com/net/sockstats+
|
||||
tailscale.com/net/netns from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/netutil from tailscale.com/client/tailscale
|
||||
tailscale.com/net/packet from tailscale.com/wgengine/filter
|
||||
@@ -129,7 +131,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||
tailscale.com/util/mak from tailscale.com/syncs+
|
||||
tailscale.com/util/multierr from tailscale.com/health
|
||||
tailscale.com/util/set from tailscale.com/health
|
||||
tailscale.com/util/set from tailscale.com/health+
|
||||
tailscale.com/util/singleflight from tailscale.com/net/dnscache
|
||||
tailscale.com/util/slicesx from tailscale.com/cmd/derper+
|
||||
tailscale.com/util/vizerror from tailscale.com/tsweb
|
||||
|
||||
@@ -29,9 +29,9 @@ func main() {
|
||||
tags := flag.String("tags", "", "comma-separated list of tags to apply to the authkey")
|
||||
flag.Parse()
|
||||
|
||||
clientId := os.Getenv("TS_API_CLIENT_ID")
|
||||
clientID := os.Getenv("TS_API_CLIENT_ID")
|
||||
clientSecret := os.Getenv("TS_API_CLIENT_SECRET")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
if clientID == "" || clientSecret == "" {
|
||||
log.Fatal("TS_API_CLIENT_ID and TS_API_CLIENT_SECRET must be set")
|
||||
}
|
||||
|
||||
@@ -39,22 +39,22 @@ func main() {
|
||||
log.Fatal("at least one tag must be specified")
|
||||
}
|
||||
|
||||
baseUrl := os.Getenv("TS_BASE_URL")
|
||||
if baseUrl == "" {
|
||||
baseUrl = "https://api.tailscale.com"
|
||||
baseURL := os.Getenv("TS_BASE_URL")
|
||||
if baseURL == "" {
|
||||
baseURL = "https://api.tailscale.com"
|
||||
}
|
||||
|
||||
credentials := clientcredentials.Config{
|
||||
ClientID: clientId,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
TokenURL: baseUrl + "/api/v2/oauth/token",
|
||||
TokenURL: baseURL + "/api/v2/oauth/token",
|
||||
Scopes: []string{"device"},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
tsClient := tailscale.NewClient("-", nil)
|
||||
tsClient.HTTPClient = credentials.Client(ctx)
|
||||
tsClient.BaseURL = baseUrl
|
||||
tsClient.BaseURL = baseURL
|
||||
|
||||
caps := tailscale.KeyCapabilities{
|
||||
Devices: tailscale.KeyDeviceCapabilities{
|
||||
|
||||
@@ -48,6 +48,7 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -64,6 +65,7 @@ func main() {
|
||||
clientIDPath = defaultEnv("CLIENT_ID_FILE", "")
|
||||
clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "")
|
||||
image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest")
|
||||
priorityClassName = defaultEnv("PROXY_PRIORITY_CLASS_NAME", "")
|
||||
tags = defaultEnv("PROXY_TAGS", "tag:k8s")
|
||||
shouldRunAuthProxy = defaultBool("AUTH_PROXY", false)
|
||||
)
|
||||
@@ -200,12 +202,13 @@ waitOnline:
|
||||
}
|
||||
|
||||
sr := &ServiceReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
tsClient: tsClient,
|
||||
defaultTags: strings.Split(tags, ","),
|
||||
operatorNamespace: tsNamespace,
|
||||
proxyImage: image,
|
||||
logger: zlog.Named("service-reconciler"),
|
||||
Client: mgr.GetClient(),
|
||||
tsClient: tsClient,
|
||||
defaultTags: strings.Split(tags, ","),
|
||||
operatorNamespace: tsNamespace,
|
||||
proxyImage: image,
|
||||
proxyPriorityClassName: priorityClassName,
|
||||
logger: zlog.Named("service-reconciler"),
|
||||
}
|
||||
|
||||
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request {
|
||||
@@ -235,7 +238,7 @@ waitOnline:
|
||||
startlog.Fatalf("could not create controller: %v", err)
|
||||
}
|
||||
|
||||
startlog.Infof("Startup complete, operator running")
|
||||
startlog.Infof("Startup complete, operator running, version: %s", version.Long())
|
||||
if shouldRunAuthProxy {
|
||||
cfg, err := restConfig.TransportConfig()
|
||||
if err != nil {
|
||||
@@ -278,11 +281,12 @@ const (
|
||||
// ServiceReconciler is a simple ControllerManagedBy example implementation.
|
||||
type ServiceReconciler struct {
|
||||
client.Client
|
||||
tsClient tsClient
|
||||
defaultTags []string
|
||||
operatorNamespace string
|
||||
proxyImage string
|
||||
logger *zap.SugaredLogger
|
||||
tsClient tsClient
|
||||
defaultTags []string
|
||||
operatorNamespace string
|
||||
proxyImage string
|
||||
proxyPriorityClassName string
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
type tsClient interface {
|
||||
@@ -566,6 +570,9 @@ func (a *ServiceReconciler) getDeviceInfo(ctx context.Context, svc *corev1.Servi
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if sec == nil {
|
||||
return "", "", nil
|
||||
}
|
||||
id = string(sec.Data["device_id"])
|
||||
if id == "" {
|
||||
return "", "", nil
|
||||
@@ -589,6 +596,7 @@ func (a *ServiceReconciler) newAuthKey(ctx context.Context, tags []string) (stri
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
key, _, err := a.tsClient.CreateKey(ctx, caps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -633,6 +641,7 @@ func (a *ServiceReconciler) reconcileSTS(ctx context.Context, logger *zap.Sugare
|
||||
ss.Spec.Template.ObjectMeta.Labels = map[string]string{
|
||||
"app": string(parentSvc.UID),
|
||||
}
|
||||
ss.Spec.Template.Spec.PriorityClassName = a.proxyPriorityClassName
|
||||
logger.Debugf("reconciling statefulset %s/%s", ss.GetNamespace(), ss.GetName())
|
||||
return createOrUpdate(ctx, a.Client, a.operatorNamespace, &ss, func(s *appsv1.StatefulSet) { s.Spec = ss.Spec })
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"go.uber.org/zap"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -65,7 +64,7 @@ func TestLoadBalancerClass(t *testing.T) {
|
||||
|
||||
expectEqual(t, fc, expectedSecret(fullName))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
|
||||
|
||||
// Normally the Tailscale proxy pod would come up here and write its info
|
||||
// into the secret. Simulate that, then verify reconcile again and verify
|
||||
@@ -186,7 +185,7 @@ func TestAnnotations(t *testing.T) {
|
||||
|
||||
expectEqual(t, fc, expectedSecret(fullName))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
|
||||
want := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
@@ -283,7 +282,7 @@ func TestAnnotationIntoLB(t *testing.T) {
|
||||
|
||||
expectEqual(t, fc, expectedSecret(fullName))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
|
||||
|
||||
// Normally the Tailscale proxy pod would come up here and write its info
|
||||
// into the secret. Simulate that, since it would have normally happened at
|
||||
@@ -327,7 +326,7 @@ func TestAnnotationIntoLB(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
// None of the proxy machinery should have changed...
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
|
||||
// ... but the service should have a LoadBalancer status.
|
||||
|
||||
want = &corev1.Service{
|
||||
@@ -399,7 +398,7 @@ func TestLBIntoAnnotation(t *testing.T) {
|
||||
|
||||
expectEqual(t, fc, expectedSecret(fullName))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
|
||||
|
||||
// Normally the Tailscale proxy pod would come up here and write its info
|
||||
// into the secret. Simulate that, then verify reconcile again and verify
|
||||
@@ -456,7 +455,7 @@ func TestLBIntoAnnotation(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", ""))
|
||||
|
||||
want = &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -523,7 +522,7 @@ func TestCustomHostname(t *testing.T) {
|
||||
|
||||
expectEqual(t, fc, expectedSecret(fullName))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "reindeer-flotilla"))
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "reindeer-flotilla", ""))
|
||||
want := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
@@ -582,6 +581,51 @@ func TestCustomHostname(t *testing.T) {
|
||||
expectEqual(t, fc, want)
|
||||
}
|
||||
|
||||
func TestCustomPriorityClassName(t *testing.T) {
|
||||
fc := fake.NewFakeClient()
|
||||
ft := &fakeTSClient{}
|
||||
zl, err := zap.NewDevelopment()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sr := &ServiceReconciler{
|
||||
Client: fc,
|
||||
tsClient: ft,
|
||||
defaultTags: []string{"tag:k8s"},
|
||||
operatorNamespace: "operator-ns",
|
||||
proxyImage: "tailscale/tailscale",
|
||||
proxyPriorityClassName: "tailscale-critical",
|
||||
logger: zl.Sugar(),
|
||||
}
|
||||
|
||||
// Create a service that we should manage, and check that the initial round
|
||||
// of objects looks right.
|
||||
mustCreate(t, fc, &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
// The apiserver is supposed to set the UID, but the fake client
|
||||
// doesn't. So, set it explicitly because other code later depends
|
||||
// on it being set.
|
||||
UID: types.UID("1234-UID"),
|
||||
Annotations: map[string]string{
|
||||
"tailscale.com/expose": "true",
|
||||
"tailscale.com/hostname": "custom-priority-class-name",
|
||||
},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
ClusterIP: "10.20.30.40",
|
||||
Type: corev1.ServiceTypeClusterIP,
|
||||
},
|
||||
})
|
||||
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test")
|
||||
|
||||
expectEqual(t, fc, expectedSTS(shortName, fullName, "custom-priority-class-name", "tailscale-critical"))
|
||||
}
|
||||
|
||||
func expectedSecret(name string) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -630,7 +674,7 @@ func expectedHeadlessService(name string) *corev1.Service {
|
||||
}
|
||||
}
|
||||
|
||||
func expectedSTS(stsName, secretName, hostname string) *appsv1.StatefulSet {
|
||||
func expectedSTS(stsName, secretName, hostname, priorityClassName string) *appsv1.StatefulSet {
|
||||
return &appsv1.StatefulSet{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StatefulSet",
|
||||
@@ -659,6 +703,7 @@ func expectedSTS(stsName, secretName, hostname string) *appsv1.StatefulSet {
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ServiceAccountName: "proxies",
|
||||
PriorityClassName: priorityClassName,
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "sysctler",
|
||||
@@ -670,11 +715,11 @@ func expectedSTS(stsName, secretName, hostname string) *appsv1.StatefulSet {
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "tailscale",
|
||||
Image: "tailscale/tailscale",
|
||||
Env: []v1.EnvVar{
|
||||
Env: []corev1.EnvVar{
|
||||
{Name: "TS_USERSPACE", Value: "false"},
|
||||
{Name: "TS_AUTH_ONCE", Value: "true"},
|
||||
{Name: "TS_DEST_IP", Value: "10.20.30.40"},
|
||||
@@ -815,7 +860,6 @@ func (c *fakeTSClient) CreateKey(ctx context.Context, caps tailscale.KeyCapabili
|
||||
k := &tailscale.Key{
|
||||
ID: "key",
|
||||
Created: time.Now(),
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
Capabilities: caps,
|
||||
}
|
||||
return "secret-authkey", k, nil
|
||||
|
||||
@@ -272,7 +272,7 @@ func (p *proxy) serve(sessionID int64, c net.Conn) error {
|
||||
}
|
||||
if buf[0] != 'S' {
|
||||
p.errors.Add("upstream-bad-protocol", 1)
|
||||
return fmt.Errorf("upstream didn't acknowldge start-ssl, said %q", buf[0])
|
||||
return fmt.Errorf("upstream didn't acknowledge start-ssl, said %q", buf[0])
|
||||
}
|
||||
tlsConf := &tls.Config{
|
||||
ServerName: p.upstreamHost,
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"inet.af/tcpproxy"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tsnet"
|
||||
"tailscale.com/types/nettype"
|
||||
@@ -36,6 +37,8 @@ func main() {
|
||||
log.Fatal("no ports")
|
||||
}
|
||||
|
||||
hostinfo.SetApp("sniproxy")
|
||||
|
||||
var s server
|
||||
defer s.ts.Close()
|
||||
|
||||
@@ -66,7 +66,7 @@ func isSystemdSystem() bool {
|
||||
return false
|
||||
}
|
||||
switch distro.Get() {
|
||||
case distro.QNAP, distro.Gokrazy, distro.Synology:
|
||||
case distro.QNAP, distro.Gokrazy, distro.Synology, distro.Unraid:
|
||||
return false
|
||||
}
|
||||
_, err := exec.LookPath("systemctl")
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/net/netcheck"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/portmapper"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -45,9 +46,14 @@ var netcheckArgs struct {
|
||||
}
|
||||
|
||||
func runNetcheck(ctx context.Context, args []string) error {
|
||||
logf := logger.WithPrefix(log.Printf, "portmap: ")
|
||||
netMon, err := netmon.New(logf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := &netcheck.Client{
|
||||
UDPBindAddr: envknob.String("TS_DEBUG_NETCHECK_UDP_BIND"),
|
||||
PortMapper: portmapper.NewClient(logger.WithPrefix(log.Printf, "portmap: "), nil, nil),
|
||||
PortMapper: portmapper.NewClient(logf, netMon, nil, nil),
|
||||
UseDNSCache: false, // always resolve, don't cache
|
||||
}
|
||||
if netcheckArgs.verbose {
|
||||
@@ -97,7 +103,6 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
|
||||
var err error
|
||||
switch netcheckArgs.format {
|
||||
case "":
|
||||
break
|
||||
case "json":
|
||||
j, err = json.MarshalIndent(report, "", "\t")
|
||||
case "json-line":
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -39,6 +40,7 @@ serve https:<port> <mount-point> <source> [off]
|
||||
serve tcp:<port> tcp://localhost:<local-port> [off]
|
||||
serve tls-terminated-tcp:<port> tcp://localhost:<local-port> [off]
|
||||
serve status [--json]
|
||||
serve reset
|
||||
`),
|
||||
LongHelp: strings.TrimSpace(`
|
||||
*** BETA; all of this is subject to change ***
|
||||
@@ -86,6 +88,13 @@ EXAMPLES
|
||||
}),
|
||||
UsageFunc: usageFunc,
|
||||
},
|
||||
{
|
||||
Name: "reset",
|
||||
Exec: e.runServeReset,
|
||||
ShortHelp: "reset current serve/funnel config",
|
||||
FlagSet: e.newFlags("serve-reset", nil),
|
||||
UsageFunc: usageFunc,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -412,6 +421,7 @@ func cleanMountPoint(mount string) (string, error) {
|
||||
if mount == "" {
|
||||
return "", errors.New("mount point cannot be empty")
|
||||
}
|
||||
mount = cleanMinGWPathConversionIfNeeded(mount)
|
||||
if !strings.HasPrefix(mount, "/") {
|
||||
mount = "/" + mount
|
||||
}
|
||||
@@ -422,6 +432,26 @@ func cleanMountPoint(mount string) (string, error) {
|
||||
return "", fmt.Errorf("invalid mount point %q", mount)
|
||||
}
|
||||
|
||||
// cleanMinGWPathConversionIfNeeded strips the EXEPATH prefix from the given
|
||||
// path if the path is a MinGW(ish) (Windows) shell arg.
|
||||
//
|
||||
// MinGW(ish) (Windows) shells perform POSIX-to-Windows path conversion
|
||||
// converting the leading "/" of any shell arg to the EXEPATH, which mangles the
|
||||
// mount point. Strip the EXEPATH prefix if it exists. #7963
|
||||
//
|
||||
// "/C:/Program Files/Git/foo" -> "/foo"
|
||||
func cleanMinGWPathConversionIfNeeded(path string) string {
|
||||
// Only do this on Windows.
|
||||
if runtime.GOOS != "windows" {
|
||||
return path
|
||||
}
|
||||
if _, ok := os.LookupEnv("MSYSTEM"); ok {
|
||||
exepath := filepath.ToSlash(os.Getenv("EXEPATH"))
|
||||
path = strings.TrimPrefix(path, exepath)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func expandProxyTarget(source string) (string, error) {
|
||||
if !strings.Contains(source, "://") {
|
||||
source = "http://" + source
|
||||
@@ -683,3 +713,15 @@ func elipticallyTruncate(s string, max int) string {
|
||||
}
|
||||
return s[:max-3] + "..."
|
||||
}
|
||||
|
||||
// runServeReset clears out the current serve config.
|
||||
//
|
||||
// Usage:
|
||||
// - tailscale serve reset
|
||||
func (e *serveEnv) runServeReset(ctx context.Context, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return flag.ErrHelp
|
||||
}
|
||||
sc := new(ipn.ServeConfig)
|
||||
return e.lc.SetServeConfig(ctx, sc)
|
||||
}
|
||||
|
||||
@@ -224,7 +224,10 @@ func TestServeConfigMutations(t *testing.T) {
|
||||
command: cmd("https:443 bar https://127.0.0.1:8443"),
|
||||
want: nil, // nothing to save
|
||||
})
|
||||
add(step{reset: true})
|
||||
add(step{ // try resetting using reset command
|
||||
command: cmd("reset"),
|
||||
want: &ipn.ServeConfig{},
|
||||
})
|
||||
add(step{
|
||||
command: cmd("https:443 / https+insecure://127.0.0.1:3001"),
|
||||
want: &ipn.ServeConfig{
|
||||
|
||||
@@ -13,11 +13,13 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
@@ -26,6 +28,9 @@ import (
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
qrcode "github.com/skip2/go-qrcode"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health/healthmsg"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
@@ -663,6 +668,10 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authKey, err = resolveAuthKey(ctx, authKey, upArgs.advertiseTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := localClient.Start(ctx, ipn.Options{
|
||||
AuthKey: authKey,
|
||||
UpdatePrefs: prefs,
|
||||
@@ -1102,3 +1111,96 @@ func anyPeerAdvertisingRoutes(st *ipnstate.Status) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Required to use our client API. We're fine with the instability since the
|
||||
// client lives in the same repo as this code.
|
||||
tailscale.I_Acknowledge_This_API_Is_Unstable = true
|
||||
}
|
||||
|
||||
// resolveAuthKey either returns v unchanged (in the common case) or, if it
|
||||
// starts with "tskey-client-" (as Tailscale OAuth secrets do) parses it like
|
||||
//
|
||||
// tskey-client-xxxx[?ephemeral=false&bar&preauthorized=BOOL&baseURL=...]
|
||||
//
|
||||
// and does the OAuth2 dance to get and return an authkey. The "ephemeral"
|
||||
// property defaults to true if unspecified. The "preauthorized" defaults to
|
||||
// false. The "baseURL" defaults to https://api.tailscale.com.
|
||||
// The passed in tags are required, and must be non-empty. These will be
|
||||
// set on the authkey generated by the OAuth2 dance.
|
||||
func resolveAuthKey(ctx context.Context, v, tags string) (string, error) {
|
||||
if !strings.HasPrefix(v, "tskey-client-") {
|
||||
return v, nil
|
||||
}
|
||||
if !envknob.Bool("TS_EXPERIMENT_OAUTH_AUTHKEY") {
|
||||
return "", errors.New("oauth authkeys are in experimental status")
|
||||
}
|
||||
if tags == "" {
|
||||
return "", errors.New("oauth authkeys require --advertise-tags")
|
||||
}
|
||||
|
||||
clientSecret, named, _ := strings.Cut(v, "?")
|
||||
attrs, err := url.ParseQuery(named)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for k := range attrs {
|
||||
switch k {
|
||||
case "ephemeral", "preauthorized", "baseURL":
|
||||
default:
|
||||
return "", fmt.Errorf("unknown attribute %q", k)
|
||||
}
|
||||
}
|
||||
getBool := func(name string, def bool) (bool, error) {
|
||||
v := attrs.Get(name)
|
||||
if v == "" {
|
||||
return def, nil
|
||||
}
|
||||
ret, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid attribute boolean attribute %s value %q", name, v)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
ephemeral, err := getBool("ephemeral", true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
preauth, err := getBool("preauthorized", false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseURL := "https://api.tailscale.com"
|
||||
if v := attrs.Get("baseURL"); v != "" {
|
||||
baseURL = v
|
||||
}
|
||||
|
||||
credentials := clientcredentials.Config{
|
||||
ClientID: "some-client-id", // ignored
|
||||
ClientSecret: clientSecret,
|
||||
TokenURL: baseURL + "/api/v2/oauth/token",
|
||||
Scopes: []string{"device"},
|
||||
}
|
||||
|
||||
tsClient := tailscale.NewClient("-", nil)
|
||||
tsClient.HTTPClient = credentials.Client(ctx)
|
||||
tsClient.BaseURL = baseURL
|
||||
|
||||
caps := tailscale.KeyCapabilities{
|
||||
Devices: tailscale.KeyDeviceCapabilities{
|
||||
Create: tailscale.KeyDeviceCreateCapabilities{
|
||||
Reusable: false,
|
||||
Ephemeral: ephemeral,
|
||||
Preauthorized: preauth,
|
||||
Tags: strings.Split(tags, ","),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
authkey, _, err := tsClient.CreateKey(ctx, caps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return authkey, nil
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ type tmplData struct {
|
||||
TUNMode bool
|
||||
IsSynology bool
|
||||
DSMVersion int // 6 or 7, if IsSynology=true
|
||||
IsUnraid bool
|
||||
UnraidToken string
|
||||
IPNVersion string
|
||||
}
|
||||
|
||||
@@ -441,6 +443,8 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||
TUNMode: st.TUN,
|
||||
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
|
||||
DSMVersion: distro.DSMVersion(),
|
||||
IsUnraid: distro.Get() == distro.Unraid,
|
||||
UnraidToken: os.Getenv("UNRAID_CSRF_TOKEN"),
|
||||
IPNVersion: versionShort,
|
||||
}
|
||||
exitNodeRouteV4 := netip.MustParsePrefix("0.0.0.0/0")
|
||||
|
||||
@@ -116,10 +116,12 @@
|
||||
<a class="text-xs text-gray-500 hover:text-gray-600" href="{{ .LicensesURL }}">Open Source Licenses</a>
|
||||
</footer>
|
||||
<script>(function () {
|
||||
const advertiseExitNode = {{.AdvertiseExitNode}};
|
||||
const advertiseExitNode = {{ .AdvertiseExitNode }};
|
||||
const isUnraid = {{ .IsUnraid }};
|
||||
const unraidCsrfToken = "{{ .UnraidToken }}";
|
||||
let fetchingUrl = false;
|
||||
var data = {
|
||||
AdvertiseRoutes: "{{.AdvertiseRoutes}}",
|
||||
AdvertiseRoutes: "{{ .AdvertiseRoutes }}",
|
||||
AdvertiseExitNode: advertiseExitNode,
|
||||
Reauthenticate: false,
|
||||
ForceLogout: false
|
||||
@@ -141,15 +143,27 @@ function postData(e) {
|
||||
}
|
||||
const nextUrl = new URL(window.location);
|
||||
nextUrl.search = nextParams.toString()
|
||||
const url = nextUrl.toString();
|
||||
|
||||
let body = JSON.stringify(data);
|
||||
let contentType = "application/json";
|
||||
|
||||
if (isUnraid) {
|
||||
const params = new URLSearchParams();
|
||||
params.append("csrf_token", unraidCsrfToken);
|
||||
params.append("ts_data", JSON.stringify(data));
|
||||
|
||||
body = params.toString();
|
||||
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
|
||||
}
|
||||
|
||||
const url = nextUrl.toString();
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Content-Type": contentType,
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
body: body
|
||||
}).then(res => res.json()).then(res => {
|
||||
fetchingUrl = false;
|
||||
const err = res["error"];
|
||||
@@ -158,7 +172,11 @@ function postData(e) {
|
||||
}
|
||||
const url = res["url"];
|
||||
if (url) {
|
||||
document.location.href = url;
|
||||
if(isUnraid) {
|
||||
window.open(url, "_blank");
|
||||
} else {
|
||||
document.location.href = url;
|
||||
}
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
|
||||
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
|
||||
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
|
||||
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
|
||||
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
@@ -13,7 +15,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
github.com/google/uuid from tailscale.com/util/quarantine+
|
||||
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
@@ -74,6 +76,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/netknob from tailscale.com/net/netns
|
||||
tailscale.com/net/netmon from tailscale.com/net/sockstats+
|
||||
tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
||||
tailscale.com/net/netutil from tailscale.com/client/tailscale+
|
||||
tailscale.com/net/packet from tailscale.com/wgengine/filter+
|
||||
@@ -151,9 +154,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
golang.org/x/net/icmp from tailscale.com/net/ping
|
||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||
golang.org/x/net/ipv4 from golang.org/x/net/icmp+
|
||||
golang.org/x/net/ipv6 from golang.org/x/net/icmp
|
||||
golang.org/x/net/ipv6 from golang.org/x/net/icmp+
|
||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||
D golang.org/x/net/route from net+
|
||||
golang.org/x/oauth2 from golang.org/x/oauth2/clientcredentials
|
||||
golang.org/x/oauth2/clientcredentials from tailscale.com/cmd/tailscale/cli
|
||||
golang.org/x/oauth2/internal from golang.org/x/oauth2+
|
||||
golang.org/x/sync/errgroup from tailscale.com/derp+
|
||||
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
||||
LD golang.org/x/sys/unix from tailscale.com/net/netns+
|
||||
|
||||
@@ -23,10 +23,10 @@ import (
|
||||
"tailscale.com/derp/derphttp"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
var debugArgs struct {
|
||||
@@ -42,7 +42,7 @@ var debugModeFunc = debugMode // so it can be addressable
|
||||
func debugMode(args []string) error {
|
||||
fs := flag.NewFlagSet("debug", flag.ExitOnError)
|
||||
fs.BoolVar(&debugArgs.ifconfig, "ifconfig", false, "If true, print network interface state")
|
||||
fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run link monitor forever. Precludes all other options.")
|
||||
fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run network monitor forever. Precludes all other options.")
|
||||
fs.BoolVar(&debugArgs.portmap, "portmap", false, "If true, run portmap debugging. Precludes all other options.")
|
||||
fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.")
|
||||
fs.StringVar(&debugArgs.derpCheck, "derp", "", "if non-empty, test a DERP ping via named region code")
|
||||
@@ -76,7 +76,7 @@ func runMonitor(ctx context.Context, loop bool) error {
|
||||
j, _ := json.MarshalIndent(st, "", " ")
|
||||
os.Stderr.Write(j)
|
||||
}
|
||||
mon, err := monitor.New(log.Printf)
|
||||
mon, err := netmon.New(log.Printf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,10 +84,10 @@ func runMonitor(ctx context.Context, loop bool) error {
|
||||
|
||||
mon.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
|
||||
if !changed {
|
||||
log.Printf("Link monitor fired; no change")
|
||||
log.Printf("Network monitor fired; no change")
|
||||
return
|
||||
}
|
||||
log.Printf("Link monitor fired. New state:")
|
||||
log.Printf("Network monitor fired. New state:")
|
||||
dump(st)
|
||||
})
|
||||
if loop {
|
||||
@@ -193,8 +193,8 @@ func checkDerp(ctx context.Context, derpRegion string) (err error) {
|
||||
priv1 := key.NewNode()
|
||||
priv2 := key.NewNode()
|
||||
|
||||
c1 := derphttp.NewRegionClient(priv1, log.Printf, getRegion)
|
||||
c2 := derphttp.NewRegionClient(priv2, log.Printf, getRegion)
|
||||
c1 := derphttp.NewRegionClient(priv1, log.Printf, nil, getRegion)
|
||||
c2 := derphttp.NewRegionClient(priv2, log.Printf, nil, getRegion)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c1.Close()
|
||||
|
||||
@@ -3,7 +3,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket
|
||||
W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio
|
||||
W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio
|
||||
W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs
|
||||
W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
@@ -12,7 +14,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2 from github.com/aws/aws-sdk-go-v2/internal/ini
|
||||
L github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/ipn/store/awsstore
|
||||
L github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||
L github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/middleware from github.com/aws/aws-sdk-go-v2/aws/retry+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/query from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/restjson from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
@@ -38,6 +40,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/internal/rand from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/sdk from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/sdkio from github.com/aws/aws-sdk-go-v2/credentials/processcreds
|
||||
L github.com/aws/aws-sdk-go-v2/internal/shareddefaults from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/strings from github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4
|
||||
L github.com/aws/aws-sdk-go-v2/internal/sync/singleflight from github.com/aws/aws-sdk-go-v2/aws
|
||||
L github.com/aws/aws-sdk-go-v2/internal/timeconv from github.com/aws/aws-sdk-go-v2/aws/retry
|
||||
@@ -48,16 +51,19 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/service/sso from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/service/sso/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sso
|
||||
L github.com/aws/aws-sdk-go-v2/service/sso/types from github.com/aws/aws-sdk-go-v2/service/sso
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssooidc from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssooidc/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssooidc
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssooidc/types from github.com/aws/aws-sdk-go-v2/service/ssooidc
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts/types from github.com/aws/aws-sdk-go-v2/credentials/stscreds+
|
||||
L github.com/aws/smithy-go from github.com/aws/aws-sdk-go-v2/aws/protocol/restjson+
|
||||
L github.com/aws/smithy-go/auth/bearer from github.com/aws/aws-sdk-go-v2/aws
|
||||
L github.com/aws/smithy-go/auth/bearer from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/context from github.com/aws/smithy-go/auth/bearer
|
||||
L github.com/aws/smithy-go/document from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/smithy-go/encoding from github.com/aws/smithy-go/encoding/json+
|
||||
L github.com/aws/smithy-go/encoding/httpbinding from github.com/aws/aws-sdk-go-v2/aws/protocol/query+
|
||||
L github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||
L github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/smithy-go/encoding/xml from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/smithy-go/internal/sync/singleflight from github.com/aws/smithy-go/auth/bearer
|
||||
L github.com/aws/smithy-go/io from github.com/aws/aws-sdk-go-v2/feature/ec2/imds+
|
||||
@@ -73,6 +79,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
|
||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com
|
||||
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled
|
||||
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
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
|
||||
@@ -93,7 +100,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
|
||||
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd+
|
||||
github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/zstd from tailscale.com/smallzstd
|
||||
github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd
|
||||
@@ -105,12 +112,17 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
|
||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||
L github.com/pierrec/lz4/v4 from github.com/u-root/uio/uio
|
||||
L github.com/pierrec/lz4/v4/internal/lz4block from github.com/pierrec/lz4/v4+
|
||||
L github.com/pierrec/lz4/v4/internal/lz4errors from github.com/pierrec/lz4/v4+
|
||||
L github.com/pierrec/lz4/v4/internal/lz4stream from github.com/pierrec/lz4/v4
|
||||
L github.com/pierrec/lz4/v4/internal/xxh32 from github.com/pierrec/lz4/v4/internal/lz4stream
|
||||
W github.com/pkg/errors from github.com/tailscale/certstore
|
||||
LD github.com/pkg/sftp from tailscale.com/ssh/tailssh
|
||||
LD github.com/pkg/sftp/internal/encoding/ssh/filexfer from github.com/pkg/sftp
|
||||
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
|
||||
LD github.com/tailscale/golang-x-crypto/chacha20 from github.com/tailscale/golang-x-crypto/ssh
|
||||
LD 💣 github.com/tailscale/golang-x-crypto/internal/subtle from github.com/tailscale/golang-x-crypto/chacha20
|
||||
LD 💣 github.com/tailscale/golang-x-crypto/internal/alias from github.com/tailscale/golang-x-crypto/chacha20
|
||||
LD github.com/tailscale/golang-x-crypto/ssh from tailscale.com/ipn/ipnlocal+
|
||||
LD github.com/tailscale/golang-x-crypto/ssh/internal/bcrypt_pbkdf from github.com/tailscale/golang-x-crypto/ssh
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
@@ -240,11 +252,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
|
||||
tailscale.com/net/netknob from tailscale.com/net/netns+
|
||||
tailscale.com/net/netmon from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
||||
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnauth+
|
||||
tailscale.com/net/netutil from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/packet from tailscale.com/net/tstun+
|
||||
tailscale.com/net/ping from tailscale.com/net/netcheck
|
||||
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/net/routetable from tailscale.com/doctor/routetable
|
||||
@@ -269,7 +282,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
|
||||
tailscale.com/tka from tailscale.com/ipn/ipnlocal+
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock+
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+
|
||||
tailscale.com/tstime/rate from tailscale.com/wgengine/filter+
|
||||
tailscale.com/tsweb/varz from tailscale.com/cmd/tailscaled
|
||||
@@ -324,7 +338,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/wgengine/capture from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
|
||||
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/wgengine/monitor from tailscale.com/control/controlclient+
|
||||
tailscale.com/wgengine/netlog from tailscale.com/wgengine
|
||||
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
func configureTaildrop(logf logger.Logf, lb *ipnlocal.LocalBackend) {
|
||||
dg := distro.Get()
|
||||
switch dg {
|
||||
case distro.Synology, distro.TrueNAS, distro.QNAP:
|
||||
case distro.Synology, distro.TrueNAS, distro.QNAP, distro.Unraid:
|
||||
// See if they have a "Taildrop" share.
|
||||
// See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319
|
||||
path, err := findTaildropDir(dg)
|
||||
@@ -42,6 +42,8 @@ func findTaildropDir(dg distro.Distro) (string, error) {
|
||||
return findTrueNASTaildropDir(name)
|
||||
case distro.QNAP:
|
||||
return findQnapTaildropDir(name)
|
||||
case distro.Unraid:
|
||||
return findUnraidTaildropDir(name)
|
||||
}
|
||||
return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg)
|
||||
}
|
||||
@@ -103,3 +105,25 @@ func findQnapTaildropDir(name string) (string, error) {
|
||||
}
|
||||
return "", fmt.Errorf("shared folder %q not found", name)
|
||||
}
|
||||
|
||||
// findUnraidTaildropDir looks for a directory linked at
|
||||
// /var/lib/tailscale/Taildrop. This is a symlink to the
|
||||
// path specified by the user in the Unraid Web UI
|
||||
func findUnraidTaildropDir(name string) (string, error) {
|
||||
dir := fmt.Sprintf("/var/lib/tailscale/%s", name)
|
||||
_, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("symlink %q not found", name)
|
||||
}
|
||||
|
||||
fullpath, err := filepath.EvalSymlinks(dir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("symlink %q to shared folder not valid", name)
|
||||
}
|
||||
|
||||
fi, err := os.Stat(fullpath)
|
||||
if err == nil && fi.IsDir() {
|
||||
return dir, nil // return the symlink
|
||||
}
|
||||
return "", fmt.Errorf("shared folder %q not found", name)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"tailscale.com/logtail"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/dnsfallback"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/proxymux"
|
||||
"tailscale.com/net/socks5"
|
||||
@@ -49,6 +50,7 @@ import (
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/smallzstd"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/tsweb/varz"
|
||||
"tailscale.com/types/flagtype"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -59,7 +61,6 @@ import (
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
"tailscale.com/wgengine/netstack"
|
||||
"tailscale.com/wgengine/router"
|
||||
)
|
||||
@@ -329,7 +330,19 @@ var logPol *logpolicy.Policy
|
||||
var debugMux *http.ServeMux
|
||||
|
||||
func run() error {
|
||||
pol := logpolicy.New(logtail.CollectionNode)
|
||||
var logf logger.Logf = log.Printf
|
||||
|
||||
sys := new(tsd.System)
|
||||
|
||||
netMon, err := netmon.New(func(format string, args ...any) {
|
||||
logf(format, args...)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("netmon.New: %w", err)
|
||||
}
|
||||
sys.Set(netMon)
|
||||
|
||||
pol := logpolicy.New(logtail.CollectionNode, netMon)
|
||||
pol.SetVerbosityLevel(args.verbose)
|
||||
logPol = pol
|
||||
defer func() {
|
||||
@@ -353,7 +366,6 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var logf logger.Logf = log.Printf
|
||||
if envknob.Bool("TS_DEBUG_MEMORY") {
|
||||
logf = logger.RusagePrefixLog(logf)
|
||||
}
|
||||
@@ -379,10 +391,10 @@ func run() error {
|
||||
debugMux = newDebugMux()
|
||||
}
|
||||
|
||||
return startIPNServer(context.Background(), logf, pol.PublicID)
|
||||
return startIPNServer(context.Background(), logf, pol.PublicID, sys)
|
||||
}
|
||||
|
||||
func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID) error {
|
||||
func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) error {
|
||||
ln, err := safesocket.Listen(args.socketpath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("safesocket.Listen: %v", err)
|
||||
@@ -408,7 +420,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID)
|
||||
}
|
||||
}()
|
||||
|
||||
srv := ipnserver.New(logf, logID)
|
||||
srv := ipnserver.New(logf, logID, sys.NetMon.Get())
|
||||
if debugMux != nil {
|
||||
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)
|
||||
}
|
||||
@@ -426,7 +438,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID)
|
||||
return
|
||||
}
|
||||
}
|
||||
lb, err := getLocalBackend(ctx, logf, logID)
|
||||
lb, err := getLocalBackend(ctx, logf, logID, sys)
|
||||
if err == nil {
|
||||
logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond))
|
||||
srv.SetLocalBackend(lb)
|
||||
@@ -450,35 +462,28 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID) (_ *ipnlocal.LocalBackend, retErr error) {
|
||||
linkMon, err := monitor.New(logf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("monitor.New: %w", err)
|
||||
}
|
||||
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) (_ *ipnlocal.LocalBackend, retErr error) {
|
||||
if logPol != nil {
|
||||
logPol.Logtail.SetLinkMonitor(linkMon)
|
||||
logPol.Logtail.SetNetMon(sys.NetMon.Get())
|
||||
}
|
||||
|
||||
socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr)
|
||||
|
||||
dialer := &tsdial.Dialer{Logf: logf} // mutated below (before used)
|
||||
e, onlyNetstack, err := createEngine(logf, linkMon, dialer)
|
||||
sys.Set(dialer)
|
||||
|
||||
onlyNetstack, err := createEngine(logf, sys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("createEngine: %w", err)
|
||||
}
|
||||
if _, ok := e.(wgengine.ResolvingEngine).GetResolver(); !ok {
|
||||
panic("internal error: exit node resolver not wired up")
|
||||
}
|
||||
if debugMux != nil {
|
||||
if ig, ok := e.(wgengine.InternalsGetter); ok {
|
||||
if _, mc, _, ok := ig.GetInternals(); ok {
|
||||
debugMux.HandleFunc("/debug/magicsock", mc.ServeHTTPDebug)
|
||||
}
|
||||
if ms, ok := sys.MagicSock.GetOK(); ok {
|
||||
debugMux.HandleFunc("/debug/magicsock", ms.ServeHTTPDebug)
|
||||
}
|
||||
go runDebugServer(debugMux, args.debug)
|
||||
}
|
||||
|
||||
ns, err := newNetstack(logf, dialer, e)
|
||||
ns, err := newNetstack(logf, sys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("newNetstack: %w", err)
|
||||
}
|
||||
@@ -486,6 +491,7 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
|
||||
ns.ProcessSubnets = onlyNetstack || handleSubnetsInNetstack()
|
||||
|
||||
if onlyNetstack {
|
||||
e := sys.Engine.Get()
|
||||
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
|
||||
_, ok := e.PeerForIP(ip)
|
||||
return ok
|
||||
@@ -516,16 +522,15 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
|
||||
tshttpproxy.SetSelfProxy(addrs...)
|
||||
}
|
||||
|
||||
e = wgengine.NewWatchdog(e)
|
||||
|
||||
opts := ipnServerOpts()
|
||||
|
||||
store, err := store.New(logf, statePathOrDefault())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store.New: %w", err)
|
||||
}
|
||||
sys.Set(store)
|
||||
|
||||
lb, err := ipnlocal.NewLocalBackend(logf, logID, store, dialer, e, opts.LoginFlags)
|
||||
lb, err := ipnlocal.NewLocalBackend(logf, logID, sys, opts.LoginFlags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err)
|
||||
}
|
||||
@@ -534,7 +539,7 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
|
||||
lb.SetLogFlusher(logPol.Logtail.StartFlush)
|
||||
}
|
||||
if root := lb.TailscaleVarRoot(); root != "" {
|
||||
dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json"))
|
||||
dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json"), logf)
|
||||
}
|
||||
lb.SetDecompressor(func() (controlclient.Decompressor, error) {
|
||||
return smallzstd.NewDecoder(nil)
|
||||
@@ -551,21 +556,21 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
|
||||
//
|
||||
// onlyNetstack is true if the user has explicitly requested that we use netstack
|
||||
// for all networking.
|
||||
func createEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer) (e wgengine.Engine, onlyNetstack bool, err error) {
|
||||
func createEngine(logf logger.Logf, sys *tsd.System) (onlyNetstack bool, err error) {
|
||||
if args.tunname == "" {
|
||||
return nil, false, errors.New("no --tun value specified")
|
||||
return false, errors.New("no --tun value specified")
|
||||
}
|
||||
var errs []error
|
||||
for _, name := range strings.Split(args.tunname, ",") {
|
||||
logf("wgengine.NewUserspaceEngine(tun %q) ...", name)
|
||||
e, onlyNetstack, err = tryEngine(logf, linkMon, dialer, name)
|
||||
onlyNetstack, err = tryEngine(logf, sys, name)
|
||||
if err == nil {
|
||||
return e, onlyNetstack, nil
|
||||
return onlyNetstack, nil
|
||||
}
|
||||
logf("wgengine.NewUserspaceEngine(tun %q) error: %v", name, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil, false, multierr.New(errs...)
|
||||
return false, multierr.New(errs...)
|
||||
}
|
||||
|
||||
// handleSubnetsInNetstack reports whether netstack should handle subnet routers
|
||||
@@ -590,21 +595,23 @@ func handleSubnetsInNetstack() bool {
|
||||
|
||||
var tstunNew = tstun.New
|
||||
|
||||
func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, name string) (e wgengine.Engine, onlyNetstack bool, err error) {
|
||||
func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack bool, err error) {
|
||||
conf := wgengine.Config{
|
||||
ListenPort: args.port,
|
||||
LinkMonitor: linkMon,
|
||||
Dialer: dialer,
|
||||
ListenPort: args.port,
|
||||
NetMon: sys.NetMon.Get(),
|
||||
Dialer: sys.Dialer.Get(),
|
||||
SetSubsystem: sys.Set,
|
||||
}
|
||||
|
||||
onlyNetstack = name == "userspace-networking"
|
||||
netstackSubnetRouter := onlyNetstack // but mutated later on some platforms
|
||||
netns.SetEnabled(!onlyNetstack)
|
||||
|
||||
if args.birdSocketPath != "" && createBIRDClient != nil {
|
||||
log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath)
|
||||
conf.BIRDClient, err = createBIRDClient(args.birdSocketPath)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("createBIRDClient: %w", err)
|
||||
return false, fmt.Errorf("createBIRDClient: %w", err)
|
||||
}
|
||||
}
|
||||
if onlyNetstack {
|
||||
@@ -617,44 +624,55 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, na
|
||||
// TODO(bradfitz): add a Synology-specific DNS manager.
|
||||
conf.DNS, err = dns.NewOSConfigurator(logf, "") // empty interface name
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
|
||||
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dev, devName, err := tstunNew(logf, name)
|
||||
if err != nil {
|
||||
tstun.Diagnose(logf, name, err)
|
||||
return nil, false, fmt.Errorf("tstun.New(%q): %w", name, err)
|
||||
return false, fmt.Errorf("tstun.New(%q): %w", name, err)
|
||||
}
|
||||
conf.Tun = dev
|
||||
if strings.HasPrefix(name, "tap:") {
|
||||
conf.IsTAP = true
|
||||
e, err := wgengine.NewUserspaceEngine(logf, conf)
|
||||
return e, false, err
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
sys.Set(e)
|
||||
return false, err
|
||||
}
|
||||
|
||||
r, err := router.New(logf, dev, linkMon)
|
||||
r, err := router.New(logf, dev, sys.NetMon.Get())
|
||||
if err != nil {
|
||||
dev.Close()
|
||||
return nil, false, fmt.Errorf("creating router: %w", err)
|
||||
return false, fmt.Errorf("creating router: %w", err)
|
||||
}
|
||||
|
||||
d, err := dns.NewOSConfigurator(logf, devName)
|
||||
if err != nil {
|
||||
dev.Close()
|
||||
r.Close()
|
||||
return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
|
||||
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
|
||||
}
|
||||
conf.DNS = d
|
||||
conf.Router = r
|
||||
if handleSubnetsInNetstack() {
|
||||
conf.Router = netstack.NewSubnetRouterWrapper(conf.Router)
|
||||
netstackSubnetRouter = true
|
||||
}
|
||||
sys.Set(conf.Router)
|
||||
}
|
||||
e, err = wgengine.NewUserspaceEngine(logf, conf)
|
||||
e, err := wgengine.NewUserspaceEngine(logf, conf)
|
||||
if err != nil {
|
||||
return nil, onlyNetstack, err
|
||||
return onlyNetstack, err
|
||||
}
|
||||
return e, onlyNetstack, nil
|
||||
e = wgengine.NewWatchdog(e)
|
||||
sys.Set(e)
|
||||
sys.NetstackRouter.Set(netstackSubnetRouter)
|
||||
|
||||
return onlyNetstack, nil
|
||||
}
|
||||
|
||||
func newDebugMux() *http.ServeMux {
|
||||
@@ -684,12 +702,8 @@ func runDebugServer(mux *http.ServeMux, addr string) {
|
||||
}
|
||||
}
|
||||
|
||||
func newNetstack(logf logger.Logf, dialer *tsdial.Dialer, e wgengine.Engine) (*netstack.Impl, error) {
|
||||
tunDev, magicConn, dns, ok := e.(wgengine.InternalsGetter).GetInternals()
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%T is not a wgengine.InternalsGetter", e)
|
||||
}
|
||||
return netstack.Create(logf, tunDev, e, magicConn, dialer, dns)
|
||||
func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
|
||||
return netstack.Create(logf, sys.Tun.Get(), sys.Engine.Get(), sys.MagicSock.Get(), sys.Dialer.Get(), sys.DNSManager.Get())
|
||||
}
|
||||
|
||||
// mustStartProxyListeners creates listeners for local SOCKS and HTTP
|
||||
|
||||
@@ -45,7 +45,9 @@ import (
|
||||
"tailscale.com/logpolicy"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tstun"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/logid"
|
||||
"tailscale.com/util/winutil"
|
||||
@@ -291,8 +293,15 @@ func beWindowsSubprocess() bool {
|
||||
}
|
||||
}()
|
||||
|
||||
sys := new(tsd.System)
|
||||
netMon, err := netmon.New(log.Printf)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create netMon: %v", err)
|
||||
}
|
||||
sys.Set(netMon)
|
||||
|
||||
publicLogID, _ := logid.ParsePublicID(logID)
|
||||
err := startIPNServer(ctx, log.Printf, publicLogID)
|
||||
err = startIPNServer(ctx, log.Printf, publicLogID, sys)
|
||||
if err != nil {
|
||||
log.Fatalf("ipnserver: %v", err)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/smallzstd"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/wgengine/netstack"
|
||||
"tailscale.com/words"
|
||||
@@ -46,7 +47,7 @@ import (
|
||||
var ControlURL = ipn.DefaultControlURL
|
||||
|
||||
func main() {
|
||||
js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 1 {
|
||||
log.Fatal("Usage: newIPN(config)")
|
||||
return nil
|
||||
@@ -96,19 +97,19 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
logtail := logtail.NewLogger(c, log.Printf)
|
||||
logf := logtail.Logf
|
||||
|
||||
sys := new(tsd.System)
|
||||
sys.Set(store)
|
||||
dialer := &tsdial.Dialer{Logf: logf}
|
||||
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
|
||||
Dialer: dialer,
|
||||
Dialer: dialer,
|
||||
SetSubsystem: sys.Set,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sys.Set(eng)
|
||||
|
||||
tunDev, magicConn, dnsManager, ok := eng.(wgengine.InternalsGetter).GetInternals()
|
||||
if !ok {
|
||||
log.Fatalf("%T is not a wgengine.InternalsGetter", eng)
|
||||
}
|
||||
ns, err := netstack.Create(logf, tunDev, eng, magicConn, dialer, dnsManager)
|
||||
ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
|
||||
if err != nil {
|
||||
log.Fatalf("netstack.Create: %v", err)
|
||||
}
|
||||
@@ -121,10 +122,11 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
|
||||
return ns.DialContextTCP(ctx, dst)
|
||||
}
|
||||
sys.NetstackRouter.Set(true)
|
||||
|
||||
logid := lpc.PublicID
|
||||
srv := ipnserver.New(logf, logid)
|
||||
lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, controlclient.LoginEphemeral)
|
||||
srv := ipnserver.New(logf, logid, nil /* no netMon */)
|
||||
lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginEphemeral)
|
||||
if err != nil {
|
||||
log.Fatalf("ipnlocal.NewLocalBackend: %v", err)
|
||||
}
|
||||
@@ -146,7 +148,7 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"run": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
"run": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 1 {
|
||||
log.Fatal(`Usage: run({
|
||||
notifyState(state: int): void,
|
||||
@@ -159,7 +161,7 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
jsIPN.run(args[0])
|
||||
return nil
|
||||
}),
|
||||
"login": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
"login": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 0 {
|
||||
log.Printf("Usage: login()")
|
||||
return nil
|
||||
@@ -167,7 +169,7 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
jsIPN.login()
|
||||
return nil
|
||||
}),
|
||||
"logout": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
"logout": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 0 {
|
||||
log.Printf("Usage: logout()")
|
||||
return nil
|
||||
@@ -175,7 +177,7 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
jsIPN.logout()
|
||||
return nil
|
||||
}),
|
||||
"ssh": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
"ssh": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 3 {
|
||||
log.Printf("Usage: ssh(hostname, userName, termConfig)")
|
||||
return nil
|
||||
@@ -185,7 +187,7 @@ func newIPN(jsConfig js.Value) map[string]any {
|
||||
args[1].String(),
|
||||
args[2])
|
||||
}),
|
||||
"fetch": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
"fetch": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if len(args) != 1 {
|
||||
log.Printf("Usage: fetch(url)")
|
||||
return nil
|
||||
@@ -334,10 +336,10 @@ func (i *jsIPN) ssh(host, username string, termConfig js.Value) map[string]any {
|
||||
go jsSSHSession.Run()
|
||||
|
||||
return map[string]any{
|
||||
"close": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
"close": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
return jsSSHSession.Close() != nil
|
||||
}),
|
||||
"resize": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
"resize": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
rows := args[0].Int()
|
||||
cols := args[1].Int()
|
||||
return jsSSHSession.Resize(rows, cols) != nil
|
||||
@@ -426,7 +428,7 @@ func (s *jsSSHSession) Run() {
|
||||
session.Stdout = termWriter{writeFn}
|
||||
session.Stderr = termWriter{writeFn}
|
||||
|
||||
setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
input := args[0].String()
|
||||
_, err := stdin.Write([]byte(input))
|
||||
if err != nil {
|
||||
@@ -496,7 +498,7 @@ func (i *jsIPN) fetch(url string) js.Value {
|
||||
return map[string]any{
|
||||
"status": res.StatusCode,
|
||||
"statusText": res.Status,
|
||||
"text": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
"text": js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
return makePromise(func() (any, error) {
|
||||
defer res.Body.Close()
|
||||
buf := new(bytes.Buffer)
|
||||
@@ -602,7 +604,7 @@ func generateHostname() string {
|
||||
// f is run on a goroutine and its return value is used to resolve the promise
|
||||
// (or reject it if an error is returned).
|
||||
func makePromise(f func() (any, error)) js.Value {
|
||||
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
handler := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
resolve := args[0]
|
||||
reject := args[1]
|
||||
go func() {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone --clone-only-type=OnlyGetClone
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded --clone-only-type=OnlyGetClone
|
||||
|
||||
type StructWithoutPtrs struct {
|
||||
Int int
|
||||
@@ -61,3 +61,8 @@ type StructWithSlices struct {
|
||||
type OnlyGetClone struct {
|
||||
SinViewerPorFavor bool
|
||||
}
|
||||
|
||||
type StructWithEmbedded struct {
|
||||
A *StructWithPtrs
|
||||
StructWithSlices
|
||||
}
|
||||
|
||||
@@ -211,3 +211,22 @@ func (src *OnlyGetClone) Clone() *OnlyGetClone {
|
||||
var _OnlyGetCloneCloneNeedsRegeneration = OnlyGetClone(struct {
|
||||
SinViewerPorFavor bool
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of StructWithEmbedded.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *StructWithEmbedded) Clone() *StructWithEmbedded {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(StructWithEmbedded)
|
||||
*dst = *src
|
||||
dst.A = src.A.Clone()
|
||||
dst.StructWithSlices = *src.StructWithSlices.Clone()
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _StructWithEmbeddedCloneNeedsRegeneration = StructWithEmbedded(struct {
|
||||
A *StructWithPtrs
|
||||
StructWithSlices
|
||||
}{})
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone
|
||||
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded
|
||||
|
||||
// View returns a readonly view of StructWithPtrs.
|
||||
func (p *StructWithPtrs) View() StructWithPtrsView {
|
||||
@@ -325,3 +325,59 @@ var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
|
||||
Prefixes []netip.Prefix
|
||||
Data []byte
|
||||
}{})
|
||||
|
||||
// View returns a readonly view of StructWithEmbedded.
|
||||
func (p *StructWithEmbedded) View() StructWithEmbeddedView {
|
||||
return StructWithEmbeddedView{ж: p}
|
||||
}
|
||||
|
||||
// StructWithEmbeddedView provides a read-only view over StructWithEmbedded.
|
||||
//
|
||||
// Its methods should only be called if `Valid()` returns true.
|
||||
type StructWithEmbeddedView struct {
|
||||
// ж is the underlying mutable value, named with a hard-to-type
|
||||
// character that looks pointy like a pointer.
|
||||
// It is named distinctively to make you think of how dangerous it is to escape
|
||||
// to callers. You must not let callers be able to mutate it.
|
||||
ж *StructWithEmbedded
|
||||
}
|
||||
|
||||
// Valid reports whether underlying value is non-nil.
|
||||
func (v StructWithEmbeddedView) Valid() bool { return v.ж != nil }
|
||||
|
||||
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||
// the original.
|
||||
func (v StructWithEmbeddedView) AsStruct() *StructWithEmbedded {
|
||||
if v.ж == nil {
|
||||
return nil
|
||||
}
|
||||
return v.ж.Clone()
|
||||
}
|
||||
|
||||
func (v StructWithEmbeddedView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
func (v *StructWithEmbeddedView) UnmarshalJSON(b []byte) error {
|
||||
if v.ж != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
var x StructWithEmbedded
|
||||
if err := json.Unmarshal(b, &x); err != nil {
|
||||
return err
|
||||
}
|
||||
v.ж = &x
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v StructWithEmbeddedView) A() StructWithPtrsView { return v.ж.A.View() }
|
||||
func (v StructWithEmbeddedView) StructWithSlices() StructWithSlicesView {
|
||||
return v.ж.StructWithSlices.View()
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _StructWithEmbeddedViewNeedsRegeneration = StructWithEmbedded(struct {
|
||||
A *StructWithPtrs
|
||||
StructWithSlices
|
||||
}{})
|
||||
|
||||
@@ -398,7 +398,7 @@ type maxMsgBuffer [maxMessageSize]byte
|
||||
|
||||
// bufPool holds the temporary buffers for Conn.Read & Write.
|
||||
var bufPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return new(maxMsgBuffer)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/dnsfallback"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tsdial"
|
||||
@@ -54,20 +55,20 @@ import (
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/util/singleflight"
|
||||
"tailscale.com/util/systemd"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
// 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
|
||||
dialer *tsdial.Dialer
|
||||
dnsCache *dnscache.Resolver
|
||||
serverURL string // URL of the tailcontrol server
|
||||
timeNow func() time.Time
|
||||
lastPrintMap time.Time
|
||||
newDecompressor func() (Decompressor, error)
|
||||
keepAlive bool
|
||||
logf logger.Logf
|
||||
linkMon *monitor.Mon // or nil
|
||||
netMon *netmon.Monitor // or nil
|
||||
discoPubKey key.DiscoPublic
|
||||
getMachinePrivKey func() (key.MachinePrivate, error)
|
||||
debugFlags []string
|
||||
@@ -113,7 +114,7 @@ type Options struct {
|
||||
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
|
||||
NoiseTestClient *http.Client // optional HTTP client to use for noise RPCs (tests only)
|
||||
DebugFlags []string // debug settings to send to control
|
||||
LinkMonitor *monitor.Mon // optional link monitor
|
||||
NetMon *netmon.Monitor // optional network monitor
|
||||
PopBrowserURL func(url string) // optional func to open browser
|
||||
OnClientVersion func(*tailcfg.ClientVersion) // optional func to inform GUI of client version status
|
||||
OnControlTime func(time.Time) // optional func to notify callers of new time from control
|
||||
@@ -199,6 +200,14 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
opts.Logf = log.Printf
|
||||
}
|
||||
|
||||
dnsCache := &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward, // use default cache's forwarder
|
||||
UseLastGood: true,
|
||||
LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, opts.NetMon),
|
||||
Logf: opts.Logf,
|
||||
NetMon: opts.NetMon,
|
||||
}
|
||||
|
||||
httpc := opts.HTTPTestClient
|
||||
if httpc == nil && runtime.GOOS == "js" {
|
||||
// In js/wasm, net/http.Transport (as of Go 1.18) will
|
||||
@@ -208,12 +217,6 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
httpc = http.DefaultClient
|
||||
}
|
||||
if httpc == nil {
|
||||
dnsCache := &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward, // use default cache's forwarder
|
||||
UseLastGood: true,
|
||||
LookupIPFallback: dnsfallback.Lookup,
|
||||
Logf: opts.Logf,
|
||||
}
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
||||
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
|
||||
@@ -241,7 +244,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
discoPubKey: opts.DiscoPublicKey,
|
||||
debugFlags: opts.DebugFlags,
|
||||
keepSharerAndUserSplit: opts.KeepSharerAndUserSplit,
|
||||
linkMon: opts.LinkMonitor,
|
||||
netMon: opts.NetMon,
|
||||
skipIPForwardingCheck: opts.SkipIPForwardingCheck,
|
||||
pinger: opts.Pinger,
|
||||
popBrowser: opts.PopBrowserURL,
|
||||
@@ -249,6 +252,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
onControlTime: opts.OnControlTime,
|
||||
c2nHandler: opts.C2NHandler,
|
||||
dialer: opts.Dialer,
|
||||
dnsCache: dnsCache,
|
||||
dialPlan: opts.DialPlan,
|
||||
}
|
||||
if opts.Hostinfo == nil {
|
||||
@@ -871,8 +875,8 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
ReadOnly: readOnly && !allowStream,
|
||||
}
|
||||
var extraDebugFlags []string
|
||||
if hi != nil && c.linkMon != nil && !c.skipIPForwardingCheck &&
|
||||
ipForwardingBroken(hi.RoutableIPs, c.linkMon.InterfaceState()) {
|
||||
if hi != nil && c.netMon != nil && !c.skipIPForwardingCheck &&
|
||||
ipForwardingBroken(hi.RoutableIPs, c.netMon.InterfaceState()) {
|
||||
extraDebugFlags = append(extraDebugFlags, "warn-ip-forwarding-off")
|
||||
}
|
||||
if health.RouterHealth() != nil {
|
||||
@@ -1508,7 +1512,16 @@ func (c *Direct) getNoiseClient() (*NoiseClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.logf("creating new noise client")
|
||||
nc, err := NewNoiseClient(k, serverNoiseKey, c.serverURL, c.dialer, dp)
|
||||
nc, err := NewNoiseClient(NoiseOpts{
|
||||
PrivKey: k,
|
||||
ServerPubKey: serverNoiseKey,
|
||||
ServerURL: c.serverURL,
|
||||
Dialer: c.dialer,
|
||||
DNSCache: c.dnsCache,
|
||||
Logf: c.logf,
|
||||
NetMon: c.netMon,
|
||||
DialPlan: dp,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -19,9 +19,12 @@ import (
|
||||
"golang.org/x/net/http2"
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/control/controlhttp"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/util/singleflight"
|
||||
@@ -156,6 +159,7 @@ type NoiseClient struct {
|
||||
sfDial singleflight.Group[struct{}, *noiseConn]
|
||||
|
||||
dialer *tsdial.Dialer
|
||||
dnsCache *dnscache.Resolver
|
||||
privKey key.MachinePrivate
|
||||
serverPubKey key.MachinePublic
|
||||
host string // the host part of serverURL
|
||||
@@ -167,6 +171,9 @@ type NoiseClient struct {
|
||||
// be nil.
|
||||
dialPlan func() *tailcfg.ControlDialPlan
|
||||
|
||||
logf logger.Logf
|
||||
netMon *netmon.Monitor
|
||||
|
||||
// mu only protects the following variables.
|
||||
mu sync.Mutex
|
||||
last *noiseConn // or nil
|
||||
@@ -174,12 +181,39 @@ type NoiseClient struct {
|
||||
connPool map[int]*noiseConn // active connections not yet closed; see noiseConn.Close
|
||||
}
|
||||
|
||||
// NoiseOpts contains options for the NewNoiseClient function. All fields are
|
||||
// required unless otherwise specified.
|
||||
type NoiseOpts struct {
|
||||
// PrivKey is this node's private key.
|
||||
PrivKey key.MachinePrivate
|
||||
// ServerPubKey is the public key of the server.
|
||||
ServerPubKey key.MachinePublic
|
||||
// ServerURL is the URL of the server to connect to.
|
||||
ServerURL string
|
||||
// Dialer's SystemDial function is used to connect to the server.
|
||||
Dialer *tsdial.Dialer
|
||||
// DNSCache is the caching Resolver to use to connect to the server.
|
||||
//
|
||||
// This field can be nil.
|
||||
DNSCache *dnscache.Resolver
|
||||
// Logf is the log function to use. This field can be nil.
|
||||
Logf logger.Logf
|
||||
// NetMon is the network monitor that, if set, will be used to get the
|
||||
// network interface state. This field can be nil; if so, the current
|
||||
// state will be looked up dynamically.
|
||||
NetMon *netmon.Monitor
|
||||
// DialPlan, if set, is a function that should return an explicit plan
|
||||
// on how to connect to the server.
|
||||
DialPlan func() *tailcfg.ControlDialPlan
|
||||
}
|
||||
|
||||
// NewNoiseClient returns a new noiseClient for the provided server and machine key.
|
||||
// serverURL is of the form https://<host>:<port> (no trailing slash).
|
||||
//
|
||||
// netMon may be nil, if non-nil it's used to do faster interface lookups.
|
||||
// dialPlan may be nil
|
||||
func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string, dialer *tsdial.Dialer, dialPlan func() *tailcfg.ControlDialPlan) (*NoiseClient, error) {
|
||||
u, err := url.Parse(serverURL)
|
||||
func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
|
||||
u, err := url.Parse(opts.ServerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -199,14 +233,18 @@ func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic,
|
||||
httpPort = "80"
|
||||
httpsPort = "443"
|
||||
}
|
||||
|
||||
np := &NoiseClient{
|
||||
serverPubKey: serverPubKey,
|
||||
privKey: privKey,
|
||||
serverPubKey: opts.ServerPubKey,
|
||||
privKey: opts.PrivKey,
|
||||
host: u.Hostname(),
|
||||
httpPort: httpPort,
|
||||
httpsPort: httpsPort,
|
||||
dialer: dialer,
|
||||
dialPlan: dialPlan,
|
||||
dialer: opts.Dialer,
|
||||
dnsCache: opts.DNSCache,
|
||||
dialPlan: opts.DialPlan,
|
||||
logf: opts.Logf,
|
||||
netMon: opts.NetMon,
|
||||
}
|
||||
|
||||
// Create the HTTP/2 Transport using a net/http.Transport
|
||||
@@ -365,7 +403,10 @@ func (nc *NoiseClient) dial() (*noiseConn, error) {
|
||||
ControlKey: nc.serverPubKey,
|
||||
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
|
||||
Dialer: nc.dialer.SystemDial,
|
||||
DNSCache: nc.dnsCache,
|
||||
DialPlan: dialPlan,
|
||||
Logf: nc.logf,
|
||||
NetMon: nc.netMon,
|
||||
}).Dial(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -74,7 +74,12 @@ func (tt noiseClientTest) run(t *testing.T) {
|
||||
defer hs.Close()
|
||||
|
||||
dialer := new(tsdial.Dialer)
|
||||
nc, err := NewNoiseClient(clientPrivate, serverPrivate.Public(), hs.URL, dialer, nil)
|
||||
nc, err := NewNoiseClient(NoiseOpts{
|
||||
PrivKey: clientPrivate,
|
||||
ServerPubKey: serverPrivate.Public(),
|
||||
ServerURL: hs.URL,
|
||||
Dialer: dialer,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -374,6 +374,22 @@ func (a *Dialer) dialURL(ctx context.Context, u *url.URL, addr netip.Addr) (*Cli
|
||||
}, nil
|
||||
}
|
||||
|
||||
// resolver returns a.DNSCache if non-nil or a new *dnscache.Resolver
|
||||
// otherwise.
|
||||
func (a *Dialer) resolver() *dnscache.Resolver {
|
||||
if a.DNSCache != nil {
|
||||
return a.DNSCache
|
||||
}
|
||||
|
||||
return &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward,
|
||||
LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon),
|
||||
UseLastGood: true,
|
||||
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
|
||||
NetMon: a.NetMon,
|
||||
}
|
||||
}
|
||||
|
||||
// tryURLUpgrade connects to u, and tries to upgrade it to a net.Conn. If addr
|
||||
// is valid, then no DNS is used and the connection will be made to the
|
||||
// provided address.
|
||||
@@ -389,14 +405,10 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr,
|
||||
SingleHostStaticResult: []netip.Addr{addr},
|
||||
SingleHost: u.Hostname(),
|
||||
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
|
||||
NetMon: a.NetMon,
|
||||
}
|
||||
} else {
|
||||
dns = &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward,
|
||||
LookupIPFallback: dnsfallback.Lookup,
|
||||
UseLastGood: true,
|
||||
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
|
||||
}
|
||||
dns = a.resolver()
|
||||
}
|
||||
|
||||
var dialer dnscache.DialContextFunc
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -66,10 +67,17 @@ type Dialer struct {
|
||||
// If not specified, this defaults to net.Dialer.DialContext.
|
||||
Dialer dnscache.DialContextFunc
|
||||
|
||||
// DNSCache is the caching Resolver used by this Dialer.
|
||||
//
|
||||
// If not specified, a new Resolver is created per attempt.
|
||||
DNSCache *dnscache.Resolver
|
||||
|
||||
// Logf, if set, is a logging function to use; if unset, logs are
|
||||
// dropped.
|
||||
Logf logger.Logf
|
||||
|
||||
NetMon *netmon.Monitor
|
||||
|
||||
// DialPlan, if set, contains instructions from the control server on
|
||||
// how to connect to it. If present, we will try the methods in this
|
||||
// plan before falling back to DNS.
|
||||
|
||||
61
derp/README.md
Normal file
61
derp/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# DERP
|
||||
|
||||
This directory (and subdirectories) contain the DERP code. The server itself is
|
||||
in `../cmd/derper`.
|
||||
|
||||
DERP is a packet relay system (client and servers) where peers are addressed
|
||||
using WireGuard public keys instead of IP addresses.
|
||||
|
||||
It relays two types of packets:
|
||||
|
||||
* "Disco" discovery messages (see `../disco`) as the a side channel during [NAT
|
||||
traversal](https://tailscale.com/blog/how-nat-traversal-works/).
|
||||
|
||||
* Encrypted WireGuard packets as the fallback of last resort when UDP is blocked
|
||||
or NAT traversal fails.
|
||||
|
||||
## DERP Map
|
||||
|
||||
Each client receives a "[DERP
|
||||
Map](https://pkg.go.dev/tailscale.com/tailcfg#DERPMap)" from the coordination
|
||||
server describing the DERP servers the client should try to use.
|
||||
|
||||
The client picks its home "DERP home" based on latency. This is done to keep
|
||||
costs low by avoid using cloud load balancers (pricey) or anycast, which would
|
||||
necessarily require server-side routing between DERP regions.
|
||||
|
||||
Clients pick their DERP home and report it to the coordination server which
|
||||
shares it to all the peers in the tailnet. When a peer wants to send a packet
|
||||
and it doesn't already have a WireGuard session open, it sends disco messages
|
||||
(some direct, and some over DERP), trying to do the NAT traversal. The client
|
||||
will make connections to multiple DERP regions as needed. Only the DERP home
|
||||
region connection needs to be alive forever.
|
||||
|
||||
## DERP Regions
|
||||
|
||||
Tailscale runs 1 or more DERP nodes (instances of `cmd/derper`) in various
|
||||
geographic regions to make sure users have low latency to their DERP home.
|
||||
|
||||
Regions generally have multiple nodes per region "meshed" (routing to each
|
||||
other) together for redundancy: it allows for cloud failures or upgrades without
|
||||
kicking users out to a higher latency region. Instead, clients will reconnect to
|
||||
the next node in the region. Each node in the region is required to to be meshed
|
||||
with every other node in the region and forward packets to the other nodes in
|
||||
the region. Packets are forwarded only one hop within the region. There is no
|
||||
routing between regions. The assumption is that the mesh TCP connections are
|
||||
over a VPC that's very fast, low latency, and not charged per byte. The
|
||||
coordination server assigns the list of nodes in a region as a function of the
|
||||
tailnet, so all nodes within a tailnet should generally be on the same node and
|
||||
not require forwarding. Only after a failure do clients of a particular tailnet
|
||||
get split between nodes in a region and require inter-node forwarding. But over
|
||||
time it balances back out. There's also an admin-only DERP frame type to force
|
||||
close the TCP connection of a particular client to force them to reconnect to
|
||||
their primary if the operator wants to force things to balance out sooner.
|
||||
(Using the `(*derphttp.Client).ClosePeer` method, as used by Tailscale's
|
||||
internal rarely-used `cmd/derpprune` maintenance tool)
|
||||
|
||||
We generally run a minimum of three nodes in a region not for quorum reasons
|
||||
(there's no voting) but just because two is too uncomfortably few for cascading
|
||||
failure reasons: if you're running two nodes at 51% load (CPU, memory, etc) and
|
||||
then one fails, that makes the second one fail. With three or more nodes, you
|
||||
can run each node a bit hotter.
|
||||
@@ -498,7 +498,7 @@ func (s *Server) registerClient(c *sclient) {
|
||||
switch set := set.(type) {
|
||||
case nil:
|
||||
s.clients[c.key] = singleClient{c}
|
||||
c.debug("register single client")
|
||||
c.debugLogf("register single client")
|
||||
case singleClient:
|
||||
s.dupClientKeys.Add(1)
|
||||
s.dupClientConns.Add(2) // both old and new count
|
||||
@@ -514,7 +514,7 @@ func (s *Server) registerClient(c *sclient) {
|
||||
},
|
||||
sendHistory: []*sclient{old},
|
||||
}
|
||||
c.debug("register duplicate client")
|
||||
c.debugLogf("register duplicate client")
|
||||
case *dupClientSet:
|
||||
s.dupClientConns.Add(1) // the gauge
|
||||
s.dupClientConnTotal.Add(1) // the counter
|
||||
@@ -522,7 +522,7 @@ func (s *Server) registerClient(c *sclient) {
|
||||
set.set[c] = true
|
||||
set.last = c
|
||||
set.sendHistory = append(set.sendHistory, c)
|
||||
c.debug("register another duplicate client")
|
||||
c.debugLogf("register another duplicate client")
|
||||
}
|
||||
|
||||
if _, ok := s.clientsMesh[c.key]; !ok {
|
||||
@@ -555,7 +555,7 @@ func (s *Server) unregisterClient(c *sclient) {
|
||||
case nil:
|
||||
c.logf("[unexpected]; clients map is empty")
|
||||
case singleClient:
|
||||
c.logf("removed connection")
|
||||
c.debugLogf("removed connection")
|
||||
delete(s.clients, c.key)
|
||||
if v, ok := s.clientsMesh[c.key]; ok && v == nil {
|
||||
delete(s.clientsMesh, c.key)
|
||||
@@ -563,7 +563,7 @@ func (s *Server) unregisterClient(c *sclient) {
|
||||
}
|
||||
s.broadcastPeerStateChangeLocked(c.key, false)
|
||||
case *dupClientSet:
|
||||
c.debug("removed duplicate client")
|
||||
c.debugLogf("removed duplicate client")
|
||||
if set.removeClient(c) {
|
||||
s.dupClientConns.Add(-1)
|
||||
} else {
|
||||
@@ -712,9 +712,12 @@ func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, rem
|
||||
if clientInfo != nil {
|
||||
c.info = *clientInfo
|
||||
if envknob.Bool("DERP_PROBER_DEBUG_LOGS") && clientInfo.IsProber {
|
||||
c.debugLogging = true
|
||||
c.debug = true
|
||||
}
|
||||
}
|
||||
if s.debug {
|
||||
c.debug = true
|
||||
}
|
||||
|
||||
s.registerClient(c)
|
||||
defer s.unregisterClient(c)
|
||||
@@ -727,6 +730,12 @@ func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, rem
|
||||
return c.run(ctx)
|
||||
}
|
||||
|
||||
func (s *Server) debugLogf(format string, v ...any) {
|
||||
if s.debug {
|
||||
s.logf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// for testing
|
||||
var (
|
||||
timeSleep = time.Sleep
|
||||
@@ -744,16 +753,20 @@ func (c *sclient) run(ctx context.Context) error {
|
||||
defer func() {
|
||||
cancelSender()
|
||||
if err := grp.Wait(); err != nil && !c.s.isClosed() {
|
||||
c.logf("sender failed: %v", err)
|
||||
if errors.Is(err, context.Canceled) {
|
||||
c.debugLogf("sender canceled by reader exiting")
|
||||
} else {
|
||||
c.logf("sender failed: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
ft, fl, err := readFrameHeader(c.br)
|
||||
c.debug("read frame type %d len %d err %v", ft, fl, err)
|
||||
c.debugLogf("read frame type %d len %d err %v", ft, fl, err)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
c.logf("read EOF")
|
||||
c.debugLogf("read EOF")
|
||||
return nil
|
||||
}
|
||||
if c.s.isClosed() {
|
||||
@@ -910,7 +923,7 @@ func (c *sclient) handleFrameForwardPacket(ft frameType, fl uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
dst.debug("received forwarded packet from %s via %s", srcKey.ShortString(), c.key.ShortString())
|
||||
dst.debugLogf("received forwarded packet from %s via %s", srcKey.ShortString(), c.key.ShortString())
|
||||
|
||||
return c.sendPkt(dst, pkt{
|
||||
bs: contents,
|
||||
@@ -960,7 +973,7 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
|
||||
if fwd != nil {
|
||||
s.packetsForwardedOut.Add(1)
|
||||
err := fwd.ForwardPacket(c.key, dstKey, contents)
|
||||
c.debug("SendPacket for %s, forwarding via %s: %v", dstKey.ShortString(), fwd, err)
|
||||
c.debugLogf("SendPacket for %s, forwarding via %s: %v", dstKey.ShortString(), fwd, err)
|
||||
if err != nil {
|
||||
// TODO:
|
||||
return nil
|
||||
@@ -974,10 +987,10 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
|
||||
c.requestPeerGoneWriteLimited(dstKey, contents, PeerGoneReasonNotHere)
|
||||
}
|
||||
s.recordDrop(contents, c.key, dstKey, reason)
|
||||
c.debug("SendPacket for %s, dropping with reason=%s", dstKey.ShortString(), reason)
|
||||
c.debugLogf("SendPacket for %s, dropping with reason=%s", dstKey.ShortString(), reason)
|
||||
return nil
|
||||
}
|
||||
c.debug("SendPacket for %s, sending directly", dstKey.ShortString())
|
||||
c.debugLogf("SendPacket for %s, sending directly", dstKey.ShortString())
|
||||
|
||||
p := pkt{
|
||||
bs: contents,
|
||||
@@ -987,8 +1000,8 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
|
||||
return c.sendPkt(dst, p)
|
||||
}
|
||||
|
||||
func (c *sclient) debug(format string, v ...any) {
|
||||
if c.debugLogging {
|
||||
func (c *sclient) debugLogf(format string, v ...any) {
|
||||
if c.debug {
|
||||
c.logf(format, v...)
|
||||
}
|
||||
}
|
||||
@@ -1011,7 +1024,8 @@ const (
|
||||
func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, reason dropReason) {
|
||||
s.packetsDropped.Add(1)
|
||||
s.packetsDroppedReasonCounters[reason].Add(1)
|
||||
if disco.LooksLikeDiscoWrapper(packetBytes) {
|
||||
looksDisco := disco.LooksLikeDiscoWrapper(packetBytes)
|
||||
if looksDisco {
|
||||
s.packetsDroppedTypeDisco.Add(1)
|
||||
} else {
|
||||
s.packetsDroppedTypeOther.Add(1)
|
||||
@@ -1024,9 +1038,7 @@ func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, r
|
||||
msg := fmt.Sprintf("drop (%s) %s -> %s", srcKey.ShortString(), reason, dstKey.ShortString())
|
||||
s.limitedLogf(msg)
|
||||
}
|
||||
if s.debug {
|
||||
s.logf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, disco.LooksLikeDiscoWrapper(packetBytes))
|
||||
}
|
||||
s.debugLogf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, looksDisco)
|
||||
}
|
||||
|
||||
func (c *sclient) sendPkt(dst *sclient, p pkt) error {
|
||||
@@ -1044,13 +1056,13 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
|
||||
select {
|
||||
case <-dst.done:
|
||||
s.recordDrop(p.bs, c.key, dstKey, dropReasonGoneDisconnected)
|
||||
dst.debug("sendPkt attempt %d dropped, dst gone", attempt)
|
||||
dst.debugLogf("sendPkt attempt %d dropped, dst gone", attempt)
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case sendQueue <- p:
|
||||
dst.debug("sendPkt attempt %d enqueued", attempt)
|
||||
dst.debugLogf("sendPkt attempt %d enqueued", attempt)
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
@@ -1066,7 +1078,7 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
|
||||
// contended queue with racing writers. Give up and tail-drop in
|
||||
// this case to keep reader unblocked.
|
||||
s.recordDrop(p.bs, c.key, dstKey, dropReasonQueueTail)
|
||||
dst.debug("sendPkt attempt %d dropped, queue full")
|
||||
dst.debugLogf("sendPkt attempt %d dropped, queue full")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1304,8 +1316,7 @@ type sclient struct {
|
||||
canMesh bool // clientInfo had correct mesh token for inter-region routing
|
||||
isDup atomic.Bool // whether more than 1 sclient for key is connected
|
||||
isDisabled atomic.Bool // whether sends to this peer are disabled due to active/active dups
|
||||
|
||||
debugLogging bool
|
||||
debug bool // turn on for verbose logging
|
||||
|
||||
// Owned by run, not thread-safe.
|
||||
br *bufio.Reader
|
||||
@@ -1593,7 +1604,7 @@ func (c *sclient) sendPacket(srcKey key.NodePublic, contents []byte) (err error)
|
||||
c.s.packetsSent.Add(1)
|
||||
c.s.bytesSent.Add(int64(len(contents)))
|
||||
}
|
||||
c.debug("sendPacket from %s: %v", srcKey.ShortString(), err)
|
||||
c.debugLogf("sendPacket from %s: %v", srcKey.ShortString(), err)
|
||||
}()
|
||||
|
||||
c.setWriteDeadline()
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"tailscale.com/derp"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/net/tlsdial"
|
||||
@@ -55,6 +56,7 @@ type Client struct {
|
||||
|
||||
privateKey key.NodePrivate
|
||||
logf logger.Logf
|
||||
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
|
||||
dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// Either url or getRegion is non-nil:
|
||||
@@ -88,11 +90,13 @@ func (c *Client) String() string {
|
||||
|
||||
// NewRegionClient returns a new DERP-over-HTTP client. It connects lazily.
|
||||
// To trigger a connection, use Connect.
|
||||
func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, getRegion func() *tailcfg.DERPRegion) *Client {
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmon.Monitor, getRegion func() *tailcfg.DERPRegion) *Client {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c := &Client{
|
||||
privateKey: privateKey,
|
||||
logf: logf,
|
||||
netMon: netMon,
|
||||
getRegion: getRegion,
|
||||
ctx: ctx,
|
||||
cancelCtx: cancel,
|
||||
@@ -492,7 +496,7 @@ func (c *Client) dialURL(ctx context.Context) (net.Conn, error) {
|
||||
return c.dialer(ctx, "tcp", net.JoinHostPort(host, urlPort(c.url)))
|
||||
}
|
||||
hostOrIP := host
|
||||
dialer := netns.NewDialer(c.logf)
|
||||
dialer := netns.NewDialer(c.logf, c.netMon)
|
||||
|
||||
if c.DNSCache != nil {
|
||||
ip, _, _, err := c.DNSCache.LookupIP(ctx, host)
|
||||
@@ -587,7 +591,7 @@ func (c *Client) DialRegionTLS(ctx context.Context, reg *tailcfg.DERPRegion) (tl
|
||||
}
|
||||
|
||||
func (c *Client) dialContext(ctx context.Context, proto, addr string) (net.Conn, error) {
|
||||
return netns.NewDialer(c.logf).DialContext(ctx, proto, addr)
|
||||
return netns.NewDialer(c.logf, c.netMon).DialContext(ctx, proto, addr)
|
||||
}
|
||||
|
||||
// shouldDialProto reports whether an explicitly provided IPv4 or IPv6
|
||||
|
||||
@@ -457,13 +457,24 @@ var applyDiskConfigErr error
|
||||
// ApplyDiskConfigError returns the most recent result of ApplyDiskConfig.
|
||||
func ApplyDiskConfigError() error { return applyDiskConfigErr }
|
||||
|
||||
// ApplyDiskConfig returns a platform-specific config file of environment keys/values and
|
||||
// applies them. On Linux and Unix operating systems, it's a no-op and always returns nil.
|
||||
// If no platform-specific config file is found, it also returns nil.
|
||||
// ApplyDiskConfig returns a platform-specific config file of environment
|
||||
// keys/values and applies them. On Linux and Unix operating systems, it's a
|
||||
// no-op and always returns nil. If no platform-specific config file is found,
|
||||
// it also returns nil.
|
||||
//
|
||||
// It exists primarily for Windows and macOS to make it easy to apply
|
||||
// environment variables to a running service in a way similar to modifying
|
||||
// /etc/default/tailscaled on Linux.
|
||||
//
|
||||
// It exists primarily for Windows to make it easy to apply environment variables to
|
||||
// a running service in a way similar to modifying /etc/default/tailscaled on Linux.
|
||||
// On Windows, you use %ProgramData%\Tailscale\tailscaled-env.txt instead.
|
||||
//
|
||||
// On macOS, use one of:
|
||||
//
|
||||
// - ~/Library/Containers/io.tailscale.ipn.macsys/Data/tailscaled-env.txt
|
||||
// for standalone macOS GUI builds
|
||||
// - ~/Library/Containers/io.tailscale.ipn.macos.network-extension/Data/tailscaled-env.txt
|
||||
// for App Store builds
|
||||
// - /etc/tailscale/tailscaled-env.txt for tailscaled-on-macOS (homebrew, etc)
|
||||
func ApplyDiskConfig() (err error) {
|
||||
var f *os.File
|
||||
defer func() {
|
||||
@@ -512,9 +523,15 @@ func getPlatformEnvFile() string {
|
||||
return "/etc/tailscale/tailscaled-env.txt"
|
||||
}
|
||||
case "darwin":
|
||||
// TODO(bradfitz): figure this out. There are three ways to run
|
||||
// Tailscale on macOS (tailscaled, GUI App Store, GUI System Extension)
|
||||
// and we should deal with all three.
|
||||
if version.IsSandboxedMacOS() { // the two GUI variants (App Store or separate download)
|
||||
// This will be user-visible as ~/Library/Containers/$VARIANT/Data/tailscaled-env.txt
|
||||
// where $VARIANT is "io.tailscale.ipn.macsys" for macsys (downloadable mac GUI builds)
|
||||
// or "io.tailscale.ipn.macos.network-extension" for App Store builds.
|
||||
return filepath.Join(os.Getenv("HOME"), "tailscaled-env.txt")
|
||||
} else {
|
||||
// Open source / homebrew variable, running tailscaled-on-macOS.
|
||||
return "/etc/tailscale/tailscaled-env.txt"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -115,4 +115,4 @@
|
||||
in
|
||||
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
|
||||
}
|
||||
# nix-direnv cache busting line: sha256-LfHkNXQbg7/u6y882gtINuQtwinYakg3abKJTDrrADo=
|
||||
# nix-direnv cache busting line: sha256-7L+dvS++UNfMVcPUCbK/xuBPwtrzW4RpZTtcl7VCwQs=
|
||||
|
||||
425
go.mod
425
go.mod
@@ -3,200 +3,207 @@ module tailscale.com
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
filippo.io/mkcert v1.4.3
|
||||
github.com/Microsoft/go-winio v0.6.0
|
||||
filippo.io/mkcert v1.4.4
|
||||
github.com/Microsoft/go-winio v0.6.1
|
||||
github.com/akutz/memconn v0.1.0
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
|
||||
github.com/andybalholm/brotli v1.0.3
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.3
|
||||
github.com/aws/aws-sdk-go-v2/config v1.11.0
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.4
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.21.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.35.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.22
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.3
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/creack/pty v1.1.17
|
||||
github.com/dave/jennifer v1.4.1
|
||||
github.com/dblohm7/wingoes v0.0.0-20221124203957-6ac47ab19aa5
|
||||
github.com/creack/pty v1.1.18
|
||||
github.com/dave/jennifer v1.6.1
|
||||
github.com/dblohm7/wingoes v0.0.0-20230426155039-111c8c3b57c8
|
||||
github.com/dsnet/try v0.0.3
|
||||
github.com/evanw/esbuild v0.14.53
|
||||
github.com/frankban/quicktest v1.14.0
|
||||
github.com/frankban/quicktest v1.14.5
|
||||
github.com/fxamacker/cbor/v2 v2.4.0
|
||||
github.com/go-json-experiment/json v0.0.0-20221017203807-c5ed296b8c92
|
||||
github.com/go-json-experiment/json v0.0.0-20230321051131-ccbac49a6929
|
||||
github.com/go-logr/zapr v1.2.3
|
||||
github.com/go-ole/go-ole v1.2.6
|
||||
github.com/godbus/dbus/v5 v5.0.6
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||
github.com/golangci/golangci-lint v1.52.2
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/go-containerregistry v0.9.0
|
||||
github.com/google/go-containerregistry v0.14.0
|
||||
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/goreleaser/nfpm v1.10.3
|
||||
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3
|
||||
github.com/hdevalence/ed25519consensus v0.1.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/illarion/gonotify v1.0.1
|
||||
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
||||
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b
|
||||
github.com/jsimonetti/rtnetlink v1.3.2
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.15.4
|
||||
github.com/klauspost/compress v1.16.5
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/mdlayher/genetlink v1.2.0
|
||||
github.com/mdlayher/netlink v1.7.1
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-isatty v0.0.18
|
||||
github.com/mdlayher/genetlink v1.3.2
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/mdlayher/sdnotify v1.0.0
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/miekg/dns v1.1.54
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/peterbourgon/ff/v3 v3.1.2
|
||||
github.com/peterbourgon/ff/v3 v3.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.4
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/prometheus/common v0.41.0
|
||||
github.com/pkg/sftp v1.13.5
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/prometheus/common v0.42.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20221115211329-17a3db2c30d2
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
|
||||
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
|
||||
github.com/tailscale/mkctr v0.0.0-20220601142259-c0b937af2e89
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
|
||||
github.com/tailscale/wireguard-go v0.0.0-20230410165232-af172621b4dd
|
||||
github.com/tc-hib/winres v0.1.6
|
||||
github.com/tc-hib/winres v0.2.0
|
||||
github.com/tcnksm/go-httpstat v0.2.0
|
||||
github.com/toqueteos/webbrowser v1.2.0
|
||||
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
|
||||
go.uber.org/zap v1.21.0
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94
|
||||
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
|
||||
golang.org/x/crypto v0.6.0
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
|
||||
golang.org/x/mod v0.7.0
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.5.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.5.1-0.20230222185716-a3b23cc77e89
|
||||
golang.org/x/term v0.5.0
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
|
||||
golang.org/x/tools v0.5.0
|
||||
github.com/u-root/u-root v0.11.0
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
go.uber.org/zap v1.24.0
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13
|
||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
|
||||
golang.org/x/crypto v0.8.0
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
golang.org/x/mod v0.10.0
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/sync v0.2.0
|
||||
golang.org/x/sys v0.8.0
|
||||
golang.org/x/term v0.7.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.8.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
gvisor.dev/gvisor v0.0.0-20230328175328-162ed5ef888d
|
||||
honnef.co/go/tools v0.4.2
|
||||
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f
|
||||
honnef.co/go/tools v0.4.3
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
|
||||
inet.af/wf v0.0.0-20220728202103-50d96caab2f6
|
||||
k8s.io/api v0.25.0
|
||||
k8s.io/apimachinery v0.25.0
|
||||
k8s.io/client-go v0.25.0
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884
|
||||
k8s.io/api v0.26.1
|
||||
k8s.io/apimachinery v0.26.1
|
||||
k8s.io/client-go v0.26.1
|
||||
nhooyr.io/websocket v1.8.7
|
||||
sigs.k8s.io/controller-runtime v0.13.1
|
||||
sigs.k8s.io/controller-runtime v0.14.6
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gochecknoglobals v0.1.0 // indirect
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
|
||||
github.com/Antonboom/errname v0.1.5 // indirect
|
||||
github.com/Antonboom/nilnil v0.1.0 // indirect
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/Abirdcfly/dupword v0.0.11 // indirect
|
||||
github.com/Antonboom/errname v0.1.9 // indirect
|
||||
github.com/Antonboom/nilnil v0.1.4 // indirect
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/Djarvur/go-err113 v0.1.0 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/OpenPeeDeeP/depguard v1.1.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.2.0 // indirect
|
||||
github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.6.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.5.1 // indirect
|
||||
github.com/ashanbrown/makezero v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.0 // indirect
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||
github.com/blizzy78/varnamelen v0.5.0 // indirect
|
||||
github.com/bombsimon/wsl/v3 v3.3.0 // indirect
|
||||
github.com/breml/bidichk v0.2.1 // indirect
|
||||
github.com/butuzov/ireturn v0.1.1 // indirect
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v3 v3.4.0 // indirect
|
||||
github.com/breml/bidichk v0.2.4 // indirect
|
||||
github.com/breml/errchkjson v0.3.1 // indirect
|
||||
github.com/butuzov/ireturn v0.2.0 // indirect
|
||||
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/charithe/durationcheck v0.0.9 // indirect
|
||||
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
|
||||
github.com/daixiang0/gci v0.2.9 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/curioswitch/go-reassign v0.2.0 // indirect
|
||||
github.com/daixiang0/gci v0.10.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingajkin/go-header v0.4.2 // indirect
|
||||
github.com/docker/cli v20.10.16+incompatible // indirect
|
||||
github.com/denis-tingaikin/go-header v0.4.3 // indirect
|
||||
github.com/docker/cli v23.0.5+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.16+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/esimonov/ifshort v1.0.3 // indirect
|
||||
github.com/docker/docker v23.0.5+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/esimonov/ifshort v1.0.4 // indirect
|
||||
github.com/ettle/strcase v0.1.1 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fzipp/gocyclo v0.3.1 // indirect
|
||||
github.com/gliderlabs/ssh v0.3.3 // indirect
|
||||
github.com/go-critic/go-critic v0.6.1 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/go-critic/go-critic v0.8.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.4.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/go-toolsmith/astcast v1.0.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.0.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.0.1 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.0.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.0.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.0.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.0.2 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.6.1 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
|
||||
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a // indirect
|
||||
github.com/golangci/golangci-lint v1.43.0 // indirect
|
||||
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect
|
||||
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
|
||||
github.com/golangci/misspell v0.3.5 // indirect
|
||||
github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2 // indirect
|
||||
github.com/golangci/misspell v0.4.0 // indirect
|
||||
github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect
|
||||
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 // indirect
|
||||
github.com/goreleaser/chglog v0.1.2 // indirect
|
||||
github.com/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
|
||||
github.com/goreleaser/chglog v0.4.2 // indirect
|
||||
github.com/goreleaser/fileglob v0.3.1 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.4.2 // indirect
|
||||
@@ -204,10 +211,12 @@ require (
|
||||
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jgautheron/goconst v1.5.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
@@ -215,116 +224,134 @@ require (
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/julz/importas v0.0.0-20210922140945-27e0a5d4dee2 // indirect
|
||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||
github.com/kisielk/errcheck v1.6.0 // indirect
|
||||
github.com/julz/importas v0.1.0 // indirect
|
||||
github.com/junk1tm/musttag v0.5.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/kisielk/errcheck v1.6.3 // indirect
|
||||
github.com/kisielk/gotool v1.0.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kulti/thelper v0.4.0 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.3 // indirect
|
||||
github.com/kyoh86/exportloopref v0.1.8 // indirect
|
||||
github.com/ldez/gomoddirectives v0.2.2 // indirect
|
||||
github.com/ldez/tagliatelle v0.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/maratori/testpackage v1.0.1 // indirect
|
||||
github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.6 // indirect
|
||||
github.com/kyoh86/exportloopref v0.1.11 // indirect
|
||||
github.com/ldez/gomoddirectives v0.2.3 // indirect
|
||||
github.com/ldez/tagliatelle v0.5.0 // indirect
|
||||
github.com/leonklingele/grouper v1.1.1 // indirect
|
||||
github.com/lufeee/execinquery v1.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
|
||||
github.com/mgechev/revive v1.1.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/mgechev/revive v1.3.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/moricho/tparallel v0.2.1 // indirect
|
||||
github.com/moricho/tparallel v0.3.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/nishanths/exhaustive v0.7.11 // indirect
|
||||
github.com/nishanths/predeclared v0.2.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.10.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.11.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/onsi/gomega v1.20.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v0.0.0-20211125173453-6d6d39c5bb8b // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.4.1 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.3.13 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.3.19 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.2.3 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.3.0 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
|
||||
github.com/sassoftware/go-rpmutils v0.1.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.9.3 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect
|
||||
github.com/sassoftware/go-rpmutils v0.2.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.15.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/sivchari/tenv v1.4.7 // indirect
|
||||
github.com/sonatard/noctx v0.0.1 // indirect
|
||||
github.com/sourcegraph/go-diff v0.6.1 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cobra v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sivchari/nosnakecase v1.7.0 // indirect
|
||||
github.com/sivchari/tenv v1.7.1 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/sonatard/noctx v0.0.2 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.9.0 // indirect
|
||||
github.com/spf13/viper v1.15.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stretchr/objx v0.4.0 // indirect
|
||||
github.com/stretchr/testify v1.8.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/sylvia7788/contextcheck v1.0.4 // indirect
|
||||
github.com/tdakkota/asciicheck v0.1.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.2 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
|
||||
github.com/tdakkota/asciicheck v0.2.0 // indirect
|
||||
github.com/tetafro/godot v1.4.11 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.4.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
|
||||
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/ultraware/funlen v0.0.3 // indirect
|
||||
github.com/ultraware/whitespace v0.0.4 // indirect
|
||||
github.com/uudashr/gocognit v1.0.5 // indirect
|
||||
github.com/ultraware/whitespace v0.0.5 // indirect
|
||||
github.com/uudashr/gocognit v1.0.6 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||
github.com/yeya24/promlinter v0.1.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.2.0 // indirect
|
||||
gitlab.com/bosi/decorder v0.2.3 // indirect
|
||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect
|
||||
golang.org/x/image v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.25.0 // indirect
|
||||
k8s.io/component-base v0.25.0 // indirect
|
||||
k8s.io/klog/v2 v2.70.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
mvdan.cc/gofumpt v0.2.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.26.1 // indirect
|
||||
k8s.io/component-base v0.26.1 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
mvdan.cc/gofumpt v0.5.0 // indirect
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
|
||||
mvdan.cc/unparam v0.0.0-20211002134041-24922b6997ca // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
sha256-LfHkNXQbg7/u6y882gtINuQtwinYakg3abKJTDrrADo=
|
||||
sha256-7L+dvS++UNfMVcPUCbK/xuBPwtrzW4RpZTtcl7VCwQs=
|
||||
|
||||
@@ -405,7 +405,7 @@ func DisabledEtcAptSource() bool {
|
||||
return false
|
||||
}
|
||||
mod := fi.ModTime()
|
||||
if c, ok := etcAptSrcCache.Load().(etcAptSrcResult); ok && c.mod == mod {
|
||||
if c, ok := etcAptSrcCache.Load().(etcAptSrcResult); ok && c.mod.Equal(mod) {
|
||||
return c.disabled
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
|
||||
@@ -95,6 +95,8 @@ func linuxVersionMeta() (meta versionMeta) {
|
||||
propFile = "/etc.defaults/VERSION"
|
||||
case distro.OpenWrt:
|
||||
propFile = "/etc/openwrt_release"
|
||||
case distro.Unraid:
|
||||
propFile = "/etc/unraid-version"
|
||||
case distro.WDMyCloud:
|
||||
slurp, _ := os.ReadFile("/etc/version")
|
||||
meta.DistroVersion = string(bytes.TrimSpace(slurp))
|
||||
@@ -153,6 +155,8 @@ func linuxVersionMeta() (meta versionMeta) {
|
||||
meta.DistroVersion = m["productversion"]
|
||||
case distro.OpenWrt:
|
||||
meta.DistroVersion = m["DISTRIB_RELEASE"]
|
||||
case distro.Unraid:
|
||||
meta.DistroVersion = m["version"]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package tooldeps
|
||||
|
||||
import (
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
_ "github.com/tailscale/depaware/depaware"
|
||||
_ "golang.org/x/tools/cmd/goimports"
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/goroutines"
|
||||
@@ -94,7 +95,8 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
b.sockstatLogger.Flush()
|
||||
fmt.Fprintln(w, b.sockstatLogger.LogID())
|
||||
fmt.Fprintf(w, "logid: %s\n", b.sockstatLogger.LogID())
|
||||
fmt.Fprintf(w, "debug info: %v\n", sockstats.DebugInfo())
|
||||
default:
|
||||
http.Error(w, "unknown c2n path", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/atomicfile"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/hostinfo"
|
||||
@@ -361,17 +362,16 @@ func (b *LocalBackend) getCertPEM(ctx context.Context, cs certStore, logf logger
|
||||
}
|
||||
key := "_acme-challenge." + domain
|
||||
|
||||
// Do a best-effort lookup to see if we've already created this DNS name
|
||||
// in a previous attempt. Don't burn too much time on it, though. Worst
|
||||
// case we ask the server to create something that already exists.
|
||||
var resolver net.Resolver
|
||||
var ok bool
|
||||
txts, _ := resolver.LookupTXT(ctx, key)
|
||||
for _, txt := range txts {
|
||||
if txt == rec {
|
||||
ok = true
|
||||
logf("TXT record already existed")
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
lookupCtx, lookupCancel := context.WithTimeout(ctx, 500*time.Millisecond)
|
||||
txts, _ := resolver.LookupTXT(lookupCtx, key)
|
||||
lookupCancel()
|
||||
if slices.Contains(txts, rec) {
|
||||
logf("TXT record already existed")
|
||||
} else {
|
||||
logf("starting SetDNS call...")
|
||||
err = b.SetDNS(ctx, key, rec)
|
||||
if err != nil {
|
||||
|
||||
@@ -158,7 +158,7 @@ func (em *expiryManager) nextPeerExpiry(nm *netmap.NetworkMap, localNow time.Tim
|
||||
// nextExpiry being zero is a sentinel that we haven't yet set
|
||||
// an expiry; otherwise, only update if this node's expiry is
|
||||
// sooner than the currently-stored one (since we want the
|
||||
// soonest-occuring expiry time).
|
||||
// soonest-occurring expiry time).
|
||||
if nextExpiry.IsZero() || peer.KeyExpiry.Before(nextExpiry) {
|
||||
nextExpiry = peer.KeyExpiry
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ func TestNextPeerExpiry(t *testing.T) {
|
||||
em := newExpiryManager(t.Logf)
|
||||
em.timeNow = func() time.Time { return now }
|
||||
got := em.nextPeerExpiry(tt.netmap, now)
|
||||
if got != tt.want {
|
||||
if !got.Equal(tt.want) {
|
||||
t.Errorf("got %q, want %q", got.Format(time.RFC3339), tt.want.Format(time.RFC3339))
|
||||
} else if !got.IsZero() && got.Before(now) {
|
||||
t.Errorf("unexpectedly got expiry %q before now %q", got.Format(time.RFC3339), now.Format(time.RFC3339))
|
||||
@@ -269,7 +269,7 @@ func TestNextPeerExpiry(t *testing.T) {
|
||||
}
|
||||
got := em.nextPeerExpiry(nm, now)
|
||||
want := now.Add(30 * time.Second)
|
||||
if got != want {
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("got %q, want %q", got.Format(time.RFC3339), want.Format(time.RFC3339))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -60,6 +60,7 @@ import (
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/empty"
|
||||
"tailscale.com/types/key"
|
||||
@@ -137,16 +138,17 @@ type LocalBackend struct {
|
||||
logf logger.Logf // general logging
|
||||
keyLogf logger.Logf // for printing list of peers on change
|
||||
statsLogf logger.Logf // for printing peers stats on change
|
||||
e wgengine.Engine
|
||||
sys *tsd.System
|
||||
e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys
|
||||
pm *profileManager
|
||||
store ipn.StateStore
|
||||
dialer *tsdial.Dialer // non-nil
|
||||
store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys
|
||||
dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys
|
||||
backendLogID logid.PublicID
|
||||
unregisterLinkMon func()
|
||||
unregisterNetMon func()
|
||||
unregisterHealthWatch func()
|
||||
portpoll *portlist.Poller // may be nil
|
||||
portpollOnce sync.Once // guards starting readPoller
|
||||
gotPortPollRes chan struct{} // closed upon first readPoller result
|
||||
portpoll chan portlist.Update // may be nil
|
||||
portpollOnce sync.Once // guards starting readPoller
|
||||
gotPortPollRes chan struct{} // closed upon first readPoller result
|
||||
newDecompressor func() (controlclient.Decompressor, error)
|
||||
varRoot string // or empty if SetVarRoot never called
|
||||
logFlushFunc func() // or nil if SetLogFlusher wasn't called
|
||||
@@ -267,10 +269,10 @@ type clientGen func(controlclient.Options) (controlclient.Client, error)
|
||||
// but is not actually running.
|
||||
//
|
||||
// If dialer is nil, a new one is made.
|
||||
func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStore, dialer *tsdial.Dialer, e wgengine.Engine, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
|
||||
if e == nil {
|
||||
panic("ipn.NewLocalBackend: engine must not be nil")
|
||||
}
|
||||
func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
|
||||
e := sys.Engine.Get()
|
||||
store := sys.StateStore.Get()
|
||||
dialer := sys.Dialer.Get()
|
||||
|
||||
pm, err := newProfileManager(store, logf)
|
||||
if err != nil {
|
||||
@@ -290,7 +292,8 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
|
||||
osshare.SetFileSharingEnabled(false, logf)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
portpoll, err := portlist.NewPoller()
|
||||
var p portlist.Poller
|
||||
portUpdates, err := p.Run(ctx)
|
||||
if err != nil {
|
||||
logf("skipping portlist: %s", err)
|
||||
}
|
||||
@@ -301,19 +304,21 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
|
||||
logf: logf,
|
||||
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
|
||||
statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
|
||||
sys: sys,
|
||||
e: e,
|
||||
pm: pm,
|
||||
store: store,
|
||||
dialer: dialer,
|
||||
store: store,
|
||||
pm: pm,
|
||||
backendLogID: logID,
|
||||
state: ipn.NoState,
|
||||
portpoll: portpoll,
|
||||
portpoll: portUpdates,
|
||||
em: newExpiryManager(logf),
|
||||
gotPortPollRes: make(chan struct{}),
|
||||
loginFlags: loginFlags,
|
||||
}
|
||||
|
||||
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID)
|
||||
netMon := sys.NetMon.Get()
|
||||
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon)
|
||||
if err != nil {
|
||||
log.Printf("error setting up sockstat logger: %v", err)
|
||||
}
|
||||
@@ -330,23 +335,17 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
|
||||
b.statusChanged = sync.NewCond(&b.statusLock)
|
||||
b.e.SetStatusCallback(b.setWgengineStatus)
|
||||
|
||||
linkMon := e.GetLinkMonitor()
|
||||
b.prevIfState = linkMon.InterfaceState()
|
||||
b.prevIfState = netMon.InterfaceState()
|
||||
// Call our linkChange code once with the current state, and
|
||||
// then also whenever it changes:
|
||||
b.linkChange(false, linkMon.InterfaceState())
|
||||
b.unregisterLinkMon = linkMon.RegisterChangeCallback(b.linkChange)
|
||||
b.linkChange(false, netMon.InterfaceState())
|
||||
b.unregisterNetMon = netMon.RegisterChangeCallback(b.linkChange)
|
||||
|
||||
b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange)
|
||||
|
||||
wiredPeerAPIPort := false
|
||||
if ig, ok := e.(wgengine.InternalsGetter); ok {
|
||||
if tunWrap, _, _, ok := ig.GetInternals(); ok {
|
||||
tunWrap.PeerAPIPort = b.GetPeerAPIPort
|
||||
wiredPeerAPIPort = true
|
||||
}
|
||||
}
|
||||
if !wiredPeerAPIPort {
|
||||
if tunWrap, ok := b.sys.Tun.GetOK(); ok {
|
||||
tunWrap.PeerAPIPort = b.GetPeerAPIPort
|
||||
} else {
|
||||
b.logf("[unexpected] failed to wire up PeerAPI port for engine %T", e)
|
||||
}
|
||||
|
||||
@@ -438,7 +437,7 @@ func (b *LocalBackend) SetComponentDebugLogging(component string, until time.Tim
|
||||
// unchanged when the timer actually fires.
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if ls := b.componentLogUntil[component]; ls.until == until {
|
||||
if ls := b.componentLogUntil[component]; ls.until.Equal(until) {
|
||||
setEnabled(false)
|
||||
b.logf("debugging logging for component %q disabled (by timer)", component)
|
||||
}
|
||||
@@ -464,6 +463,7 @@ func (b *LocalBackend) GetComponentDebugLogging(component string) time.Time {
|
||||
}
|
||||
|
||||
// Dialer returns the backend's dialer.
|
||||
// It is always non-nil.
|
||||
func (b *LocalBackend) Dialer() *tsdial.Dialer {
|
||||
return b.dialer
|
||||
}
|
||||
@@ -499,7 +499,7 @@ func (b *LocalBackend) maybePauseControlClientLocked() {
|
||||
b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || !networkUp)
|
||||
}
|
||||
|
||||
// linkChange is our link monitor callback, called whenever the network changes.
|
||||
// linkChange is our network monitor callback, called whenever the network changes.
|
||||
// major is whether ifst is different than earlier.
|
||||
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
|
||||
b.mu.Lock()
|
||||
@@ -576,7 +576,7 @@ func (b *LocalBackend) Shutdown() {
|
||||
b.sockstatLogger.Shutdown()
|
||||
}
|
||||
|
||||
b.unregisterLinkMon()
|
||||
b.unregisterNetMon()
|
||||
b.unregisterHealthWatch()
|
||||
if cc != nil {
|
||||
cc.Shutdown()
|
||||
@@ -644,7 +644,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
|
||||
defer b.mu.Unlock()
|
||||
sb.MutateStatus(func(s *ipnstate.Status) {
|
||||
s.Version = version.Long()
|
||||
s.TUN = !wgengine.IsNetstack(b.e)
|
||||
s.TUN = !b.sys.IsNetstack()
|
||||
s.BackendState = b.state.String()
|
||||
s.AuthURL = b.authURLSticky
|
||||
if err := health.OverallError(); err != nil {
|
||||
@@ -1077,7 +1077,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
|
||||
b.e.SetDERPMap(st.NetMap.DERPMap)
|
||||
|
||||
// Update our cached DERP map
|
||||
dnsfallback.UpdateCache(st.NetMap.DERPMap)
|
||||
dnsfallback.UpdateCache(st.NetMap.DERPMap, b.logf)
|
||||
|
||||
b.send(ipn.Notify{NetMap: st.NetMap})
|
||||
}
|
||||
@@ -1315,8 +1315,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
hostinfo := hostinfo.New()
|
||||
hostinfo.BackendLogID = b.backendLogID.String()
|
||||
hostinfo.FrontendLogID = opts.FrontendLogID
|
||||
hostinfo.Userspace.Set(wgengine.IsNetstack(b.e))
|
||||
hostinfo.UserspaceRouter.Set(wgengine.IsNetstackRouter(b.e))
|
||||
hostinfo.Userspace.Set(b.sys.IsNetstack())
|
||||
hostinfo.UserspaceRouter.Set(b.sys.IsNetstackRouter())
|
||||
|
||||
if b.cc != nil {
|
||||
// TODO(apenwarr): avoid the need to reinit controlclient.
|
||||
@@ -1378,7 +1378,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
|
||||
if b.portpoll != nil {
|
||||
b.portpollOnce.Do(func() {
|
||||
go b.portpoll.Run(b.ctx)
|
||||
go b.readPoller()
|
||||
|
||||
// Give the poller a second to get results to
|
||||
@@ -1401,7 +1400,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
|
||||
var err error
|
||||
|
||||
isNetstack := wgengine.IsNetstackRouter(b.e)
|
||||
isNetstack := b.sys.IsNetstackRouter()
|
||||
debugFlags := controlDebugFlags
|
||||
if isNetstack {
|
||||
debugFlags = append([]string{"netstack"}, debugFlags...)
|
||||
@@ -1423,7 +1422,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
HTTPTestClient: httpTestClient,
|
||||
DiscoPublicKey: discoPublic,
|
||||
DebugFlags: debugFlags,
|
||||
LinkMonitor: b.e.GetLinkMonitor(),
|
||||
NetMon: b.sys.NetMon.Get(),
|
||||
Pinger: b,
|
||||
PopBrowserURL: b.tellClientToBrowseToURL,
|
||||
OnClientVersion: b.onClientVersion,
|
||||
@@ -1815,12 +1814,17 @@ func dnsMapsEqual(new, old *netmap.NetworkMap) bool {
|
||||
func (b *LocalBackend) readPoller() {
|
||||
n := 0
|
||||
for {
|
||||
ports, ok := <-b.portpoll.Updates()
|
||||
update, ok := <-b.portpoll
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if update.Err() != nil {
|
||||
// TODO(marwan-at-work): do we need to log this?
|
||||
// TODO(marwan-at-work): should we return or keep trying?
|
||||
return
|
||||
}
|
||||
sl := []tailcfg.Service{}
|
||||
for _, p := range ports {
|
||||
for _, p := range update.List() {
|
||||
s := tailcfg.Service{
|
||||
Proto: tailcfg.ServiceProto(p.Proto),
|
||||
Port: p.Port,
|
||||
@@ -2479,7 +2483,7 @@ func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret ipn.EngineSt
|
||||
// [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.statsLogf("[v1] v%v peers: %v", version.Long(), strings.TrimSpace(peerStats.String()))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -3317,14 +3321,12 @@ func (b *LocalBackend) initPeerAPIListener() {
|
||||
directFileMode: b.directFileRoot != "",
|
||||
directFileDoFinalRename: b.directFileDoFinalRename,
|
||||
}
|
||||
if re, ok := b.e.(wgengine.ResolvingEngine); ok {
|
||||
if r, ok := re.GetResolver(); ok {
|
||||
ps.resolver = r
|
||||
}
|
||||
if dm, ok := b.sys.DNSManager.GetOK(); ok {
|
||||
ps.resolver = dm.Resolver()
|
||||
}
|
||||
b.peerAPIServer = ps
|
||||
|
||||
isNetstack := wgengine.IsNetstack(b.e)
|
||||
isNetstack := b.sys.IsNetstack()
|
||||
for i, a := range b.netMap.Addresses {
|
||||
var ln net.Listener
|
||||
var err error
|
||||
@@ -3630,6 +3632,19 @@ func (b *LocalBackend) hasNodeKey() bool {
|
||||
return p.Valid() && p.Persist().Valid() && !p.Persist().PrivateNodeKey().IsZero()
|
||||
}
|
||||
|
||||
// NodeKey returns the public node key.
|
||||
func (b *LocalBackend) NodeKey() key.NodePublic {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
p := b.pm.CurrentPrefs()
|
||||
if !p.Valid() || !p.Persist().Valid() || p.Persist().PrivateNodeKey().IsZero() {
|
||||
return key.NodePublic{}
|
||||
}
|
||||
|
||||
return p.Persist().PublicNodeKey()
|
||||
}
|
||||
|
||||
// nextState returns the state the backend seems to be in, based on
|
||||
// its internal state.
|
||||
func (b *LocalBackend) nextState() ipn.State {
|
||||
@@ -4040,7 +4055,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
|
||||
b.setServeProxyHandlersLocked()
|
||||
|
||||
// don't listen on netmap addresses if we're in userspace mode
|
||||
if !wgengine.IsNetstack(b.e) {
|
||||
if !b.sys.IsNetstack() {
|
||||
b.updateServeTCPPortNetMapAddrListenersLocked(servePorts)
|
||||
}
|
||||
}
|
||||
@@ -4391,7 +4406,7 @@ func nodeIP(n *tailcfg.Node, pred func(netip.Addr) bool) netip.Addr {
|
||||
}
|
||||
|
||||
func (b *LocalBackend) CheckIPForwarding() error {
|
||||
if wgengine.IsNetstackRouter(b.e) {
|
||||
if b.sys.IsNetstackRouter() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4537,13 +4552,9 @@ func (b *LocalBackend) DebugReSTUN() error {
|
||||
}
|
||||
|
||||
func (b *LocalBackend) magicConn() (*magicsock.Conn, error) {
|
||||
ig, ok := b.e.(wgengine.InternalsGetter)
|
||||
mc, ok := b.sys.MagicSock.GetOK()
|
||||
if !ok {
|
||||
return nil, errors.New("engine isn't InternalsGetter")
|
||||
}
|
||||
_, mc, _, ok := ig.GetInternals()
|
||||
if !ok {
|
||||
return nil, errors.New("failed to get internals")
|
||||
return nil, errors.New("failed to get magicsock from sys")
|
||||
}
|
||||
return mc, nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -501,13 +502,16 @@ func TestLazyMachineKeyGeneration(t *testing.T) {
|
||||
tstest.Replace(t, &panicOnMachineKeyGeneration, func() bool { return true })
|
||||
|
||||
var logf logger.Logf = logger.Discard
|
||||
sys := new(tsd.System)
|
||||
store := new(mem.Store)
|
||||
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||
sys.Set(store)
|
||||
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
|
||||
if err != nil {
|
||||
t.Fatalf("NewFakeUserspaceEngine: %v", err)
|
||||
}
|
||||
t.Cleanup(eng.Close)
|
||||
lb, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, eng, 0)
|
||||
sys.Set(eng)
|
||||
lb, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("NewLocalBackend: %v", err)
|
||||
}
|
||||
@@ -765,13 +769,16 @@ func TestPacketFilterPermitsUnlockedNodes(t *testing.T) {
|
||||
func TestStatusWithoutPeers(t *testing.T) {
|
||||
logf := tstest.WhileTestRunningLogger(t)
|
||||
store := new(testStateStorage)
|
||||
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||
sys := new(tsd.System)
|
||||
sys.Set(store)
|
||||
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
|
||||
if err != nil {
|
||||
t.Fatalf("NewFakeUserspaceEngine: %v", err)
|
||||
}
|
||||
sys.Set(e)
|
||||
t.Cleanup(e.Close)
|
||||
|
||||
b, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, e, 0)
|
||||
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("NewLocalBackend: %v", err)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -47,14 +48,17 @@ func TestLocalLogLines(t *testing.T) {
|
||||
idA := logid(0xaa)
|
||||
|
||||
// set up a LocalBackend, super bare bones. No functional data.
|
||||
sys := new(tsd.System)
|
||||
store := new(mem.Store)
|
||||
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||
sys.Set(store)
|
||||
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(e.Close)
|
||||
sys.Set(e)
|
||||
|
||||
lb, err := NewLocalBackend(logf, idA, store, nil, e, 0)
|
||||
lb, err := NewLocalBackend(logf, idA, sys, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -875,7 +875,7 @@ func TestTKAForceDisable(t *testing.T) {
|
||||
}
|
||||
|
||||
if b.tka != nil {
|
||||
t.Fatal("tka was re-initalized")
|
||||
t.Fatal("tka was re-initialized")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/version/distro"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
@@ -468,7 +468,7 @@ func (s *peerAPIServer) listen(ip netip.Addr, ifState *interfaces.State) (ln net
|
||||
}
|
||||
}
|
||||
|
||||
if wgengine.IsNetstack(s.b.e) {
|
||||
if s.b.sys.IsNetstack() {
|
||||
ipStr = ""
|
||||
}
|
||||
|
||||
@@ -605,6 +605,16 @@ func (h *peerAPIHandler) logf(format string, a ...any) {
|
||||
h.ps.b.logf("peerapi: "+format, a...)
|
||||
}
|
||||
|
||||
// isAddressValid reports whether addr is a valid destination address for this
|
||||
// node originating from the peer.
|
||||
func (h *peerAPIHandler) isAddressValid(addr netip.Addr) bool {
|
||||
if h.peerNode.SelfNodeV4MasqAddrForThisPeer != nil {
|
||||
return *h.peerNode.SelfNodeV4MasqAddrForThisPeer == addr
|
||||
}
|
||||
pfx := netip.PrefixFrom(addr, addr.BitLen())
|
||||
return slices.Contains(h.selfNode.Addresses, pfx)
|
||||
}
|
||||
|
||||
func (h *peerAPIHandler) validateHost(r *http.Request) error {
|
||||
if r.Host == "peer" {
|
||||
return nil
|
||||
@@ -613,9 +623,8 @@ func (h *peerAPIHandler) validateHost(r *http.Request) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostIPPfx := netip.PrefixFrom(ap.Addr(), ap.Addr().BitLen())
|
||||
if !slices.Contains(h.selfNode.Addresses, hostIPPfx) {
|
||||
return fmt.Errorf("%v not found in self addresses", hostIPPfx)
|
||||
if !h.isAddressValid(ap.Addr()) {
|
||||
return fmt.Errorf("%v not found in self addresses", ap.Addr())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -947,6 +956,12 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req
|
||||
fmt.Fprintln(w, "</tfoot>")
|
||||
|
||||
fmt.Fprintln(w, "</table>")
|
||||
|
||||
fmt.Fprintln(w, "<h2>Debug Info</h2>")
|
||||
|
||||
fmt.Fprintln(w, "<pre>")
|
||||
fmt.Fprintln(w, html.EscapeString(sockstats.DebugInfo()))
|
||||
fmt.Fprintln(w, "</pre>")
|
||||
}
|
||||
|
||||
type incomingFile struct {
|
||||
@@ -1075,6 +1090,10 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, errNoTaildrop.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if distro.Get() == distro.Unraid && !h.ps.directFileMode {
|
||||
http.Error(w, "Taildrop folder not configured or accessible", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rawPath := r.URL.EscapedPath()
|
||||
suffix, ok := strings.CutPrefix(rawPath, "/v0/put/")
|
||||
if !ok {
|
||||
@@ -1219,12 +1238,9 @@ func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Req
|
||||
http.Error(w, "denied; no debug access", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
eng := h.ps.b.e
|
||||
if ig, ok := eng.(wgengine.InternalsGetter); ok {
|
||||
if _, mc, _, ok := ig.GetInternals(); ok {
|
||||
mc.ServeHTTPDebug(w, r)
|
||||
return
|
||||
}
|
||||
if mc, ok := h.ps.b.sys.MagicSock.GetOK(); ok {
|
||||
mc.ServeHTTPDebug(w, r)
|
||||
return
|
||||
}
|
||||
http.Error(w, "miswired", 500)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"tailscale.com/util/winutil"
|
||||
)
|
||||
|
||||
var errAlreadyMigrated = errors.New("profile migration already completed")
|
||||
|
||||
// profileManager is a wrapper around a StateStore that manages
|
||||
// multiple profiles and the current profile.
|
||||
type profileManager struct {
|
||||
@@ -66,7 +68,7 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
|
||||
b, err := pm.store.ReadState(ipn.CurrentProfileKey(string(uid)))
|
||||
if err == ipn.ErrStateNotExist || len(b) == 0 {
|
||||
if runtime.GOOS == "windows" {
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil {
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil && !errors.Is(err, errAlreadyMigrated) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -544,7 +546,14 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, goos stri
|
||||
if err := pm.setPrefsLocked(prefs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(knownProfiles) == 0 && goos != "windows" {
|
||||
// Most platform behavior is controlled by the goos parameter, however
|
||||
// some behavior is implied by build tag and fails when run on Windows,
|
||||
// so we explicitly avoid that behavior when running on Windows.
|
||||
// Specifically this reaches down into legacy preference loading that is
|
||||
// specialized by profiles_windows.go and fails in tests on an invalid
|
||||
// uid passed in from the unix tests. The uid's used for Windows tests
|
||||
// and runtime must be valid Windows security identifier structures.
|
||||
} else if len(knownProfiles) == 0 && goos != "windows" && runtime.GOOS != "windows" {
|
||||
// No known profiles, try a migration.
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil {
|
||||
return nil, err
|
||||
@@ -562,7 +571,7 @@ func (pm *profileManager) migrateFromLegacyPrefs() error {
|
||||
sentinel, prefs, err := pm.loadLegacyPrefs()
|
||||
if err != nil {
|
||||
metricMigrationError.Add(1)
|
||||
return err
|
||||
return fmt.Errorf("load legacy prefs: %w", err)
|
||||
}
|
||||
if err := pm.SetPrefs(prefs); err != nil {
|
||||
metricMigrationError.Add(1)
|
||||
|
||||
@@ -5,7 +5,7 @@ package ipnlocal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -18,9 +18,6 @@ import (
|
||||
)
|
||||
|
||||
func TestProfileCurrentUserSwitch(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
}
|
||||
store := new(mem.Store)
|
||||
|
||||
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
|
||||
@@ -77,9 +74,6 @@ func TestProfileCurrentUserSwitch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProfileList(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
}
|
||||
store := new(mem.Store)
|
||||
|
||||
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
|
||||
@@ -158,9 +152,6 @@ func TestProfileList(t *testing.T) {
|
||||
|
||||
// TestProfileManagement tests creating, loading, and switching profiles.
|
||||
func TestProfileManagement(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
}
|
||||
store := new(mem.Store)
|
||||
|
||||
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
|
||||
@@ -312,10 +303,11 @@ func TestProfileManagement(t *testing.T) {
|
||||
// TestProfileManagementWindows tests going into and out of Unattended mode on
|
||||
// Windows.
|
||||
func TestProfileManagementWindows(t *testing.T) {
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
uid := ipn.WindowsUserID(u.Uid)
|
||||
|
||||
store := new(mem.Store)
|
||||
|
||||
@@ -365,8 +357,8 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
|
||||
{
|
||||
t.Logf("Set user1 as logged in user")
|
||||
if err := pm.SetCurrentUserID("user1"); err != nil {
|
||||
t.Fatal(err)
|
||||
if err := pm.SetCurrentUserID(uid); err != nil {
|
||||
t.Fatalf("can't set user id: %s", err)
|
||||
}
|
||||
checkProfiles(t)
|
||||
t.Logf("Save prefs for user1")
|
||||
@@ -401,7 +393,7 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
|
||||
{
|
||||
t.Logf("Set user1 as current user")
|
||||
if err := pm.SetCurrentUserID("user1"); err != nil {
|
||||
if err := pm.SetCurrentUserID(uid); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantCurProfile = "test"
|
||||
@@ -411,8 +403,8 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
t.Logf("set unattended mode")
|
||||
wantProfiles["test"] = setPrefs(t, "test", true)
|
||||
}
|
||||
if pm.CurrentUserID() != "user1" {
|
||||
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), "user1")
|
||||
if pm.CurrentUserID() != uid {
|
||||
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
|
||||
}
|
||||
|
||||
// Recreate the profile manager to ensure that it starts with test profile.
|
||||
@@ -421,7 +413,7 @@ func TestProfileManagementWindows(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkProfiles(t)
|
||||
if pm.CurrentUserID() != "user1" {
|
||||
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), "user1")
|
||||
if pm.CurrentUserID() != uid {
|
||||
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package ipnlocal
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
@@ -21,8 +22,6 @@ const (
|
||||
legacyPrefsExt = ".conf"
|
||||
)
|
||||
|
||||
var errAlreadyMigrated = errors.New("profile migration already completed")
|
||||
|
||||
func legacyPrefsDir(uid ipn.WindowsUserID) (string, error) {
|
||||
// TODO(aaron): Ideally we'd have the impersonation token for the pipe's
|
||||
// client and use it to call SHGetKnownFolderPath, thus yielding the correct
|
||||
@@ -56,6 +55,9 @@ func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
|
||||
|
||||
prefsPath := filepath.Join(userLegacyPrefsDir, legacyPrefsFile+legacyPrefsExt)
|
||||
prefs, err := ipn.LoadPrefs(prefsPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return "", ipn.PrefsView{}, errAlreadyMigrated
|
||||
}
|
||||
if err != nil {
|
||||
return "", ipn.PrefsView{}, err
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
pathpkg "path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -144,7 +143,7 @@ func (s *serveListener) Run() {
|
||||
}
|
||||
|
||||
func (s *serveListener) shouldWarnAboutListenError(err error) bool {
|
||||
if !s.b.e.GetLinkMonitor().InterfaceState().HasIP(s.ap.Addr()) {
|
||||
if !s.b.sys.NetMon.Get().InterfaceState().HasIP(s.ap.Addr()) {
|
||||
// Machine likely doesn't have IPv6 enabled (or the IP is still being
|
||||
// assigned). No need to warn. Notably, WSL2 (Issue 6303).
|
||||
return false
|
||||
@@ -420,19 +419,19 @@ func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView,
|
||||
if h, ok := wsc.Handlers().GetOk(r.URL.Path); ok {
|
||||
return h, r.URL.Path, true
|
||||
}
|
||||
path := path.Clean(r.URL.Path)
|
||||
pth := path.Clean(r.URL.Path)
|
||||
for {
|
||||
withSlash := path + "/"
|
||||
withSlash := pth + "/"
|
||||
if h, ok := wsc.Handlers().GetOk(withSlash); ok {
|
||||
return h, withSlash, true
|
||||
}
|
||||
if h, ok := wsc.Handlers().GetOk(path); ok {
|
||||
return h, path, true
|
||||
if h, ok := wsc.Handlers().GetOk(pth); ok {
|
||||
return h, pth, true
|
||||
}
|
||||
if path == "/" {
|
||||
if pth == "/" {
|
||||
return z, "", false
|
||||
}
|
||||
path = pathpkg.Dir(path)
|
||||
pth = path.Dir(pth)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/empty"
|
||||
"tailscale.com/types/key"
|
||||
@@ -297,14 +298,17 @@ func TestStateMachine(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
logf := tstest.WhileTestRunningLogger(t)
|
||||
sys := new(tsd.System)
|
||||
store := new(testStateStorage)
|
||||
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||
sys.Set(store)
|
||||
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
|
||||
if err != nil {
|
||||
t.Fatalf("NewFakeUserspaceEngine: %v", err)
|
||||
}
|
||||
t.Cleanup(e.Close)
|
||||
sys.Set(e)
|
||||
|
||||
b, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, e, 0)
|
||||
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("NewLocalBackend: %v", err)
|
||||
}
|
||||
@@ -941,13 +945,16 @@ func TestStateMachine(t *testing.T) {
|
||||
|
||||
func TestEditPrefsHasNoKeys(t *testing.T) {
|
||||
logf := tstest.WhileTestRunningLogger(t)
|
||||
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||
sys := new(tsd.System)
|
||||
sys.Set(new(mem.Store))
|
||||
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
|
||||
if err != nil {
|
||||
t.Fatalf("NewFakeUserspaceEngine: %v", err)
|
||||
}
|
||||
t.Cleanup(e.Close)
|
||||
sys.Set(e)
|
||||
|
||||
b, err := NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), nil, e, 0)
|
||||
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("NewLocalBackend: %v", err)
|
||||
}
|
||||
@@ -1023,10 +1030,14 @@ func TestWGEngineStatusRace(t *testing.T) {
|
||||
t.Skip("test fails")
|
||||
c := qt.New(t)
|
||||
logf := tstest.WhileTestRunningLogger(t)
|
||||
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||
sys := new(tsd.System)
|
||||
sys.Set(new(mem.Store))
|
||||
|
||||
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
|
||||
c.Assert(err, qt.IsNil)
|
||||
t.Cleanup(eng.Close)
|
||||
b, err := NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), nil, eng, 0)
|
||||
sys.Set(eng)
|
||||
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
var cc *mockControl
|
||||
|
||||
@@ -37,7 +37,8 @@ func (s *Server) handleProxyConnectConn(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
back, err := logpolicy.DialContext(ctx, "tcp", hostPort)
|
||||
dialContext := logpolicy.MakeDialFunc(s.netMon)
|
||||
back, err := dialContext(ctx, "tcp", hostPort)
|
||||
if err != nil {
|
||||
s.logf("error CONNECT dialing %v: %v", hostPort, err)
|
||||
http.Error(w, "Connect failure", http.StatusBadGateway)
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"tailscale.com/ipn/ipnauth"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/ipn/localapi"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/logid"
|
||||
"tailscale.com/util/mak"
|
||||
@@ -36,6 +37,7 @@ import (
|
||||
type Server struct {
|
||||
lb atomic.Pointer[ipnlocal.LocalBackend]
|
||||
logf logger.Logf
|
||||
netMon *netmon.Monitor // must be non-nil
|
||||
backendLogID logid.PublicID
|
||||
// resetOnZero is whether to call bs.Reset on transition from
|
||||
// 1->0 active HTTP requests. That is, this is whether the backend is
|
||||
@@ -197,7 +199,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer onDone()
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/localapi/") {
|
||||
lah := localapi.NewHandler(lb, s.logf, s.backendLogID)
|
||||
lah := localapi.NewHandler(lb, s.logf, s.netMon, s.backendLogID)
|
||||
lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci)
|
||||
lah.PermitCert = s.connCanFetchCerts(ci)
|
||||
lah.ServeHTTP(w, r)
|
||||
@@ -413,10 +415,14 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit
|
||||
//
|
||||
// At some point, either before or after Run, the Server's SetLocalBackend
|
||||
// method must also be called before Server can do anything useful.
|
||||
func New(logf logger.Logf, logID logid.PublicID) *Server {
|
||||
func New(logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) *Server {
|
||||
if netMon == nil {
|
||||
panic("nil netMon")
|
||||
}
|
||||
return &Server{
|
||||
backendLogID: logID,
|
||||
logf: logf,
|
||||
netMon: netMon,
|
||||
resetOnZero: envknob.GOOS() == "windows",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,24 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"tailscale.com/derp/derphttp"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/stun"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/nettype"
|
||||
)
|
||||
|
||||
func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -132,6 +139,92 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
return hasIPv4 || hasIPv6
|
||||
}
|
||||
|
||||
checkSTUN4 := func(derpNode *tailcfg.DERPNode) {
|
||||
u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(h.logf, h.netMon)).ListenPacket(ctx, "udp4", ":0")
|
||||
if err != nil {
|
||||
st.Errors = append(st.Errors, fmt.Sprintf("Error creating IPv4 STUN listener: %v", err))
|
||||
return
|
||||
}
|
||||
defer u4.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var addr netip.Addr
|
||||
if derpNode.IPv4 != "" {
|
||||
addr, err = netip.ParseAddr(derpNode.IPv4)
|
||||
if err != nil {
|
||||
// Error printed elsewhere
|
||||
return
|
||||
}
|
||||
} else {
|
||||
addrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", derpNode.HostName)
|
||||
if err != nil {
|
||||
st.Errors = append(st.Errors, fmt.Sprintf("Error resolving node %q IPv4 addresses: %v", derpNode.HostName, err))
|
||||
return
|
||||
}
|
||||
addr = addrs[0]
|
||||
}
|
||||
|
||||
addrPort := netip.AddrPortFrom(addr, uint16(firstNonzero(derpNode.STUNPort, 3478)))
|
||||
|
||||
txID := stun.NewTxID()
|
||||
req := stun.Request(txID)
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-done:
|
||||
}
|
||||
u4.Close()
|
||||
}()
|
||||
|
||||
gotResponse := make(chan netip.AddrPort, 1)
|
||||
go func() {
|
||||
defer u4.Close()
|
||||
|
||||
var buf [64 << 10]byte
|
||||
for {
|
||||
n, addr, err := u4.ReadFromUDPAddrPort(buf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pkt := buf[:n]
|
||||
if !stun.Is(pkt) {
|
||||
continue
|
||||
}
|
||||
ap := netaddr.Unmap(addr)
|
||||
if !ap.IsValid() {
|
||||
continue
|
||||
}
|
||||
tx, addrPort, err := stun.ParseResponse(pkt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if tx == txID {
|
||||
gotResponse <- addrPort
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = u4.WriteToUDPAddrPort(req, addrPort)
|
||||
if err != nil {
|
||||
st.Errors = append(st.Errors, fmt.Sprintf("Error sending IPv4 STUN packet to %v (%q): %v", addrPort, derpNode.HostName, err))
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case resp := <-gotResponse:
|
||||
st.Info = append(st.Info, fmt.Sprintf("Node %q returned IPv4 STUN response: %v", derpNode.HostName, resp))
|
||||
case <-ctx.Done():
|
||||
st.Warnings = append(st.Warnings, fmt.Sprintf("Node %q did not return a IPv4 STUN response", derpNode.HostName))
|
||||
}
|
||||
}
|
||||
|
||||
// Start by checking whether we can establish a HTTP connection
|
||||
for _, derpNode := range reg.Nodes {
|
||||
connSuccess := checkConn(derpNode)
|
||||
@@ -156,7 +249,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
serverPubKeys := make(map[key.NodePublic]bool)
|
||||
for i := 0; i < 5; i++ {
|
||||
func() {
|
||||
rc := derphttp.NewRegionClient(fakePrivKey, h.logf, func() *tailcfg.DERPRegion {
|
||||
rc := derphttp.NewRegionClient(fakePrivKey, h.logf, h.netMon, func() *tailcfg.DERPRegion {
|
||||
return &tailcfg.DERPRegion{
|
||||
RegionID: reg.RegionID,
|
||||
RegionCode: reg.RegionCode,
|
||||
@@ -178,6 +271,10 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
if len(serverPubKeys) > 1 {
|
||||
st.Errors = append(st.Errors, fmt.Sprintf("Received multiple server public keys (%d); is the DERP server behind a load balancer?", len(serverPubKeys)))
|
||||
}
|
||||
|
||||
// Send a STUN query to this node to verify whether or not it
|
||||
// correctly returns an IP address.
|
||||
checkSTUN4(derpNode)
|
||||
}
|
||||
|
||||
// TODO(bradfitz): finish:
|
||||
@@ -191,7 +288,6 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
|
||||
// protocol to say how many peers it's meshed with. Should match count
|
||||
// in DERPRegion. Or maybe even list all their server pub keys that it's peered
|
||||
// with.
|
||||
// * try STUN queries
|
||||
// * If their certificate is bad, either expired or just wrongly
|
||||
// issued in the first place, tell them specifically that the
|
||||
// cert is bad not just that the connection failed.
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/logtail"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/portmapper"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -46,7 +47,6 @@ import (
|
||||
"tailscale.com/util/httpm"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
type localAPIHandler func(*Handler, http.ResponseWriter, *http.Request)
|
||||
@@ -125,8 +125,10 @@ var (
|
||||
metrics = map[string]*clientmetric.Metric{}
|
||||
)
|
||||
|
||||
func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, logID logid.PublicID) *Handler {
|
||||
return &Handler{b: b, logf: logf, backendLogID: logID}
|
||||
// NewHandler creates a new LocalAPI HTTP handler. All parameters except netMon
|
||||
// are required (if non-nil it's used to do faster interface lookups).
|
||||
func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, netMon *netmon.Monitor, logID logid.PublicID) *Handler {
|
||||
return &Handler{b: b, logf: logf, netMon: netMon, backendLogID: logID}
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
@@ -150,6 +152,7 @@ type Handler struct {
|
||||
|
||||
b *ipnlocal.LocalBackend
|
||||
logf logger.Logf
|
||||
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
|
||||
backendLogID logid.PublicID
|
||||
}
|
||||
|
||||
@@ -679,7 +682,7 @@ func (h *Handler) serveDebugPortmap(w http.ResponseWriter, r *http.Request) {
|
||||
done := make(chan bool, 1)
|
||||
|
||||
var c *portmapper.Client
|
||||
c = portmapper.NewClient(logger.WithPrefix(logf, "portmapper: "), debugKnobs, func() {
|
||||
c = portmapper.NewClient(logger.WithPrefix(logf, "portmapper: "), h.netMon, debugKnobs, func() {
|
||||
logf("portmapping changed.")
|
||||
logf("have mapping: %v", c.HaveMapping())
|
||||
|
||||
@@ -695,7 +698,7 @@ func (h *Handler) serveDebugPortmap(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
defer c.Close()
|
||||
|
||||
linkMon, err := monitor.New(logger.WithPrefix(logf, "monitor: "))
|
||||
netMon, err := netmon.New(logger.WithPrefix(logf, "monitor: "))
|
||||
if err != nil {
|
||||
logf("error creating monitor: %v", err)
|
||||
return
|
||||
@@ -707,14 +710,14 @@ func (h *Handler) serveDebugPortmap(w http.ResponseWriter, r *http.Request) {
|
||||
self = netip.MustParseAddr(b)
|
||||
return gw, self, true
|
||||
}
|
||||
return linkMon.GatewayAndSelfIP()
|
||||
return netMon.GatewayAndSelfIP()
|
||||
}
|
||||
|
||||
c.SetGatewayLookupFunc(gatewayAndSelfIP)
|
||||
|
||||
gw, selfIP, ok := gatewayAndSelfIP()
|
||||
if !ok {
|
||||
logf("no gateway or self IP; %v", linkMon.InterfaceState())
|
||||
logf("no gateway or self IP; %v", netMon.InterfaceState())
|
||||
return
|
||||
}
|
||||
logf("gw=%v; self=%v", gw, selfIP)
|
||||
|
||||
@@ -51,6 +51,12 @@ type awsStore struct {
|
||||
|
||||
// New returns a new ipn.StateStore using the AWS SSM storage
|
||||
// location given by ssmARN.
|
||||
//
|
||||
// Note that we store the entire store in a single parameter
|
||||
// key, therefore if the state is above 8kb, it can cause
|
||||
// Tailscaled to only only store new state in-memory and
|
||||
// restarting Tailscaled can fail until you delete your state
|
||||
// from the AWS Parameter Store.
|
||||
func New(_ logger.Logf, ssmARN string) (ipn.StateStore, error) {
|
||||
return newStore(ssmARN, nil)
|
||||
}
|
||||
@@ -160,14 +166,19 @@ func (s *awsStore) persistState() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store in AWS SSM parameter store
|
||||
// Store in AWS SSM parameter store.
|
||||
//
|
||||
// We use intelligent tiering so that when the state is below 4kb, it uses Standard tiering
|
||||
// which is free. However, if it exceeds 4kb it switches the parameter to advanced tiering
|
||||
// doubling the capacity to 8kb per the following docs:
|
||||
// https://aws.amazon.com/about-aws/whats-new/2019/08/aws-systems-manager-parameter-store-announces-intelligent-tiering-to-enable-automatic-parameter-tier-selection/
|
||||
_, err = s.ssmClient.PutParameter(
|
||||
context.TODO(),
|
||||
&ssm.PutParameterInput{
|
||||
Name: aws.String(s.ParameterName()),
|
||||
Value: aws.String(string(bs)),
|
||||
Overwrite: aws.Bool(true),
|
||||
Tier: ssmTypes.ParameterTierStandard,
|
||||
Tier: ssmTypes.ParameterTierIntelligentTiering,
|
||||
Type: ssmTypes.ParameterTypeSecureString,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -9,22 +9,23 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||
|
||||
|
||||
- [eliasnaur.com/font/roboto](https://pkg.go.dev/eliasnaur.com/font/roboto) ([BSD-3-Clause](https://git.sr.ht/~eliasnaur/font/tree/832bb8fc08c3/LICENSE))
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
|
||||
- [gioui.org](https://pkg.go.dev/gioui.org) ([MIT](https://git.sr.ht/~eliasnaur/gio/tree/32c6a9b10d0b/LICENSE))
|
||||
- [gioui.org/cpu](https://pkg.go.dev/gioui.org/cpu) ([MIT](https://git.sr.ht/~eliasnaur/gio-cpu/tree/8d6a761490d2/LICENSE))
|
||||
- [gioui.org/shader](https://pkg.go.dev/gioui.org/shader) ([MIT](https://git.sr.ht/~eliasnaur/gio-shader/tree/v1.0.6/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.11.0/config/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.6.4/credentials/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.8.2/feature/ec2/imds/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.27/internal/configsources/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.21/internal/endpoints/v2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.2/internal/ini/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.5.2/service/internal/presigned-url/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.35.0/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.6.2/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.11.1/service/sts/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.22/config/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.13.21/credentials/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.13.3/feature/ec2/imds/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.33/internal/configsources/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.27/internal/endpoints/v2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.34/internal/ini/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.27/service/internal/presigned-url/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.36.3/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.12.9/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.14.9/service/ssooidc/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.18.10/service/sts/LICENSE.txt))
|
||||
- [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.13.5/LICENSE))
|
||||
- [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.13.5/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/benoitkugler/textlayout](https://pkg.go.dev/github.com/benoitkugler/textlayout) ([MIT](https://github.com/benoitkugler/textlayout/blob/v0.3.0/LICENSE))
|
||||
@@ -34,50 +35,51 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/go-text/typesetting](https://pkg.go.dev/github.com/go-text/typesetting) ([BSD-3-Clause](https://github.com/go-text/typesetting/blob/0399769901d5/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.0/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.0.1/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
||||
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/de60144f33f8/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/LICENSE))
|
||||
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.4/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.4/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.4/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.5/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.16.5/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.16.5/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.1/LICENSE.md))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
|
||||
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.17/LICENSE))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/bc99ab8c2d17/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/17a3db2c30d2/LICENSE))
|
||||
- [github.com/tailscale/goupnp](https://pkg.go.dev/github.com/tailscale/goupnp) ([BSD-2-Clause](https://github.com/tailscale/goupnp/blob/c64d0f06ea05/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tailscale/tailscale-android](https://pkg.go.dev/github.com/tailscale/tailscale-android) ([BSD-3-Clause](https://github.com/tailscale/tailscale-android/blob/HEAD/LICENSE))
|
||||
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/af172621b4dd/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/c3537552635f/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/3e8cd9d6bf63/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/intern](https://pkg.go.dev/go4.org/intern) ([BSD-3-Clause](https://github.com/go4org/intern/blob/ae77deb06f29/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/927187094b94/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
||||
- [go4.org/unsafe/assume-no-moving-gc](https://pkg.go.dev/go4.org/unsafe/assume-no-moving-gc) ([BSD-3-Clause](https://github.com/go4org/unsafe-assume-no-moving-gc/blob/ee73d164e760/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47842c84:LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||
- [golang.org/x/exp/shiny](https://pkg.go.dev/golang.org/x/exp/shiny) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/334a2380:shiny/LICENSE))
|
||||
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/a3b23cc7:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/LICENSE))
|
||||
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
||||
- [inet.af/netaddr](https://pkg.go.dev/inet.af/netaddr) ([BSD-3-Clause](https://github.com/inetaf/netaddr/blob/097006376321/LICENSE))
|
||||
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||
|
||||
@@ -10,62 +10,63 @@ and [iOS][]. See also the dependencies in the [Tailscale CLI][].
|
||||
## Go Packages
|
||||
|
||||
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.17.7/config/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.12.20/credentials/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.12.17/feature/ec2/imds/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.27/internal/configsources/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.21/internal/endpoints/v2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.24/internal/ini/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.17/service/internal/presigned-url/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.35.0/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.11.23/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.13.5/service/ssooidc/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.16.19/service/sts/LICENSE.txt))
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.22/config/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.13.21/credentials/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.13.3/feature/ec2/imds/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.33/internal/configsources/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.27/internal/endpoints/v2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.34/internal/ini/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.27/service/internal/presigned-url/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.36.3/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.12.9/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.14.9/service/ssooidc/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.18.10/service/sts/LICENSE.txt))
|
||||
- [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.13.5/LICENSE))
|
||||
- [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.13.5/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.0/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
||||
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/de60144f33f8/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/LICENSE))
|
||||
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.11/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.11/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.11/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.5/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.16.5/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.16.5/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.1/LICENSE.md))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
|
||||
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/bc99ab8c2d17/LICENSE))
|
||||
- [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.17/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/17a3db2c30d2/LICENSE))
|
||||
- [github.com/tailscale/goupnp](https://pkg.go.dev/github.com/tailscale/goupnp) ([BSD-2-Clause](https://github.com/tailscale/goupnp/blob/c64d0f06ea05/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/af172621b4dd/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/c3537552635f/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/3e8cd9d6bf63/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/cafedaf6:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/a3b23cc7:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
||||
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE))
|
||||
|
||||
@@ -13,85 +13,88 @@ well as an [option for macOS][].
|
||||
Some packages may only be included on certain architectures or operating systems.
|
||||
|
||||
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.0/LICENSE))
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
|
||||
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.1/LICENSE))
|
||||
- [github.com/akutz/memconn](https://pkg.go.dev/github.com/akutz/memconn) ([Apache-2.0](https://github.com/akutz/memconn/blob/v0.1.0/LICENSE))
|
||||
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
|
||||
- [github.com/anmitsu/go-shlex](https://pkg.go.dev/github.com/anmitsu/go-shlex) ([MIT](https://github.com/anmitsu/go-shlex/blob/38f4b401e2be/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.11.0/config/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.6.4/credentials/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.8.2/feature/ec2/imds/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.27/internal/configsources/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.21/internal/endpoints/v2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.2/internal/ini/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.3/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.5.2/service/internal/presigned-url/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.35.0/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.6.2/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.11.1/service/sts/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.22/config/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.13.21/credentials/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.13.3/feature/ec2/imds/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.33/internal/configsources/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.27/internal/endpoints/v2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.34/internal/ini/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.18.0/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.27/service/internal/presigned-url/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.36.3/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.12.9/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.14.9/service/ssooidc/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.18.10/service/sts/LICENSE.txt))
|
||||
- [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.13.5/LICENSE))
|
||||
- [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.13.5/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
|
||||
- [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.17/LICENSE))
|
||||
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/6ac47ab19aa5/LICENSE))
|
||||
- [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.18/LICENSE))
|
||||
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/111c8c3b57c8/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/go-ole/go-ole](https://pkg.go.dev/github.com/go-ole/go-ole) ([MIT](https://github.com/go-ole/go-ole/blob/v1.2.6/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.0.6/LICENSE))
|
||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.0/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.0.1/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
||||
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.3.0/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
||||
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/de60144f33f8/LICENSE))
|
||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/LICENSE))
|
||||
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/kballard/go-shellquote](https://pkg.go.dev/github.com/kballard/go-shellquote) ([MIT](https://github.com/kballard/go-shellquote/blob/95032a82bc51/LICENSE))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.4/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.4/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.4/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.5/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.16.5/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.16.5/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/kr/fs](https://pkg.go.dev/github.com/kr/fs) ([BSD-3-Clause](https://github.com/kr/fs/blob/v0.1.0/LICENSE))
|
||||
- [github.com/mattn/go-colorable](https://pkg.go.dev/github.com/mattn/go-colorable) ([MIT](https://github.com/mattn/go-colorable/blob/v0.1.12/LICENSE))
|
||||
- [github.com/mattn/go-isatty](https://pkg.go.dev/github.com/mattn/go-isatty) ([MIT](https://github.com/mattn/go-isatty/blob/v0.0.14/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.1/LICENSE.md))
|
||||
- [github.com/mattn/go-colorable](https://pkg.go.dev/github.com/mattn/go-colorable) ([MIT](https://github.com/mattn/go-colorable/blob/v0.1.13/LICENSE))
|
||||
- [github.com/mattn/go-isatty](https://pkg.go.dev/github.com/mattn/go-isatty) ([MIT](https://github.com/mattn/go-isatty/blob/v0.0.18/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
|
||||
- [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.0/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/peterbourgon/ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3) ([Apache-2.0](https://github.com/peterbourgon/ff/blob/v3.1.2/LICENSE))
|
||||
- [github.com/peterbourgon/ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3) ([Apache-2.0](https://github.com/peterbourgon/ff/blob/v3.3.0/LICENSE))
|
||||
- [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.17/LICENSE))
|
||||
- [github.com/pkg/errors](https://pkg.go.dev/github.com/pkg/errors) ([BSD-2-Clause](https://github.com/pkg/errors/blob/v0.9.1/LICENSE))
|
||||
- [github.com/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) ([BSD-2-Clause](https://github.com/pkg/sftp/blob/v1.13.4/LICENSE))
|
||||
- [github.com/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) ([BSD-2-Clause](https://github.com/pkg/sftp/blob/v1.13.5/LICENSE))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/certstore](https://pkg.go.dev/github.com/tailscale/certstore) ([MIT](https://github.com/tailscale/certstore/blob/78d6e1c49d8d/LICENSE.md))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/bc99ab8c2d17/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/17a3db2c30d2/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/af172621b4dd/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/toqueteos/webbrowser](https://pkg.go.dev/github.com/toqueteos/webbrowser) ([MIT](https://github.com/toqueteos/webbrowser/blob/v1.2.0/LICENSE.md))
|
||||
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/948a78c969ad/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/c3537552635f/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/650dca95af54/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/50045581ed74/LICENSE))
|
||||
- [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/v0.11.0/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/3e8cd9d6bf63/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/927187094b94/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47842c84:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/a3b23cc7:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/579cf78f:LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
|
||||
- [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/162ed5ef888d/LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
||||
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
||||
- [inet.af/wf](https://pkg.go.dev/inet.af/wf) ([BSD-3-Clause](https://github.com/inetaf/wf/blob/50d96caab2f6/LICENSE))
|
||||
- [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.25.0/LICENSE))
|
||||
- [inet.af/wf](https://pkg.go.dev/inet.af/wf) ([BSD-3-Clause](https://github.com/inetaf/wf/blob/36129f591884/LICENSE))
|
||||
- [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.26.1/LICENSE))
|
||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||
- [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([MIT](https://github.com/kubernetes-sigs/yaml/blob/v1.3.0/LICENSE))
|
||||
- [software.sslmate.com/src/go-pkcs12](https://pkg.go.dev/software.sslmate.com/src/go-pkcs12) ([BSD-3-Clause](https://github.com/SSLMate/go-pkcs12/blob/v0.2.0/LICENSE))
|
||||
|
||||
@@ -9,44 +9,44 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
## Go Packages
|
||||
|
||||
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0-rc.1/LICENSE))
|
||||
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.0/LICENSE))
|
||||
- [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.0.0/LICENSE))
|
||||
- [github.com/Microsoft/go-winio](https://pkg.go.dev/github.com/Microsoft/go-winio) ([MIT](https://github.com/Microsoft/go-winio/blob/v0.6.1/LICENSE))
|
||||
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
|
||||
- [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE))
|
||||
- [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE))
|
||||
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/2b26ab7fb5f9/LICENSE))
|
||||
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/111c8c3b57c8/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
||||
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.3.0/LICENSE))
|
||||
- [github.com/gregjones/httpcache](https://pkg.go.dev/github.com/gregjones/httpcache) ([MIT](https://github.com/gregjones/httpcache/blob/901d90724c79/LICENSE.txt))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/c00d1f31bab3/LICENSE))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/d380b505068b/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.15.11/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.15.11/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.15.11/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.1/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.0/LICENSE.md))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.16.5/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.16.5/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.16.5/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.4.1/LICENSE.md))
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/nfnt/resize](https://pkg.go.dev/github.com/nfnt/resize) ([ISC](https://github.com/nfnt/resize/blob/83c6a9932646/LICENSE))
|
||||
- [github.com/peterbourgon/diskv](https://pkg.go.dev/github.com/peterbourgon/diskv) ([MIT](https://github.com/peterbourgon/diskv/blob/v2.0.1/LICENSE))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/f6f2f17d9da1/LICENSE))
|
||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/ad93eed16885/LICENSE))
|
||||
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.1.6/LICENSE))
|
||||
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/f63dace725d8/LICENSE))
|
||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/59dfb47dfef1/LICENSE))
|
||||
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.2.0/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/7e7bdc8411bf/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.6.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/cafedaf6:LICENSE))
|
||||
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.1.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/a3b23cc7:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.7.0:LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.10.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
|
||||
- [gopkg.in/Knetic/govaluate.v3](https://pkg.go.dev/gopkg.in/Knetic/govaluate.v3) ([MIT](https://github.com/Knetic/govaluate/blob/v3.0.0/LICENSE))
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"tailscale.com/logpolicy"
|
||||
"tailscale.com/logtail"
|
||||
"tailscale.com/logtail/filch"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/smallzstd"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -33,7 +34,7 @@ const pollInterval = time.Second / 10
|
||||
// logInterval specifies how often to log sockstat events to disk.
|
||||
// This delay is added to prevent an infinite loop when logs are uploaded,
|
||||
// which itself creates additional sockstat events.
|
||||
const logInterval = 3 * time.Second
|
||||
const logInterval = 10 * time.Second
|
||||
|
||||
// maxLogFileSize specifies the maximum size of a log file before it is rotated.
|
||||
// Our logs are fairly compact, and we are mostly only looking at a few hours of data.
|
||||
@@ -83,7 +84,7 @@ type event struct {
|
||||
// SockstatLogID reproducibly derives a new logid.PrivateID for sockstat logging from a node's public backend log ID.
|
||||
// The returned PrivateID is the sha256 sum of logID + "sockstat".
|
||||
// If a node's public log ID becomes known, it is trivial to spoof sockstat logs for that node.
|
||||
// Given the this is just for debugging, we're not too concerned about that.
|
||||
// Given that this is just for debugging, we're not too concerned about that.
|
||||
func SockstatLogID(logID logid.PublicID) logid.PrivateID {
|
||||
return logid.PrivateID(sha256.Sum256([]byte(logID.String() + "sockstat")))
|
||||
}
|
||||
@@ -92,7 +93,8 @@ func SockstatLogID(logID logid.PublicID) logid.PrivateID {
|
||||
// On platforms that do not support sockstat logging, a nil Logger will be returned.
|
||||
// The returned Logger is not yet enabled, and must be shut down with Shutdown when it is no longer needed.
|
||||
// Logs will be uploaded to the log server using a new log ID derived from the provided backend logID.
|
||||
func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID) (*Logger, error) {
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) (*Logger, error) {
|
||||
if !sockstats.IsAvailable {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -112,7 +114,7 @@ func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID) (*Logger,
|
||||
logger := &Logger{
|
||||
logf: logf,
|
||||
filch: filch,
|
||||
tr: logpolicy.NewLogtailTransport(logtail.DefaultHost),
|
||||
tr: logpolicy.NewLogtailTransport(logtail.DefaultHost, netMon),
|
||||
}
|
||||
logger.logger = logtail.NewLogger(logtail.Config{
|
||||
BaseURL: logpolicy.LogURL(),
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestResourceCleanup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lg, err := NewLogger(td, logger.Discard, id.Public())
|
||||
lg, err := NewLogger(td, logger.Discard, id.Public(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/dnsfallback"
|
||||
"tailscale.com/net/netknob"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
@@ -450,14 +451,15 @@ func tryFixLogStateLocation(dir, cmdname string) {
|
||||
|
||||
// New returns a new log policy (a logger and its instance ID) for a
|
||||
// given collection name.
|
||||
func New(collection string) *Policy {
|
||||
return NewWithConfigPath(collection, "", "")
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func New(collection string, netMon *netmon.Monitor) *Policy {
|
||||
return NewWithConfigPath(collection, "", "", netMon)
|
||||
}
|
||||
|
||||
// NewWithConfigPath is identical to New,
|
||||
// but uses the specified directory and command name.
|
||||
// If either is empty, it derives them automatically.
|
||||
func NewWithConfigPath(collection, dir, cmdName string) *Policy {
|
||||
func NewWithConfigPath(collection, dir, cmdName string, netMon *netmon.Monitor) *Policy {
|
||||
var lflags int
|
||||
if term.IsTerminal(2) || runtime.GOOS == "windows" {
|
||||
lflags = 0
|
||||
@@ -554,7 +556,7 @@ func NewWithConfigPath(collection, dir, cmdName string) *Policy {
|
||||
}
|
||||
return w
|
||||
},
|
||||
HTTPC: &http.Client{Transport: NewLogtailTransport(logtail.DefaultHost)},
|
||||
HTTPC: &http.Client{Transport: NewLogtailTransport(logtail.DefaultHost, netMon)},
|
||||
}
|
||||
if collection == logtail.CollectionNode {
|
||||
conf.MetricsDelta = clientmetric.EncodeLogTailMetricsDelta
|
||||
@@ -569,7 +571,7 @@ func NewWithConfigPath(collection, dir, cmdName string) *Policy {
|
||||
log.Println("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.")
|
||||
conf.BaseURL = val
|
||||
u, _ := url.Parse(val)
|
||||
conf.HTTPC = &http.Client{Transport: NewLogtailTransport(u.Host)}
|
||||
conf.HTTPC = &http.Client{Transport: NewLogtailTransport(u.Host, netMon)}
|
||||
}
|
||||
|
||||
filchOptions := filch.Options{
|
||||
@@ -670,13 +672,22 @@ func (p *Policy) Shutdown(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DialContext is a net.Dialer.DialContext specialized for use by logtail.
|
||||
// MakeDialFunc creates a net.Dialer.DialContext function specialized for use
|
||||
// by logtail.
|
||||
// It does the following:
|
||||
// - If DNS lookup fails, consults the bootstrap DNS list of Tailscale hostnames.
|
||||
// - If TLS connection fails, try again using LetsEncrypt's built-in root certificate,
|
||||
// for the benefit of older OS platforms which might not include it.
|
||||
func DialContext(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
nd := netns.FromDialer(log.Printf, &net.Dialer{
|
||||
//
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func MakeDialFunc(netMon *netmon.Monitor) func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
return func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
return dialContext(ctx, netw, addr, netMon)
|
||||
}
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, netw, addr string, netMon *netmon.Monitor) (net.Conn, error) {
|
||||
nd := netns.FromDialer(log.Printf, netMon, &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: netknob.PlatformTCPKeepAlive(),
|
||||
})
|
||||
@@ -711,7 +722,8 @@ func DialContext(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
dnsCache := &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward, // use default cache's forwarder
|
||||
UseLastGood: true,
|
||||
LookupIPFallback: dnsfallback.Lookup,
|
||||
LookupIPFallback: dnsfallback.MakeLookupFunc(log.Printf, netMon),
|
||||
NetMon: netMon,
|
||||
}
|
||||
dialer := dnscache.Dialer(nd.DialContext, dnsCache)
|
||||
c, err = dialer(ctx, netw, addr)
|
||||
@@ -723,7 +735,8 @@ func DialContext(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
|
||||
// NewLogtailTransport returns an HTTP Transport particularly suited to uploading
|
||||
// logs to the given host name. See DialContext for details on how it works.
|
||||
func NewLogtailTransport(host string) http.RoundTripper {
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func NewLogtailTransport(host string, netMon *netmon.Monitor) http.RoundTripper {
|
||||
if inTest() {
|
||||
return noopPretendSuccessTransport{}
|
||||
}
|
||||
@@ -739,7 +752,7 @@ func NewLogtailTransport(host string) http.RoundTripper {
|
||||
tr.DisableCompression = true
|
||||
|
||||
// Log whenever we dial:
|
||||
tr.DialContext = DialContext
|
||||
tr.DialContext = MakeDialFunc(netMon)
|
||||
|
||||
// We're contacting exactly 1 hostname, so the default's 100
|
||||
// max idle conns is very high for our needs. Even 2 is
|
||||
|
||||
@@ -13,22 +13,22 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
mrand "math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/tstime"
|
||||
tslogger "tailscale.com/types/logger"
|
||||
"tailscale.com/types/logid"
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
// DefaultHost is the default host name to upload logs to when
|
||||
@@ -128,9 +128,6 @@ func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
|
||||
cfg.FlushDelayFn = func() time.Duration { return 0 }
|
||||
}
|
||||
|
||||
stdLogf := func(f string, a ...any) {
|
||||
fmt.Fprintf(cfg.Stderr, strings.TrimSuffix(f, "\n")+"\n", a...)
|
||||
}
|
||||
var urlSuffix string
|
||||
if !cfg.CopyPrivateID.IsZero() {
|
||||
urlSuffix = "?copyId=" + cfg.CopyPrivateID.String()
|
||||
@@ -148,7 +145,6 @@ func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
|
||||
sentinel: make(chan int32, 16),
|
||||
flushDelayFn: cfg.FlushDelayFn,
|
||||
timeNow: cfg.TimeNow,
|
||||
bo: backoff.NewBackoff("logtail", stdLogf, 30*time.Second),
|
||||
metricsDelta: cfg.MetricsDelta,
|
||||
sockstatsLabel: sockstats.LabelLogtailLogger,
|
||||
|
||||
@@ -179,14 +175,13 @@ type Logger struct {
|
||||
url string
|
||||
lowMem bool
|
||||
skipClientTime bool
|
||||
linkMonitor *monitor.Mon
|
||||
netMonitor *netmon.Monitor
|
||||
buffer Buffer
|
||||
drainWake chan struct{} // signal to speed up drain
|
||||
flushDelayFn func() time.Duration // negative or zero return value to upload aggressively, or >0 to batch at this delay
|
||||
flushPending atomic.Bool
|
||||
sentinel chan int32
|
||||
timeNow func() time.Time
|
||||
bo *backoff.Backoff
|
||||
zstdEncoder Encoder
|
||||
uploadCancel func()
|
||||
explainedRaw bool
|
||||
@@ -214,12 +209,12 @@ func (l *Logger) SetVerbosityLevel(level int) {
|
||||
atomic.StoreInt64(&l.stderrLevel, int64(level))
|
||||
}
|
||||
|
||||
// SetLinkMonitor sets the optional the link monitor.
|
||||
// SetNetMon sets the optional the network monitor.
|
||||
//
|
||||
// It should not be changed concurrently with log writes and should
|
||||
// only be set once.
|
||||
func (l *Logger) SetLinkMonitor(lm *monitor.Mon) {
|
||||
l.linkMonitor = lm
|
||||
func (l *Logger) SetNetMon(lm *netmon.Monitor) {
|
||||
l.netMonitor = lm
|
||||
}
|
||||
|
||||
// SetSockstatsLabel sets the label used in sockstat logs to identify network traffic from this logger.
|
||||
@@ -373,23 +368,38 @@ func (l *Logger) uploading(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
for len(body) > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
uploaded, err := l.upload(ctx, body, origlen)
|
||||
var lastError string
|
||||
var numFailures int
|
||||
var firstFailure time.Time
|
||||
for len(body) > 0 && ctx.Err() == nil {
|
||||
retryAfter, err := l.upload(ctx, body, origlen)
|
||||
if err != nil {
|
||||
numFailures++
|
||||
firstFailure = time.Now()
|
||||
|
||||
if !l.internetUp() {
|
||||
fmt.Fprintf(l.stderr, "logtail: internet down; waiting\n")
|
||||
l.awaitInternetUp(ctx)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(l.stderr, "logtail: upload: %v\n", err)
|
||||
}
|
||||
l.bo.BackOff(ctx, err)
|
||||
if uploaded {
|
||||
|
||||
// Only print the same message once.
|
||||
if currError := err.Error(); lastError != currError {
|
||||
fmt.Fprintf(l.stderr, "logtail: upload: %v\n", err)
|
||||
lastError = currError
|
||||
}
|
||||
|
||||
// Sleep for the specified retryAfter period,
|
||||
// otherwise default to some random value.
|
||||
if retryAfter <= 0 {
|
||||
retryAfter = time.Duration(30+mrand.Intn(30)) * time.Second
|
||||
}
|
||||
tstime.Sleep(ctx, retryAfter)
|
||||
} else {
|
||||
// Only print a success message after recovery.
|
||||
if numFailures > 0 {
|
||||
fmt.Fprintf(l.stderr, "logtail: upload succeeded after %d failures and %s\n", numFailures, time.Since(firstFailure).Round(time.Second))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -403,16 +413,16 @@ func (l *Logger) uploading(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (l *Logger) internetUp() bool {
|
||||
if l.linkMonitor == nil {
|
||||
if l.netMonitor == nil {
|
||||
// No way to tell, so assume it is.
|
||||
return true
|
||||
}
|
||||
return l.linkMonitor.InterfaceState().AnyInterfaceUp()
|
||||
return l.netMonitor.InterfaceState().AnyInterfaceUp()
|
||||
}
|
||||
|
||||
func (l *Logger) awaitInternetUp(ctx context.Context) {
|
||||
upc := make(chan bool, 1)
|
||||
defer l.linkMonitor.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
|
||||
defer l.netMonitor.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
|
||||
if st.AnyInterfaceUp() {
|
||||
select {
|
||||
case upc <- true:
|
||||
@@ -433,7 +443,7 @@ func (l *Logger) awaitInternetUp(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, origlen int) (retryAfter time.Duration, err error) {
|
||||
const maxUploadTime = 45 * time.Second
|
||||
ctx = sockstats.WithSockStats(ctx, l.sockstatsLabel, l.Logf)
|
||||
ctx, cancel := context.WithTimeout(ctx, maxUploadTime)
|
||||
@@ -460,17 +470,16 @@ func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded
|
||||
l.httpDoCalls.Add(1)
|
||||
resp, err := l.httpc.Do(req)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("log upload of %d bytes %s failed: %v", len(body), compressedNote, err)
|
||||
return 0, fmt.Errorf("log upload of %d bytes %s failed: %v", len(body), compressedNote, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
uploaded = resp.StatusCode == 400 // the server saved the logs anyway
|
||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
return uploaded, fmt.Errorf("log upload of %d bytes %s failed %d: %q", len(body), compressedNote, resp.StatusCode, b)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
n, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
|
||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<10))
|
||||
return time.Duration(n) * time.Second, fmt.Errorf("log upload of %d bytes %s failed %d: %s", len(body), compressedNote, resp.StatusCode, bytes.TrimSpace(b))
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Flush uploads all logs to the server. It blocks until complete or there is an
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/types/netlogtype"
|
||||
)
|
||||
|
||||
@@ -92,6 +93,11 @@ func (s *Statistics) UpdateRxVirtual(b []byte) {
|
||||
s.updateVirtual(b, true)
|
||||
}
|
||||
|
||||
var (
|
||||
tailscaleServiceIPv4 = tsaddr.TailscaleServiceIP()
|
||||
tailscaleServiceIPv6 = tsaddr.TailscaleServiceIPv6()
|
||||
)
|
||||
|
||||
func (s *Statistics) updateVirtual(b []byte, receive bool) {
|
||||
var p packet.Parsed
|
||||
p.Decode(b)
|
||||
@@ -100,6 +106,15 @@ func (s *Statistics) updateVirtual(b []byte, receive bool) {
|
||||
conn.Src, conn.Dst = conn.Dst, conn.Src
|
||||
}
|
||||
|
||||
// Network logging is defined as traffic between two Tailscale nodes.
|
||||
// Traffic with the internal Tailscale service is not with another node
|
||||
// and should not be logged. It also happens to be a high volume
|
||||
// amount of discrete traffic flows (e.g., DNS lookups).
|
||||
switch conn.Dst.Addr() {
|
||||
case tailscaleServiceIPv4, tailscaleServiceIPv6:
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
cnts, found := s.virtual[conn]
|
||||
|
||||
@@ -18,12 +18,12 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns/resolver"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -64,14 +64,15 @@ type Manager struct {
|
||||
}
|
||||
|
||||
// NewManagers created a new manager from the given config.
|
||||
func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector) *Manager {
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func NewManager(logf logger.Logf, oscfg OSConfigurator, netMon *netmon.Monitor, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector) *Manager {
|
||||
if dialer == nil {
|
||||
panic("nil Dialer")
|
||||
}
|
||||
logf = logger.WithPrefix(logf, "dns: ")
|
||||
m := &Manager{
|
||||
logf: logf,
|
||||
resolver: resolver.New(logf, linkMon, linkSel, dialer),
|
||||
resolver: resolver.New(logf, netMon, linkSel, dialer),
|
||||
os: oscfg,
|
||||
}
|
||||
m.ctx, m.ctxCancel = context.WithCancel(context.Background())
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestDNSOverTCP(t *testing.T) {
|
||||
"bradfitz.ts.com.": "2.3.4.5",
|
||||
}
|
||||
|
||||
for domain, _ := range wantResults {
|
||||
for domain := range wantResults {
|
||||
b := mkDNSRequest(domain, dns.TypeA, addEDNS)
|
||||
binary.Write(c, binary.BigEndian, uint16(len(b)))
|
||||
c.Write(b)
|
||||
|
||||
@@ -502,7 +502,7 @@ func genRandomSubdomains(t *testing.T, n int) []dnsname.FQDN {
|
||||
for len(domains) < cap(domains) {
|
||||
l := r.Intn(19) + 1
|
||||
b := make([]byte, l)
|
||||
for i, _ := range b {
|
||||
for i := range b {
|
||||
b[i] = charset[r.Intn(len(charset))]
|
||||
}
|
||||
d := string(b) + ".example.com"
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"tailscale.com/net/dns/publicdns"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/neterror"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/net/tsdial"
|
||||
@@ -34,7 +35,6 @@ import (
|
||||
"tailscale.com/util/cloudenv"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
// headerBytes is the number of bytes in a DNS message header.
|
||||
@@ -176,7 +176,7 @@ type resolverAndDelay struct {
|
||||
// forwarder forwards DNS packets to a number of upstream nameservers.
|
||||
type forwarder struct {
|
||||
logf logger.Logf
|
||||
linkMon *monitor.Mon
|
||||
netMon *netmon.Monitor
|
||||
linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absorbs it
|
||||
dialer *tsdial.Dialer
|
||||
|
||||
@@ -206,10 +206,10 @@ func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func newForwarder(logf logger.Logf, linkMon *monitor.Mon, linkSel ForwardLinkSelector, dialer *tsdial.Dialer) *forwarder {
|
||||
func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer) *forwarder {
|
||||
f := &forwarder{
|
||||
logf: logger.WithPrefix(logf, "forward: "),
|
||||
linkMon: linkMon,
|
||||
netMon: netMon,
|
||||
linkSel: linkSel,
|
||||
dialer: dialer,
|
||||
}
|
||||
@@ -355,7 +355,7 @@ func (f *forwarder) packetListener(ip netip.Addr) (nettype.PacketListenerWithNet
|
||||
return stdNetPacketListener, nil
|
||||
}
|
||||
lc := new(net.ListenConfig)
|
||||
if err := initListenConfig(lc, f.linkMon, linkName); err != nil {
|
||||
if err := initListenConfig(lc, f.netMon, linkName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nettype.MakePacketListenerWithNetIP(lc), nil
|
||||
@@ -380,11 +380,12 @@ func (f *forwarder) getKnownDoHClientForProvider(urlBase string) (c *http.Client
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
nsDialer := netns.NewDialer(f.logf)
|
||||
nsDialer := netns.NewDialer(f.logf, f.netMon)
|
||||
dialer := dnscache.Dialer(nsDialer.DialContext, &dnscache.Resolver{
|
||||
SingleHost: dohURL.Hostname(),
|
||||
SingleHostStaticResult: allIPs,
|
||||
Logf: f.logf,
|
||||
NetMon: f.netMon,
|
||||
})
|
||||
c = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
@@ -521,7 +522,7 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn
|
||||
|
||||
// The 1 extra byte is to detect packet truncation.
|
||||
out := make([]byte, maxResponseBytes+1)
|
||||
n, _, err := conn.ReadFrom(out)
|
||||
n, _, err := conn.ReadFromUDPAddrPort(out)
|
||||
if err != nil {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
@@ -763,7 +764,7 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
||||
}
|
||||
}
|
||||
|
||||
var initListenConfig func(_ *net.ListenConfig, _ *monitor.Mon, tunName string) error
|
||||
var initListenConfig func(_ *net.ListenConfig, _ *netmon.Monitor, tunName string) error
|
||||
|
||||
// nameFromQuery extracts the normalized query name from bs.
|
||||
func nameFromQuery(bs []byte) (dnsname.FQDN, error) {
|
||||
|
||||
@@ -9,16 +9,16 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initListenConfig = initListenConfigNetworkExtension
|
||||
}
|
||||
|
||||
func initListenConfigNetworkExtension(nc *net.ListenConfig, mon *monitor.Mon, tunName string) error {
|
||||
nif, ok := mon.InterfaceState().Interface[tunName]
|
||||
func initListenConfigNetworkExtension(nc *net.ListenConfig, netMon *netmon.Monitor, tunName string) error {
|
||||
nif, ok := netMon.InterfaceState().Interface[tunName]
|
||||
if !ok {
|
||||
return errors.New("utun not found")
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/dns/resolvconffile"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/syncs"
|
||||
@@ -34,7 +35,6 @@ import (
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/cloudenv"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
const dnsSymbolicFQDN = "magicdns.localhost-tailscale-daemon."
|
||||
@@ -179,7 +179,7 @@ func WriteRoutes(w *bufio.Writer, routes map[dnsname.FQDN][]*dnstype.Resolver) {
|
||||
// it delegates to upstream nameservers if any are set.
|
||||
type Resolver struct {
|
||||
logf logger.Logf
|
||||
linkMon *monitor.Mon // or nil
|
||||
netMon *netmon.Monitor // or nil
|
||||
dialer *tsdial.Dialer // non-nil
|
||||
saveConfigForTests func(cfg Config) // used in tests to capture resolver config
|
||||
// forwarder forwards requests to upstream nameservers.
|
||||
@@ -205,20 +205,20 @@ type ForwardLinkSelector interface {
|
||||
}
|
||||
|
||||
// New returns a new resolver.
|
||||
// linkMon optionally specifies a link monitor to use for socket rebinding.
|
||||
func New(logf logger.Logf, linkMon *monitor.Mon, linkSel ForwardLinkSelector, dialer *tsdial.Dialer) *Resolver {
|
||||
// netMon optionally specifies a network monitor to use for socket rebinding.
|
||||
func New(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer) *Resolver {
|
||||
if dialer == nil {
|
||||
panic("nil Dialer")
|
||||
}
|
||||
r := &Resolver{
|
||||
logf: logger.WithPrefix(logf, "resolver: "),
|
||||
linkMon: linkMon,
|
||||
netMon: netMon,
|
||||
closed: make(chan struct{}),
|
||||
hostToIP: map[dnsname.FQDN][]netip.Addr{},
|
||||
ipToHost: map[netip.Addr]dnsname.FQDN{},
|
||||
dialer: dialer,
|
||||
}
|
||||
r.forwarder = newForwarder(r.logf, linkMon, linkSel, dialer)
|
||||
r.forwarder = newForwarder(r.logf, netMon, linkSel, dialer)
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
@@ -22,14 +22,13 @@ import (
|
||||
"time"
|
||||
|
||||
miekdns "github.com/miekg/dns"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
dns "golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -316,7 +315,7 @@ func TestRDNSNameToIPv6(t *testing.T) {
|
||||
}
|
||||
|
||||
func newResolver(t testing.TB) *Resolver {
|
||||
return New(t.Logf, nil /* no link monitor */, nil /* no link selector */, new(tsdial.Dialer))
|
||||
return New(t.Logf, nil /* no network monitor */, nil /* no link selector */, new(tsdial.Dialer))
|
||||
}
|
||||
|
||||
func TestResolveLocal(t *testing.T) {
|
||||
@@ -998,7 +997,7 @@ func TestMarshalResponseFormatError(t *testing.T) {
|
||||
|
||||
func TestForwardLinkSelection(t *testing.T) {
|
||||
configCall := make(chan string, 1)
|
||||
tstest.Replace(t, &initListenConfig, func(nc *net.ListenConfig, mon *monitor.Mon, tunName string) error {
|
||||
tstest.Replace(t, &initListenConfig, func(nc *net.ListenConfig, netMon *netmon.Monitor, tunName string) error {
|
||||
select {
|
||||
case configCall <- tunName:
|
||||
return nil
|
||||
@@ -1121,15 +1120,15 @@ func TestHandleExitNodeDNSQueryWithNetPkg(t *testing.T) {
|
||||
|
||||
t.Run("no_such_host", func(t *testing.T) {
|
||||
res, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), t.Logf, backResolver, &response{
|
||||
Header: dnsmessage.Header{
|
||||
Header: dns.Header{
|
||||
ID: 123,
|
||||
Response: true,
|
||||
OpCode: 0, // query
|
||||
},
|
||||
Question: dnsmessage.Question{
|
||||
Name: dnsmessage.MustNewName("nx-domain.test."),
|
||||
Type: dnsmessage.TypeA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
Question: dns.Question{
|
||||
Name: dns.MustNewName("nx-domain.test."),
|
||||
Type: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1156,74 +1155,74 @@ func TestHandleExitNodeDNSQueryWithNetPkg(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Type dnsmessage.Type
|
||||
Type dns.Type
|
||||
Name string
|
||||
Check func(t testing.TB, got []byte)
|
||||
}{
|
||||
{
|
||||
Type: dnsmessage.TypeA,
|
||||
Type: dns.TypeA,
|
||||
Name: "one-a.test.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x05one-a\x04test\x00\x00\x01\x00\x01\x05one-a\x04test\x00\x00\x01\x00\x01\x00\x00\x02X\x00\x04\x01\x02\x03\x04"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeA,
|
||||
Type: dns.TypeA,
|
||||
Name: "two-a.test.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x02\x00\x00\x00\x00\x05two-a\x04test\x00\x00\x01\x00\x01\xc0\f\x00\x01\x00\x01\x00\x00\x02X\x00\x04\x01\x02\x03\x04\xc0\f\x00\x01\x00\x01\x00\x00\x02X\x00\x04\x05\x06\a\b"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeAAAA,
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "one-aaaa.test.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\bone-aaaa\x04test\x00\x00\x1c\x00\x01\bone-aaaa\x04test\x00\x00\x1c\x00\x01\x00\x00\x02X\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeAAAA,
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "two-aaaa.test.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x02\x00\x00\x00\x00\btwo-aaaa\x04test\x00\x00\x1c\x00\x01\xc0\f\x00\x1c\x00\x01\x00\x00\x02X\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc0\f\x00\x1c\x00\x01\x00\x00\x02X\x00\x10\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypePTR,
|
||||
Type: dns.TypePTR,
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x014\x013\x012\x011\ain-addr\x04arpa\x00\x00\f\x00\x01\x014\x013\x012\x011\ain-addr\x04arpa\x00\x00\f\x00\x01\x00\x00\x02X\x00\t\x03foo\x03com\x00"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeCNAME,
|
||||
Type: dns.TypeCNAME,
|
||||
Name: "cname.test.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x05cname\x04test\x00\x00\x05\x00\x01\x05cname\x04test\x00\x00\x05\x00\x01\x00\x00\x02X\x00\x10\nthe-target\x03foo\x00"),
|
||||
},
|
||||
|
||||
// No records of various types
|
||||
{
|
||||
Type: dnsmessage.TypeA,
|
||||
Type: dns.TypeA,
|
||||
Name: "no-records.test.",
|
||||
Check: matchPacked("\x00{\x84\x03\x00\x01\x00\x00\x00\x00\x00\x00\nno-records\x04test\x00\x00\x01\x00\x01"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeAAAA,
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "no-records.test.",
|
||||
Check: matchPacked("\x00{\x84\x03\x00\x01\x00\x00\x00\x00\x00\x00\nno-records\x04test\x00\x00\x1c\x00\x01"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeCNAME,
|
||||
Type: dns.TypeCNAME,
|
||||
Name: "no-records.test.",
|
||||
Check: matchPacked("\x00{\x84\x03\x00\x01\x00\x00\x00\x00\x00\x00\nno-records\x04test\x00\x00\x05\x00\x01"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeSRV,
|
||||
Type: dns.TypeSRV,
|
||||
Name: "no-records.test.",
|
||||
Check: matchPacked("\x00{\x84\x03\x00\x01\x00\x00\x00\x00\x00\x00\nno-records\x04test\x00\x00!\x00\x01"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeTXT,
|
||||
Type: dns.TypeTXT,
|
||||
Name: "txt.test.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x03\x00\x00\x00\x00\x03txt\x04test\x00\x00\x10\x00\x01\x03txt\x04test\x00\x00\x10\x00\x01\x00\x00\x02X\x00\t\btxt1=one\x03txt\x04test\x00\x00\x10\x00\x01\x00\x00\x02X\x00\t\btxt2=two\x03txt\x04test\x00\x00\x10\x00\x01\x00\x00\x02X\x00\v\ntxt3=three"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeSRV,
|
||||
Type: dns.TypeSRV,
|
||||
Name: "srv.test.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x02\x00\x00\x00\x00\x03srv\x04test\x00\x00!\x00\x01\x03srv\x04test\x00\x00!\x00\x01\x00\x00\x02X\x00\x0f\x00\x01\x00\x02\x00\x03\x03foo\x03com\x00\x03srv\x04test\x00\x00!\x00\x01\x00\x00\x02X\x00\x0f\x00\x04\x00\x05\x00\x06\x03bar\x03com\x00"),
|
||||
},
|
||||
{
|
||||
Type: dnsmessage.TypeNS,
|
||||
Type: dns.TypeNS,
|
||||
Name: "ns.test.",
|
||||
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x02\x00\x00\x00\x00\x02ns\x04test\x00\x00\x02\x00\x01\x02ns\x04test\x00\x00\x02\x00\x01\x00\x00\x02X\x00\t\x03ns1\x03foo\x00\x02ns\x04test\x00\x00\x02\x00\x01\x00\x00\x02X\x00\t\x03ns2\x03bar\x00"),
|
||||
},
|
||||
@@ -1232,15 +1231,15 @@ func TestHandleExitNodeDNSQueryWithNetPkg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%v_%v", tt.Type, strings.Trim(tt.Name, ".")), func(t *testing.T) {
|
||||
got, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), t.Logf, backResolver, &response{
|
||||
Header: dnsmessage.Header{
|
||||
Header: dns.Header{
|
||||
ID: 123,
|
||||
Response: true,
|
||||
OpCode: 0, // query
|
||||
},
|
||||
Question: dnsmessage.Question{
|
||||
Name: dnsmessage.MustNewName(tt.Name),
|
||||
Question: dns.Question{
|
||||
Name: dns.MustNewName(tt.Name),
|
||||
Type: tt.Type,
|
||||
Class: dnsmessage.ClassINET,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/cloudenv"
|
||||
"tailscale.com/util/singleflight"
|
||||
@@ -91,6 +92,11 @@ type Resolver struct {
|
||||
// be added to all log messages printed with this logger.
|
||||
Logf logger.Logf
|
||||
|
||||
// NetMon optionally provides a netmon.Monitor to use to get the current
|
||||
// (cached) network interface.
|
||||
// If nil, the interface will be looked up dynamically.
|
||||
NetMon *netmon.Monitor
|
||||
|
||||
sf singleflight.Group[string, ipRes]
|
||||
|
||||
mu sync.Mutex
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@@ -24,16 +23,25 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/atomicfile"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/slicesx"
|
||||
)
|
||||
|
||||
func Lookup(ctx context.Context, host string) ([]netip.Addr, error) {
|
||||
// MakeLookupFunc creates a function that can be used to resolve hostnames
|
||||
// (e.g. as a LookupIPFallback from dnscache.Resolver).
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func MakeLookupFunc(logf logger.Logf, netMon *netmon.Monitor) func(ctx context.Context, host string) ([]netip.Addr, error) {
|
||||
return func(ctx context.Context, host string) ([]netip.Addr, error) {
|
||||
return lookup(ctx, host, logf, netMon)
|
||||
}
|
||||
}
|
||||
|
||||
func lookup(ctx context.Context, host string, logf logger.Logf, netMon *netmon.Monitor) ([]netip.Addr, error) {
|
||||
if ip, err := netip.ParseAddr(host); err == nil && ip.IsValid() {
|
||||
return []netip.Addr{ip}, nil
|
||||
}
|
||||
@@ -81,7 +89,7 @@ func Lookup(ctx context.Context, host string) ([]netip.Addr, error) {
|
||||
logf("trying bootstrapDNS(%q, %q) for %q ...", cand.dnsName, cand.ip, host)
|
||||
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
dm, err := bootstrapDNSMap(ctx, cand.dnsName, cand.ip, host)
|
||||
dm, err := bootstrapDNSMap(ctx, cand.dnsName, cand.ip, host, logf, netMon)
|
||||
if err != nil {
|
||||
logf("bootstrapDNS(%q, %q) for %q error: %v", cand.dnsName, cand.ip, host, err)
|
||||
continue
|
||||
@@ -100,8 +108,8 @@ func Lookup(ctx context.Context, host string) ([]netip.Addr, error) {
|
||||
|
||||
// serverName and serverIP of are, say, "derpN.tailscale.com".
|
||||
// queryName is the name being sought (e.g. "controlplane.tailscale.com"), passed as hint.
|
||||
func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netip.Addr, queryName string) (dnsMap, error) {
|
||||
dialer := netns.NewDialer(logf)
|
||||
func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netip.Addr, queryName string, logf logger.Logf, netMon *netmon.Monitor) (dnsMap, error) {
|
||||
dialer := netns.NewDialer(logf, netMon)
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
||||
tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
@@ -194,7 +202,7 @@ var cachePath string
|
||||
// UpdateCache stores the DERP map cache back to disk.
|
||||
//
|
||||
// The caller must not mutate 'c' after calling this function.
|
||||
func UpdateCache(c *tailcfg.DERPMap) {
|
||||
func UpdateCache(c *tailcfg.DERPMap, logf logger.Logf) {
|
||||
// Don't do anything if nothing changed.
|
||||
curr := cachedDERPMap.Load()
|
||||
if reflect.DeepEqual(curr, c) {
|
||||
@@ -227,7 +235,7 @@ func UpdateCache(c *tailcfg.DERPMap) {
|
||||
//
|
||||
// This function should be called before any calls to UpdateCache, as it is not
|
||||
// concurrency-safe.
|
||||
func SetCachePath(path string) {
|
||||
func SetCachePath(path string, logf logger.Logf) {
|
||||
cachePath = path
|
||||
|
||||
f, err := os.Open(path)
|
||||
@@ -246,23 +254,3 @@ func SetCachePath(path string) {
|
||||
cachedDERPMap.Store(dm)
|
||||
logf("[v2] dnsfallback: SetCachePath loaded cached DERP map")
|
||||
}
|
||||
|
||||
// logfunc stores the logging function to use for this package.
|
||||
var logfunc syncs.AtomicValue[logger.Logf]
|
||||
|
||||
// SetLogger sets the logging function that this package will use, and returns
|
||||
// the old value (which may be nil).
|
||||
//
|
||||
// If this function is never called, or if this function is called with a nil
|
||||
// value, 'log.Printf' will be used to print logs.
|
||||
func SetLogger(log logger.Logf) (old logger.Logf) {
|
||||
return logfunc.Swap(log)
|
||||
}
|
||||
|
||||
func logf(format string, args ...any) {
|
||||
if lf := logfunc.Load(); lf != nil {
|
||||
lf(format, args...)
|
||||
} else {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,6 @@ func TestGetDERPMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
oldlog := logfunc.Load()
|
||||
SetLogger(t.Logf)
|
||||
t.Cleanup(func() {
|
||||
SetLogger(oldlog)
|
||||
})
|
||||
cacheFile := filepath.Join(t.TempDir(), "cache.json")
|
||||
|
||||
// Write initial cache value
|
||||
@@ -73,7 +68,7 @@ func TestCache(t *testing.T) {
|
||||
cachedDERPMap.Store(nil)
|
||||
|
||||
// Load the cache
|
||||
SetCachePath(cacheFile)
|
||||
SetCachePath(cacheFile, t.Logf)
|
||||
if cm := cachedDERPMap.Load(); !reflect.DeepEqual(initialCache, cm) {
|
||||
t.Fatalf("cached map was %+v; want %+v", cm, initialCache)
|
||||
}
|
||||
@@ -105,11 +100,6 @@ func TestCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCacheUnchanged(t *testing.T) {
|
||||
oldlog := logfunc.Load()
|
||||
SetLogger(t.Logf)
|
||||
t.Cleanup(func() {
|
||||
SetLogger(oldlog)
|
||||
})
|
||||
cacheFile := filepath.Join(t.TempDir(), "cache.json")
|
||||
|
||||
// Write initial cache value
|
||||
@@ -140,7 +130,7 @@ func TestCacheUnchanged(t *testing.T) {
|
||||
cachedDERPMap.Store(nil)
|
||||
|
||||
// Load the cache
|
||||
SetCachePath(cacheFile)
|
||||
SetCachePath(cacheFile, t.Logf)
|
||||
if cm := cachedDERPMap.Load(); !reflect.DeepEqual(initialCache, cm) {
|
||||
t.Fatalf("cached map was %+v; want %+v", cm, initialCache)
|
||||
}
|
||||
@@ -152,7 +142,7 @@ func TestCacheUnchanged(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
UpdateCache(initialCache)
|
||||
UpdateCache(initialCache, t.Logf)
|
||||
if _, err := os.Stat(cacheFile); !os.IsNotExist(err) {
|
||||
t.Fatalf("got err=%v; expected to not find cache file", err)
|
||||
}
|
||||
@@ -173,7 +163,7 @@ func TestCacheUnchanged(t *testing.T) {
|
||||
clonedNode.IPv4 = "1.2.3.5"
|
||||
updatedCache.Regions[99].Nodes = append(updatedCache.Regions[99].Nodes, &clonedNode)
|
||||
|
||||
UpdateCache(updatedCache)
|
||||
UpdateCache(updatedCache, t.Logf)
|
||||
if st, err := os.Stat(cacheFile); err != nil {
|
||||
t.Fatalf("could not stat cache file; err=%v", err)
|
||||
} else if !st.Mode().IsRegular() || st.Size() == 0 {
|
||||
|
||||
@@ -351,12 +351,6 @@ func (s *State) String() string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// ChangeFunc is a callback function (usually registered with
|
||||
// wgengine/monitor's Mon) that's called when the network
|
||||
// changed. The changed parameter is whether the network changed
|
||||
// enough for State to have changed since the last callback.
|
||||
type ChangeFunc func(changed bool, state *State)
|
||||
|
||||
// An InterfaceFilter indicates whether EqualFiltered should use i when deciding whether two States are equal.
|
||||
// ips are all the IPPrefixes associated with i.
|
||||
type InterfaceFilter func(i Interface, ips []netip.Prefix) bool
|
||||
|
||||
@@ -35,6 +35,10 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// ErrNoGatewayIndexFound is returned by DefaultRouteInterfaceIndex when no
|
||||
// default route is found.
|
||||
var ErrNoGatewayIndexFound = errors.New("no gateway index found")
|
||||
|
||||
// DefaultRouteInterfaceIndex returns the index of the network interface that
|
||||
// owns the default route. It returns the first IPv4 or IPv6 default route it
|
||||
// finds (it does not prefer one or the other).
|
||||
@@ -75,7 +79,7 @@ func DefaultRouteInterfaceIndex() (int, error) {
|
||||
return rm.Index, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("no gateway index found")
|
||||
return 0, ErrNoGatewayIndexFound
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -98,7 +98,22 @@ func likelyHomeRouterIPLinux() (ret netip.Addr, ok bool) {
|
||||
}
|
||||
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
|
||||
}
|
||||
return ret, ret.IsValid()
|
||||
if ret.IsValid() {
|
||||
return ret, true
|
||||
}
|
||||
if lineNum >= maxProcNetRouteRead {
|
||||
// If we went over our line limit without finding an answer, assume
|
||||
// we're a big fancy Linux router (or at least not a home system)
|
||||
// and set the error bit so we stop trying this in the future (and wasting CPU).
|
||||
// See https://github.com/tailscale/tailscale/issues/7621.
|
||||
//
|
||||
// Remember that "likelyHomeRouterIP" exists purely to find the port
|
||||
// mapping service (UPnP, PMP, PCP) often present on a home router. If we hit
|
||||
// the route (line) limit without finding an answer, we're unlikely to ever
|
||||
// find one in the future.
|
||||
procNetRouteErr.Store(true)
|
||||
}
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
// Android apps don't have permission to read /proc/net/route, at
|
||||
@@ -152,8 +167,8 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
// /proc/net/route. Use netlink to find the default route.
|
||||
//
|
||||
// TODO(bradfitz): this allocates a fair bit. We should track
|
||||
// this in wgengine/monitor instead and have
|
||||
// interfaces.GetState take a link monitor or similar so the
|
||||
// this in net/interfaces/monitor instead and have
|
||||
// interfaces.GetState take a netmon.Monitor or similar so the
|
||||
// routing table can be cached and the monitor's existing
|
||||
// subscription to route changes can update the cached state,
|
||||
// rather than querying the whole thing every time like
|
||||
|
||||
@@ -224,7 +224,7 @@ func TestStateString(t *testing.T) {
|
||||
},
|
||||
},
|
||||
InterfaceIPs: map[string][]netip.Prefix{
|
||||
"eth0": []netip.Prefix{
|
||||
"eth0": {
|
||||
netip.MustParsePrefix("10.0.0.2/8"),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/neterror"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/ping"
|
||||
"tailscale.com/net/portmapper"
|
||||
@@ -39,6 +40,7 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/nettype"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
@@ -158,6 +160,11 @@ type Client struct {
|
||||
// If nil, log.Printf is used.
|
||||
Logf logger.Logf
|
||||
|
||||
// NetMon optionally provides a netmon.Monitor to use to get the current
|
||||
// (cached) network interface.
|
||||
// If nil, the interface will be looked up dynamically.
|
||||
NetMon *netmon.Monitor
|
||||
|
||||
// TimeNow, if non-nil, is used instead of time.Now.
|
||||
TimeNow func() time.Time
|
||||
|
||||
@@ -208,8 +215,7 @@ type Client struct {
|
||||
// reusing an existing UDP connection.
|
||||
type STUNConn interface {
|
||||
WriteToUDPAddrPort([]byte, netip.AddrPort) (int, error)
|
||||
WriteTo([]byte, net.Addr) (int, error)
|
||||
ReadFrom([]byte) (int, net.Addr, error)
|
||||
ReadFromUDPAddrPort([]byte) (int, netip.AddrPort, error)
|
||||
}
|
||||
|
||||
func (c *Client) enoughRegions() int {
|
||||
@@ -518,9 +524,14 @@ func nodeMight4(n *tailcfg.DERPNode) bool {
|
||||
return ip.Is4()
|
||||
}
|
||||
|
||||
type packetReaderFromCloser interface {
|
||||
ReadFromUDPAddrPort([]byte) (int, netip.AddrPort, error)
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// readPackets reads STUN packets from pc until there's an error or ctx is done.
|
||||
// In either case, it closes pc.
|
||||
func (c *Client) readPackets(ctx context.Context, pc net.PacketConn) {
|
||||
func (c *Client) readPackets(ctx context.Context, pc packetReaderFromCloser) {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
@@ -534,7 +545,7 @@ func (c *Client) readPackets(ctx context.Context, pc net.PacketConn) {
|
||||
|
||||
var buf [64 << 10]byte
|
||||
for {
|
||||
n, addr, err := pc.ReadFrom(buf[:])
|
||||
n, addr, err := pc.ReadFromUDPAddrPort(buf[:])
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
@@ -542,16 +553,11 @@ func (c *Client) readPackets(ctx context.Context, pc net.PacketConn) {
|
||||
c.logf("ReadFrom: %v", err)
|
||||
return
|
||||
}
|
||||
ua, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
c.logf("ReadFrom: unexpected addr %T", addr)
|
||||
continue
|
||||
}
|
||||
pkt := buf[:n]
|
||||
if !stun.Is(pkt) {
|
||||
continue
|
||||
}
|
||||
if ap := netaddr.Unmap(ua.AddrPort()); ap.IsValid() {
|
||||
if ap := netaddr.Unmap(addr); ap.IsValid() {
|
||||
c.ReceiveSTUNPacket(pkt, ap)
|
||||
}
|
||||
}
|
||||
@@ -865,22 +871,29 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report,
|
||||
return c.finishAndStoreReport(rs, dm), nil
|
||||
}
|
||||
|
||||
ifState, err := interfaces.GetState()
|
||||
if err != nil {
|
||||
c.logf("[v1] interfaces: %v", err)
|
||||
return nil, err
|
||||
var ifState *interfaces.State
|
||||
if c.NetMon == nil {
|
||||
directState, err := interfaces.GetState()
|
||||
if err != nil {
|
||||
c.logf("[v1] interfaces: %v", err)
|
||||
return nil, err
|
||||
} else {
|
||||
ifState = directState
|
||||
}
|
||||
} else {
|
||||
ifState = c.NetMon.InterfaceState()
|
||||
}
|
||||
|
||||
// See if IPv6 works at all, or if it's been hard disabled at the
|
||||
// OS level.
|
||||
v6udp, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, "udp6", "[::1]:0")
|
||||
v6udp, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, c.NetMon)).ListenPacket(ctx, "udp6", "[::1]:0")
|
||||
if err == nil {
|
||||
rs.report.OSHasIPv6 = true
|
||||
v6udp.Close()
|
||||
}
|
||||
|
||||
// Create a UDP4 socket used for sending to our discovered IPv4 address.
|
||||
rs.pc4Hair, err = nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, "udp4", ":0")
|
||||
rs.pc4Hair, err = nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, c.NetMon)).ListenPacket(ctx, "udp4", ":0")
|
||||
if err != nil {
|
||||
c.logf("udp4: %v", err)
|
||||
return nil, err
|
||||
@@ -903,12 +916,14 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report,
|
||||
// So do that for now. In the future we might want to classify networks
|
||||
// that do and don't require this separately. But for now help it.
|
||||
const documentationIP = "203.0.113.1"
|
||||
rs.pc4Hair.WriteTo([]byte("tailscale netcheck; see https://github.com/tailscale/tailscale/issues/188"), &net.UDPAddr{IP: net.ParseIP(documentationIP), Port: 12345})
|
||||
rs.pc4Hair.WriteToUDPAddrPort(
|
||||
[]byte("tailscale netcheck; see https://github.com/tailscale/tailscale/issues/188"),
|
||||
netip.AddrPortFrom(netip.MustParseAddr(documentationIP), 12345))
|
||||
|
||||
if f := c.GetSTUNConn4; f != nil {
|
||||
rs.pc4 = f()
|
||||
} else {
|
||||
u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, "udp4", c.udpBindAddr())
|
||||
u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, nil)).ListenPacket(ctx, "udp4", c.udpBindAddr())
|
||||
if err != nil {
|
||||
c.logf("udp4: %v", err)
|
||||
return nil, err
|
||||
@@ -921,7 +936,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report,
|
||||
if f := c.GetSTUNConn6; f != nil {
|
||||
rs.pc6 = f()
|
||||
} else {
|
||||
u6, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, "udp6", c.udpBindAddr())
|
||||
u6, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, nil)).ListenPacket(ctx, "udp6", c.udpBindAddr())
|
||||
if err != nil {
|
||||
c.logf("udp6: %v", err)
|
||||
} else {
|
||||
@@ -929,6 +944,16 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report,
|
||||
go c.readPackets(ctx, u6)
|
||||
}
|
||||
}
|
||||
|
||||
// If our interfaces.State suggested we have IPv6 support but then we
|
||||
// failed to get an IPv6 sending socket (as in
|
||||
// https://github.com/tailscale/tailscale/issues/7949), then change
|
||||
// ifState.HaveV6 before we make a probe plan that involves sending IPv6
|
||||
// packets and thus assuming rs.pc6 is non-nil.
|
||||
if rs.pc6 == nil {
|
||||
ifState = ptr.To(*ifState) // shallow clone
|
||||
ifState.HaveV6 = false
|
||||
}
|
||||
}
|
||||
|
||||
plan := makeProbePlan(dm, ifState, last)
|
||||
@@ -1296,10 +1321,7 @@ func (c *Client) measureAllICMPLatency(ctx context.Context, rs *reportState, nee
|
||||
ctx, done := context.WithTimeout(ctx, icmpProbeTimeout)
|
||||
defer done()
|
||||
|
||||
p, err := ping.New(ctx, c.logf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := ping.New(ctx, c.logf, netns.Listener(c.logf, c.NetMon))
|
||||
defer p.Close()
|
||||
|
||||
c.logf("UDP is blocked, trying ICMP")
|
||||
@@ -1634,6 +1656,7 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
|
||||
Forward: net.DefaultResolver,
|
||||
UseLastGood: true,
|
||||
Logf: c.logf,
|
||||
NetMon: c.NetMon,
|
||||
}
|
||||
}
|
||||
resolver := c.resolver
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -156,9 +155,6 @@ func TestHairpinWait(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
}
|
||||
stunAddr, cleanup := stuntest.Serve(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -830,9 +826,6 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
|
||||
func TestNodeAddrResolve(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("TODO(#7876): test regressed on windows while CI was broken")
|
||||
}
|
||||
c := &Client{
|
||||
Logf: t.Logf,
|
||||
UDPBindAddr: "127.0.0.1:0",
|
||||
@@ -852,6 +845,29 @@ func TestNodeAddrResolve(t *testing.T) {
|
||||
// No IPv4 or IPv6 addrs
|
||||
}
|
||||
|
||||
// Checks whether IPv6 and IPv6 DNS resolution works on this platform.
|
||||
ipv6Works := func(t *testing.T) bool {
|
||||
// Verify that we can create an IPv6 socket.
|
||||
ln, err := net.ListenPacket("udp6", "[::1]:0")
|
||||
if err != nil {
|
||||
t.Logf("IPv6 may not work on this machine: %v", err)
|
||||
return false
|
||||
}
|
||||
ln.Close()
|
||||
|
||||
// Resolve a hostname that we know has an IPv6 address.
|
||||
addrs, err := net.DefaultResolver.LookupNetIP(context.Background(), "ip6", "google.com")
|
||||
if err != nil {
|
||||
t.Logf("IPv6 DNS resolution error: %v", err)
|
||||
return false
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
t.Logf("IPv6 DNS resolution returned no addresses")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for _, tt := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("UseDNSCache=%v", tt), func(t *testing.T) {
|
||||
@@ -869,6 +885,11 @@ func TestNodeAddrResolve(t *testing.T) {
|
||||
t.Logf("got IPv4 addr: %v", ap)
|
||||
})
|
||||
t.Run("IPv6", func(t *testing.T) {
|
||||
// Skip if IPv6 doesn't work on this machine.
|
||||
if !ipv6Works(t) {
|
||||
t.Skipf("IPv6 may not work on this machine")
|
||||
}
|
||||
|
||||
ap := c.nodeAddr(ctx, dn, probeIPv6)
|
||||
if !ap.IsValid() {
|
||||
t.Fatal("expected valid AddrPort")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Package monitor provides facilities for monitoring network
|
||||
// interface and route changes. It primarily exists to know when
|
||||
// portable devices move between different networks.
|
||||
package monitor
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -48,15 +48,15 @@ type osMon interface {
|
||||
IsInterestingInterface(iface string) bool
|
||||
}
|
||||
|
||||
// Mon represents a monitoring instance.
|
||||
type Mon struct {
|
||||
// Monitor represents a monitoring instance.
|
||||
type Monitor struct {
|
||||
logf logger.Logf
|
||||
om osMon // nil means not supported on this platform
|
||||
change chan struct{}
|
||||
stop chan struct{} // closed on Stop
|
||||
|
||||
mu sync.Mutex // guards all following fields
|
||||
cbs set.HandleSet[interfaces.ChangeFunc]
|
||||
cbs set.HandleSet[ChangeFunc]
|
||||
ruleDelCB set.HandleSet[RuleDeleteCallback]
|
||||
ifState *interfaces.State
|
||||
gwValid bool // whether gw and gwSelfIP are valid
|
||||
@@ -70,12 +70,17 @@ type Mon struct {
|
||||
timeJumped bool // whether we need to send a changed=true after a big time jump
|
||||
}
|
||||
|
||||
// ChangeFunc is a callback function registered with Monitor that's called when the
|
||||
// network changed. The changed parameter is whether the network changed
|
||||
// enough for State to have changed since the last callback.
|
||||
type ChangeFunc func(changed bool, state *interfaces.State)
|
||||
|
||||
// New instantiates and starts a monitoring instance.
|
||||
// The returned monitor is inactive until it's started by the Start method.
|
||||
// Use RegisterChangeCallback to get notified of network changes.
|
||||
func New(logf logger.Logf) (*Mon, error) {
|
||||
func New(logf logger.Logf) (*Monitor, error) {
|
||||
logf = logger.WithPrefix(logf, "monitor: ")
|
||||
m := &Mon{
|
||||
m := &Monitor{
|
||||
logf: logf,
|
||||
change: make(chan struct{}, 1),
|
||||
stop: make(chan struct{}),
|
||||
@@ -102,13 +107,13 @@ func New(logf logger.Logf) (*Mon, error) {
|
||||
// interfaces.
|
||||
//
|
||||
// The returned value is owned by Mon; it must not be modified.
|
||||
func (m *Mon) InterfaceState() *interfaces.State {
|
||||
func (m *Monitor) InterfaceState() *interfaces.State {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.ifState
|
||||
}
|
||||
|
||||
func (m *Mon) interfaceStateUncached() (*interfaces.State, error) {
|
||||
func (m *Monitor) interfaceStateUncached() (*interfaces.State, error) {
|
||||
return interfaces.GetState()
|
||||
}
|
||||
|
||||
@@ -117,7 +122,7 @@ func (m *Mon) interfaceStateUncached() (*interfaces.State, error) {
|
||||
//
|
||||
// It's the same as interfaces.LikelyHomeRouterIP, but it caches the
|
||||
// result until the monitor detects a network change.
|
||||
func (m *Mon) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) {
|
||||
func (m *Monitor) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.gwValid {
|
||||
@@ -133,7 +138,7 @@ func (m *Mon) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) {
|
||||
// RegisterChangeCallback adds callback to the set of parties to be
|
||||
// notified (in their own goroutine) when the network state changes.
|
||||
// To remove this callback, call unregister (or close the monitor).
|
||||
func (m *Mon) RegisterChangeCallback(callback interfaces.ChangeFunc) (unregister func()) {
|
||||
func (m *Monitor) RegisterChangeCallback(callback ChangeFunc) (unregister func()) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
handle := m.cbs.Add(callback)
|
||||
@@ -153,7 +158,7 @@ type RuleDeleteCallback func(table uint8, priority uint32)
|
||||
// RegisterRuleDeleteCallback adds callback to the set of parties to be
|
||||
// notified (in their own goroutine) when a Linux ip rule is deleted.
|
||||
// To remove this callback, call unregister (or close the monitor).
|
||||
func (m *Mon) RegisterRuleDeleteCallback(callback RuleDeleteCallback) (unregister func()) {
|
||||
func (m *Monitor) RegisterRuleDeleteCallback(callback RuleDeleteCallback) (unregister func()) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
handle := m.ruleDelCB.Add(callback)
|
||||
@@ -166,7 +171,7 @@ func (m *Mon) RegisterRuleDeleteCallback(callback RuleDeleteCallback) (unregiste
|
||||
|
||||
// Start starts the monitor.
|
||||
// A monitor can only be started & closed once.
|
||||
func (m *Mon) Start() {
|
||||
func (m *Monitor) Start() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.started || m.closed {
|
||||
@@ -187,7 +192,7 @@ func (m *Mon) Start() {
|
||||
}
|
||||
|
||||
// Close closes the monitor.
|
||||
func (m *Mon) Close() error {
|
||||
func (m *Monitor) Close() error {
|
||||
m.mu.Lock()
|
||||
if m.closed {
|
||||
m.mu.Unlock()
|
||||
@@ -218,7 +223,7 @@ func (m *Mon) Close() error {
|
||||
// change and re-check the state of the network. Any registered
|
||||
// ChangeFunc callbacks will be called within the event coalescing
|
||||
// period (under a fraction of a second).
|
||||
func (m *Mon) InjectEvent() {
|
||||
func (m *Monitor) InjectEvent() {
|
||||
select {
|
||||
case m.change <- struct{}{}:
|
||||
default:
|
||||
@@ -228,7 +233,7 @@ func (m *Mon) InjectEvent() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mon) stopped() bool {
|
||||
func (m *Monitor) stopped() bool {
|
||||
select {
|
||||
case <-m.stop:
|
||||
return true
|
||||
@@ -239,7 +244,7 @@ func (m *Mon) stopped() bool {
|
||||
|
||||
// pump continuously retrieves messages from the connection, notifying
|
||||
// the change channel of changes, and stopping when a stop is issued.
|
||||
func (m *Mon) pump() {
|
||||
func (m *Monitor) pump() {
|
||||
defer m.goroutines.Done()
|
||||
for !m.stopped() {
|
||||
msg, err := m.om.Receive()
|
||||
@@ -263,7 +268,7 @@ func (m *Mon) pump() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mon) notifyRuleDeleted(rdm ipRuleDeletedMessage) {
|
||||
func (m *Monitor) notifyRuleDeleted(rdm ipRuleDeletedMessage) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for _, cb := range m.ruleDelCB {
|
||||
@@ -274,13 +279,13 @@ func (m *Mon) notifyRuleDeleted(rdm ipRuleDeletedMessage) {
|
||||
// isInterestingInterface reports whether the provided interface should be
|
||||
// considered when checking for network state changes.
|
||||
// The ips parameter should be the IPs of the provided interface.
|
||||
func (m *Mon) isInterestingInterface(i interfaces.Interface, ips []netip.Prefix) bool {
|
||||
func (m *Monitor) isInterestingInterface(i interfaces.Interface, ips []netip.Prefix) bool {
|
||||
return m.om.IsInterestingInterface(i.Name) && interfaces.UseInterestingInterfaces(i, ips)
|
||||
}
|
||||
|
||||
// debounce calls the callback function with a delay between events
|
||||
// and exits when a stop is issued.
|
||||
func (m *Mon) debounce() {
|
||||
func (m *Monitor) debounce() {
|
||||
defer m.goroutines.Done()
|
||||
for {
|
||||
select {
|
||||
@@ -343,7 +348,7 @@ func wallTime() time.Time {
|
||||
return time.Now().Round(0)
|
||||
}
|
||||
|
||||
func (m *Mon) pollWallTime() {
|
||||
func (m *Monitor) pollWallTime() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.closed {
|
||||
@@ -365,7 +370,7 @@ const shouldMonitorTimeJump = runtime.GOOS != "android" && runtime.GOOS != "ios"
|
||||
// checkWallTimeAdvanceLocked reports whether wall time jumped more than 150% of
|
||||
// pollWallTimeInterval, indicating we probably just came out of sleep. Once a
|
||||
// time jump is detected it must be reset by calling resetTimeJumpedLocked.
|
||||
func (m *Mon) checkWallTimeAdvanceLocked() bool {
|
||||
func (m *Monitor) checkWallTimeAdvanceLocked() bool {
|
||||
if !shouldMonitorTimeJump {
|
||||
panic("unreachable") // if callers are correct
|
||||
}
|
||||
@@ -378,7 +383,7 @@ func (m *Mon) checkWallTimeAdvanceLocked() bool {
|
||||
}
|
||||
|
||||
// resetTimeJumpedLocked consumes the signal set by checkWallTimeAdvanceLocked.
|
||||
func (m *Mon) resetTimeJumpedLocked() {
|
||||
func (m *Monitor) resetTimeJumpedLocked() {
|
||||
m.timeJumped = false
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package monitor
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -24,7 +24,7 @@ type unspecifiedMessage struct{}
|
||||
|
||||
func (unspecifiedMessage) ignore() bool { return false }
|
||||
|
||||
func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) {
|
||||
func newOSMon(logf logger.Logf, _ *Monitor) (osMon, error) {
|
||||
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user