Compare commits
116 Commits
marwan/tmp
...
tom/derp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4df98808da | ||
|
|
1c4a047ad0 | ||
|
|
f8f0b981ac | ||
|
|
a353ae079b | ||
|
|
43e230d4cd | ||
|
|
5dd0b02133 | ||
|
|
d3c8c3dd00 | ||
|
|
64f16f7f38 | ||
|
|
6554a0cbec | ||
|
|
d17312265e | ||
|
|
4321d1d6e9 | ||
|
|
2492ca2900 | ||
|
|
570cb018da | ||
|
|
dc1d8826a2 | ||
|
|
67882ad35d | ||
|
|
07eacdfe92 | ||
|
|
d06fac0ede | ||
|
|
9d09c821f7 | ||
|
|
2aa8299c37 | ||
|
|
88ee857bc8 | ||
|
|
1a691ec5b2 | ||
|
|
6a156f6243 | ||
|
|
525b9c806f | ||
|
|
fc5b137d25 | ||
|
|
32e0ba5e68 | ||
|
|
399a80785e | ||
|
|
c0b4a54146 | ||
|
|
c4fe9c536d | ||
|
|
370b2c37e0 | ||
|
|
cb94ddb7b8 | ||
|
|
66f97f4bea | ||
|
|
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 |
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@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
|
||||
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
|
||||
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"
|
||||
|
||||
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
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
|
||||
18
.github/workflows/test.yml
vendored
18
.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
|
||||
@@ -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
|
||||
|
||||
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@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
|
||||
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
|
||||
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"
|
||||
|
||||
5
Makefile
5
Makefile
@@ -48,11 +48,10 @@ staticcheck: ## Run staticcheck.io checks
|
||||
./tool/go run honnef.co/go/tools/cmd/staticcheck -- $$(./tool/go list ./... | grep -v tempfork)
|
||||
|
||||
spk: ## Build synology package for ${SYNO_ARCH} architecture and ${SYNO_DSM} DSM version
|
||||
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o tailscale.spk --source=. --goarch=${SYNO_ARCH} --dsm-version=${SYNO_DSM}
|
||||
./tool/go run ./cmd/dist build synology/dsm${SYNO_DSM}/${SYNO_ARCH}
|
||||
|
||||
spkall: ## Build synology packages for all architectures and DSM versions
|
||||
mkdir -p spks
|
||||
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o spks --source=. --goarch=all --dsm-version=all
|
||||
./tool/go run ./cmd/dist build synology
|
||||
|
||||
pushspk: spk ## Push and install synology package on ${SYNO_HOST} host
|
||||
echo "Pushing SPK to root@${SYNO_HOST} (env var SYNO_HOST) ..."
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -49,4 +49,4 @@ while [ "$#" -gt 1 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
exec ./tool/go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"
|
||||
exec $go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/types/opt"
|
||||
)
|
||||
@@ -213,8 +212,20 @@ func (c *Client) DeleteDevice(ctx context.Context, deviceID string) (err error)
|
||||
|
||||
// AuthorizeDevice marks a device as authorized.
|
||||
func (c *Client) AuthorizeDevice(ctx context.Context, deviceID string) error {
|
||||
return c.SetAuthorized(ctx, deviceID, true)
|
||||
}
|
||||
|
||||
// SetAuthorized marks a device as authorized or not.
|
||||
func (c *Client) SetAuthorized(ctx context.Context, deviceID string, authorized bool) error {
|
||||
params := &struct {
|
||||
Authorized bool `json:"authorized"`
|
||||
}{Authorized: authorized}
|
||||
data, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := fmt.Sprintf("%s/api/v2/device/%s/authorized", c.baseURL(), url.PathEscape(deviceID))
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", path, strings.NewReader(`{"authorized":true}`))
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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,7 +15,6 @@ 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+
|
||||
@@ -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
|
||||
|
||||
@@ -12,15 +12,19 @@ import (
|
||||
"expvar"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -33,6 +37,7 @@ import (
|
||||
"tailscale.com/net/stun"
|
||||
"tailscale.com/tsweb"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -55,6 +60,8 @@ var (
|
||||
|
||||
acceptConnLimit = flag.Float64("accept-connection-limit", math.Inf(+1), "rate limit for accepting new connection")
|
||||
acceptConnBurst = flag.Int("accept-connection-burst", math.MaxInt, "burst limit for accepting new connection")
|
||||
|
||||
debugAddr = flag.String("debug_addr", "", "listening address for debug server")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -136,6 +143,7 @@ func main() {
|
||||
|
||||
if *dev {
|
||||
*addr = ":3340" // above the keys DERP
|
||||
*debugAddr = "localhost:8383"
|
||||
log.Printf("Running in dev mode.")
|
||||
tsweb.DevMode = true
|
||||
}
|
||||
@@ -222,6 +230,11 @@ func main() {
|
||||
go serveSTUN(listenHost, *stunPort)
|
||||
}
|
||||
|
||||
if *debugAddr != "" {
|
||||
log.Printf("running debug server on %s", *debugAddr)
|
||||
go runDebugServer(*debugAddr, s)
|
||||
}
|
||||
|
||||
quietLogger := log.New(logFilter{}, "", 0)
|
||||
httpsrv := &http.Server{
|
||||
Addr: *addr,
|
||||
@@ -513,3 +526,79 @@ func (logFilter) Write(p []byte) (int, error) {
|
||||
log.Printf("%s", p)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func runDebugServer(addr string, s *derp.Server) {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/debug/vars", http.DefaultServeMux) // serve out expvar
|
||||
mux.HandleFunc("/debug/varz", tsweb.VarzHandler) // expvar but in prometheus format
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
mux.Handle("/debug/pprofrate", http.HandlerFunc(handlePprofRate))
|
||||
|
||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
fmt.Fprintf(w, `<html><body>
|
||||
<h1>DERP</h1>
|
||||
<p>
|
||||
This is a Tailscale DERP debug server.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Version: <pre style="display: inline">%s</pre>
|
||||
<li><a href="/debug/vars">/debug/vars</a> (JSON)</li>
|
||||
<li><a href="/debug/varz">/debug/varz</a> (Prometheus)</li>
|
||||
<li><a href="/debug/pprof">/debug/pprof</a></li>
|
||||
<li><a href="/debug/pprof/goroutine?debug=1">/debug/pprof/goroutine?debug=1</a> (goroutine summary)</li>
|
||||
</ul>
|
||||
`, html.EscapeString(version.Long()))
|
||||
}))
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
}
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
if err != http.ErrServerClosed {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handlePprofRate adjusts the pprof rate.
|
||||
//
|
||||
// $ curl -d block=0 http://localhost:8383/debug/pprofrate
|
||||
// $ curl -d mutex=0 http://localhost:8383/debug/pprofrate
|
||||
//
|
||||
// See:
|
||||
//
|
||||
// - https://pkg.go.dev/runtime#SetMutexProfileFraction
|
||||
// - https://pkg.go.dev/runtime#SetBlockProfileRate
|
||||
//
|
||||
// And corp#5277.
|
||||
func handlePprofRate(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "method not allowed; want POST", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if s := r.FormValue("block"); s != "" {
|
||||
v, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
http.Error(w, "bad block value", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
runtime.SetBlockProfileRate(v)
|
||||
fmt.Fprintf(w, "block set to %v\n", v)
|
||||
}
|
||||
if s := r.FormValue("mutex"); s != "" {
|
||||
v, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
http.Error(w, "bad mutex value", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
old := runtime.SetMutexProfileFraction(v)
|
||||
fmt.Fprintf(w, "mutex changed from %v to %v\n", old, v)
|
||||
}
|
||||
}
|
||||
|
||||
25
cmd/dist/dist.go
vendored
25
cmd/dist/dist.go
vendored
@@ -13,15 +13,38 @@ import (
|
||||
|
||||
"tailscale.com/release/dist"
|
||||
"tailscale.com/release/dist/cli"
|
||||
"tailscale.com/release/dist/synology"
|
||||
"tailscale.com/release/dist/unixpkgs"
|
||||
)
|
||||
|
||||
var synologyPackageCenter bool
|
||||
|
||||
func getTargets() ([]dist.Target, error) {
|
||||
return unixpkgs.Targets(), nil
|
||||
var ret []dist.Target
|
||||
|
||||
ret = append(ret, unixpkgs.Targets()...)
|
||||
// Synology packages can be built either for sideloading, or for
|
||||
// distribution by Synology in their package center. When
|
||||
// distributed through the package center, apps can request
|
||||
// additional permissions to use a tuntap interface and control
|
||||
// the NAS's network stack, rather than be forced to run in
|
||||
// userspace mode.
|
||||
//
|
||||
// Since only we can provide packages to Synology for
|
||||
// distribution, we default to building the "sideload" variant of
|
||||
// packages that we distribute on pkgs.tailscale.com.
|
||||
ret = append(ret, synology.Targets(synologyPackageCenter)...)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd := cli.CLI(getTargets)
|
||||
for _, subcmd := range cmd.Subcommands {
|
||||
if subcmd.Name == "build" {
|
||||
subcmd.FlagSet.BoolVar(&synologyPackageCenter, "synology-package-center", false, "build synology packages with extra metadata for the official package center")
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil && !errors.Is(err, flag.ErrHelp) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/transport"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
@@ -38,7 +37,6 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
"sigs.k8s.io/yaml"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/hostinfo"
|
||||
@@ -48,6 +46,7 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -64,6 +63,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)
|
||||
)
|
||||
@@ -183,32 +183,33 @@ waitOnline:
|
||||
// the cache that sits a few layers below the builder stuff, which will
|
||||
// implicitly filter what parts of the world the builder code gets to see at
|
||||
// all.
|
||||
nsFilter := cache.ObjectSelector{
|
||||
Field: fields.SelectorFromSet(fields.Set{"metadata.namespace": tsNamespace}),
|
||||
nsFilter := cache.ByObject{
|
||||
Field: client.InNamespace(tsNamespace).AsSelector(),
|
||||
}
|
||||
restConfig := config.GetConfigOrDie()
|
||||
mgr, err := manager.New(restConfig, manager.Options{
|
||||
NewCache: cache.BuilderWithOptions(cache.Options{
|
||||
SelectorsByObject: map[client.Object]cache.ObjectSelector{
|
||||
Cache: cache.Options{
|
||||
ByObject: map[client.Object]cache.ByObject{
|
||||
&corev1.Secret{}: nsFilter,
|
||||
&appsv1.StatefulSet{}: nsFilter,
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
startlog.Fatalf("could not create manager: %v", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(_ context.Context, o client.Object) []reconcile.Request {
|
||||
ls := o.GetLabels()
|
||||
if ls[LabelManaged] != "true" {
|
||||
return nil
|
||||
@@ -228,14 +229,14 @@ waitOnline:
|
||||
err = builder.
|
||||
ControllerManagedBy(mgr).
|
||||
For(&corev1.Service{}).
|
||||
Watches(&source.Kind{Type: &appsv1.StatefulSet{}}, reconcileFilter).
|
||||
Watches(&source.Kind{Type: &corev1.Secret{}}, reconcileFilter).
|
||||
Watches(&appsv1.StatefulSet{}, reconcileFilter).
|
||||
Watches(&corev1.Secret{}, reconcileFilter).
|
||||
Complete(sr)
|
||||
if err != nil {
|
||||
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 +279,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 +568,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 +594,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 +639,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 })
|
||||
}
|
||||
|
||||
@@ -64,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
|
||||
@@ -110,6 +110,8 @@ func TestLoadBalancerClass(t *testing.T) {
|
||||
mustUpdate(t, fc, "default", "test", func(s *corev1.Service) {
|
||||
s.Spec.Type = corev1.ServiceTypeClusterIP
|
||||
s.Spec.LoadBalancerClass = nil
|
||||
})
|
||||
mustUpdateStatus(t, fc, "default", "test", func(s *corev1.Service) {
|
||||
// Fake client doesn't automatically delete the LoadBalancer status when
|
||||
// changing away from the LoadBalancer type, we have to do
|
||||
// controller-manager's work by hand.
|
||||
@@ -185,7 +187,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",
|
||||
@@ -282,7 +284,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
|
||||
@@ -326,7 +328,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{
|
||||
@@ -398,7 +400,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
|
||||
@@ -447,6 +449,8 @@ func TestLBIntoAnnotation(t *testing.T) {
|
||||
}
|
||||
s.Spec.Type = corev1.ServiceTypeClusterIP
|
||||
s.Spec.LoadBalancerClass = nil
|
||||
})
|
||||
mustUpdateStatus(t, fc, "default", "test", func(s *corev1.Service) {
|
||||
// Fake client doesn't automatically delete the LoadBalancer status when
|
||||
// changing away from the LoadBalancer type, we have to do
|
||||
// controller-manager's work by hand.
|
||||
@@ -455,7 +459,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{
|
||||
@@ -522,7 +526,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",
|
||||
@@ -581,6 +585,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{
|
||||
@@ -629,7 +678,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",
|
||||
@@ -658,6 +707,7 @@ func expectedSTS(stsName, secretName, hostname string) *appsv1.StatefulSet {
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ServiceAccountName: "proxies",
|
||||
PriorityClassName: priorityClassName,
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "sysctler",
|
||||
@@ -731,6 +781,21 @@ func mustUpdate[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, n
|
||||
}
|
||||
}
|
||||
|
||||
func mustUpdateStatus[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
|
||||
t.Helper()
|
||||
obj := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}, obj); err != nil {
|
||||
t.Fatalf("getting %q: %v", name, err)
|
||||
}
|
||||
update(obj)
|
||||
if err := client.Status().Update(context.Background(), obj); err != nil {
|
||||
t.Fatalf("updating %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func expectEqual[T any, O ptrObject[T]](t *testing.T, client client.Client, want O) {
|
||||
t.Helper()
|
||||
got := O(new(T))
|
||||
@@ -814,7 +879,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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -152,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+
|
||||
|
||||
@@ -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+
|
||||
@@ -245,7 +257,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 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
|
||||
@@ -270,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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -50,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"
|
||||
@@ -330,12 +331,16 @@ var debugMux *http.ServeMux
|
||||
|
||||
func run() error {
|
||||
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)
|
||||
@@ -386,10 +391,10 @@ func run() error {
|
||||
debugMux = newDebugMux()
|
||||
}
|
||||
|
||||
return startIPNServer(context.Background(), logf, pol.PublicID, netMon)
|
||||
return startIPNServer(context.Background(), logf, pol.PublicID, sys)
|
||||
}
|
||||
|
||||
func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) 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)
|
||||
@@ -415,7 +420,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
|
||||
}
|
||||
}()
|
||||
|
||||
srv := ipnserver.New(logf, logID, netMon)
|
||||
srv := ipnserver.New(logf, logID, sys.NetMon.Get())
|
||||
if debugMux != nil {
|
||||
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)
|
||||
}
|
||||
@@ -433,7 +438,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
|
||||
return
|
||||
}
|
||||
}
|
||||
lb, err := getLocalBackend(ctx, logf, logID, netMon)
|
||||
lb, err := getLocalBackend(ctx, logf, logID, sys)
|
||||
if err == nil {
|
||||
logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond))
|
||||
srv.SetLocalBackend(lb)
|
||||
@@ -457,31 +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, netMon *netmon.Monitor) (_ *ipnlocal.LocalBackend, retErr error) {
|
||||
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) (_ *ipnlocal.LocalBackend, retErr error) {
|
||||
if logPol != nil {
|
||||
logPol.Logtail.SetNetMon(netMon)
|
||||
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, netMon, 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)
|
||||
}
|
||||
@@ -489,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
|
||||
@@ -519,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)
|
||||
}
|
||||
@@ -554,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, netMon *netmon.Monitor, 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, netMon, 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
|
||||
@@ -593,21 +595,23 @@ func handleSubnetsInNetstack() bool {
|
||||
|
||||
var tstunNew = tstun.New
|
||||
|
||||
func tryEngine(logf logger.Logf, netMon *netmon.Monitor, 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,
|
||||
NetMon: netMon,
|
||||
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 {
|
||||
@@ -620,44 +624,55 @@ func tryEngine(logf logger.Logf, netMon *netmon.Monitor, dialer *tsdial.Dialer,
|
||||
// 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, netMon)
|
||||
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 {
|
||||
@@ -687,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
|
||||
|
||||
@@ -47,6 +47,7 @@ import (
|
||||
"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"
|
||||
@@ -125,6 +126,10 @@ var syslogf logger.Logf = logger.Discard
|
||||
// At this point we're still the parent process that
|
||||
// Windows started.
|
||||
func runWindowsService(pol *logpolicy.Policy) error {
|
||||
go func() {
|
||||
winutil.LogSupportInfo(log.Printf)
|
||||
}()
|
||||
|
||||
if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 {
|
||||
syslog, err := eventlog.Open(serviceName)
|
||||
if err == nil {
|
||||
@@ -292,13 +297,15 @@ func beWindowsSubprocess() bool {
|
||||
}
|
||||
}()
|
||||
|
||||
sys := new(tsd.System)
|
||||
netMon, err := netmon.New(log.Printf)
|
||||
if err != nil {
|
||||
log.Printf("Could not create netMon: %v", err)
|
||||
netMon = nil
|
||||
log.Fatalf("Could not create netMon: %v", err)
|
||||
}
|
||||
sys.Set(netMon)
|
||||
|
||||
publicLogID, _ := logid.ParsePublicID(logID)
|
||||
err = startIPNServer(ctx, log.Printf, publicLogID, netMon)
|
||||
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"
|
||||
@@ -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, nil /* no netMon */)
|
||||
lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, controlclient.LoginEphemeral)
|
||||
lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginEphemeral)
|
||||
if err != nil {
|
||||
log.Fatalf("ipnlocal.NewLocalBackend: %v", err)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}{})
|
||||
|
||||
@@ -61,6 +61,7 @@ import (
|
||||
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
|
||||
@@ -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,13 +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.MakeLookupFunc(opts.Logf, opts.NetMon),
|
||||
Logf: opts.Logf,
|
||||
NetMon: opts.NetMon,
|
||||
}
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
||||
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
|
||||
@@ -250,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 {
|
||||
@@ -1509,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, c.logf, c.netMon, 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,6 +19,7 @@ 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"
|
||||
@@ -158,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
|
||||
@@ -179,13 +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, logf logger.Logf, netMon *netmon.Monitor, 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
|
||||
}
|
||||
@@ -205,16 +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,
|
||||
logf: logf,
|
||||
netMon: netMon,
|
||||
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
|
||||
@@ -373,6 +403,7 @@ 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,
|
||||
|
||||
@@ -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, nil, 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.
|
||||
@@ -392,13 +408,7 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr,
|
||||
NetMon: a.NetMon,
|
||||
}
|
||||
} else {
|
||||
dns = &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,
|
||||
}
|
||||
dns = a.resolver()
|
||||
}
|
||||
|
||||
var dialer dnscache.DialContextFunc
|
||||
|
||||
@@ -67,6 +67,11 @@ 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
|
||||
|
||||
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()
|
||||
|
||||
@@ -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-lirn07XE3JOS6oiwZBMwxzywkbXHowOJUMWWLrZtccY=
|
||||
# nix-direnv cache busting line: sha256-l2uIma2oEdSN0zVo9BOFJF2gC3S60vXwTLVadv8yQPo=
|
||||
|
||||
280
go.mod
280
go.mod
@@ -3,98 +3,98 @@ 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.3
|
||||
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-logr/zapr v1.2.3
|
||||
github.com/go-json-experiment/json v0.0.0-20230321051131-ccbac49a6929
|
||||
github.com/go-logr/zapr v1.2.4
|
||||
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.13
|
||||
github.com/mattn/go-isatty v0.0.17
|
||||
github.com/mdlayher/genetlink v1.2.0
|
||||
github.com/mdlayher/netlink v1.7.1
|
||||
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
|
||||
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-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.9.0
|
||||
golang.org/x/net v0.8.0
|
||||
golang.org/x/oauth2 v0.5.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.6.0
|
||||
golang.org/x/term v0.6.0
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
|
||||
golang.org/x/tools v0.7.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.10.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.8.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.9.1
|
||||
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
|
||||
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.27.2
|
||||
k8s.io/apimachinery v0.27.2
|
||||
k8s.io/client-go v0.27.2
|
||||
nhooyr.io/websocket v1.8.7
|
||||
sigs.k8s.io/controller-runtime v0.13.1
|
||||
sigs.k8s.io/controller-runtime v0.15.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0
|
||||
)
|
||||
@@ -102,37 +102,38 @@ require (
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
filippo.io/edwards25519 v1.0.0-rc.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.3 // 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.1.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/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/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.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/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
|
||||
@@ -141,41 +142,40 @@ require (
|
||||
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.1.1 // indirect
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // 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.10 // indirect
|
||||
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // 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-tingaikin/go-header v0.4.3 // indirect
|
||||
github.com/docker/cli v20.10.16+incompatible // 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/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.15.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/gliderlabs/ssh v0.3.3 // indirect
|
||||
github.com/go-critic/go-critic v0.7.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-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
|
||||
@@ -187,7 +187,7 @@ require (
|
||||
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-20220329215616-d24fe342adfe // indirect
|
||||
@@ -197,13 +197,13 @@ require (
|
||||
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/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
|
||||
github.com/goreleaser/chglog v0.1.2 // 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
|
||||
@@ -214,9 +214,9 @@ require (
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // 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
|
||||
@@ -226,10 +226,11 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/julz/importas v0.1.0 // indirect
|
||||
github.com/junk1tm/musttag v0.5.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.1.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
|
||||
@@ -237,18 +238,18 @@ require (
|
||||
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.4.0 // 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.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // 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.13 // 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/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
|
||||
@@ -261,76 +262,79 @@ require (
|
||||
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.9.5 // indirect
|
||||
github.com/nishanths/exhaustive v0.10.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.9.0 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.11.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // 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.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // 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 v1.4.0 // 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.19 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // 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/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect
|
||||
github.com/sassoftware/go-rpmutils v0.1.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.2.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.9.0 // indirect
|
||||
github.com/sivchari/containedctx v1.0.2 // 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.8.2 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.6.1 // 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.12.0 // indirect
|
||||
github.com/spf13/viper v1.15.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.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.1 // 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-20221125081123-e39cf3fc478e // 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-20221213070652-c3537552635f // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // 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.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/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
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // 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.3.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
|
||||
@@ -339,15 +343,15 @@ require (
|
||||
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.4.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.27.2 // indirect
|
||||
k8s.io/component-base v0.27.2 // 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-20221223090309-7455f1af531d // 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-lirn07XE3JOS6oiwZBMwxzywkbXHowOJUMWWLrZtccY=
|
||||
sha256-l2uIma2oEdSN0zVo9BOFJF2gC3S60vXwTLVadv8yQPo=
|
||||
|
||||
@@ -1 +1 @@
|
||||
ddff070c02790cb571006e820e58cce9627569cf
|
||||
480a0c381923c53e70ed5e72f9a9f79ce1884859
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -100,11 +101,13 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
|
||||
}
|
||||
|
||||
if pair, err := getCertPEMCached(cs, domain, now); err == nil {
|
||||
future := now.AddDate(0, 0, 14)
|
||||
if b.shouldStartDomainRenewal(cs, domain, future) {
|
||||
shouldRenew, err := shouldStartDomainRenewal(domain, now, pair)
|
||||
if err != nil {
|
||||
logf("error checking for certificate renewal: %v", err)
|
||||
} else if shouldRenew {
|
||||
logf("starting async renewal")
|
||||
// Start renewal in the background.
|
||||
go b.getCertPEM(context.Background(), cs, logf, traceACME, domain, future)
|
||||
go b.getCertPEM(context.Background(), cs, logf, traceACME, domain, now)
|
||||
}
|
||||
return pair, nil
|
||||
}
|
||||
@@ -117,18 +120,41 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
|
||||
return pair, nil
|
||||
}
|
||||
|
||||
func (b *LocalBackend) shouldStartDomainRenewal(cs certStore, domain string, future time.Time) bool {
|
||||
func shouldStartDomainRenewal(domain string, now time.Time, pair *TLSCertKeyPair) (bool, error) {
|
||||
renewMu.Lock()
|
||||
defer renewMu.Unlock()
|
||||
now := time.Now()
|
||||
if last, ok := lastRenewCheck[domain]; ok && now.Sub(last) < time.Minute {
|
||||
// We checked very recently. Don't bother reparsing &
|
||||
// validating the x509 cert.
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
lastRenewCheck[domain] = now
|
||||
_, err := getCertPEMCached(cs, domain, future)
|
||||
return errors.Is(err, errCertExpired)
|
||||
|
||||
block, _ := pem.Decode(pair.CertPEM)
|
||||
if block == nil {
|
||||
return false, fmt.Errorf("parsing certificate PEM")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("parsing certificate: %w", err)
|
||||
}
|
||||
|
||||
certLifetime := cert.NotAfter.Sub(cert.NotBefore)
|
||||
if certLifetime < 0 {
|
||||
return false, fmt.Errorf("negative certificate lifetime %v", certLifetime)
|
||||
}
|
||||
|
||||
// Per https://github.com/tailscale/tailscale/issues/8204, check
|
||||
// whether we're more than 2/3 of the way through the certificate's
|
||||
// lifetime, which is the officially-recommended best practice by Let's
|
||||
// Encrypt.
|
||||
renewalDuration := certLifetime * 2 / 3
|
||||
renewAt := cert.NotBefore.Add(renewalDuration)
|
||||
|
||||
if now.After(renewAt) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// certStore provides a way to perist and retrieve TLS certificates.
|
||||
@@ -361,17 +387,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 {
|
||||
|
||||
@@ -6,12 +6,19 @@
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"embed"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/exp/maps"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
)
|
||||
|
||||
@@ -100,3 +107,94 @@ func TestCertStoreRoundTrip(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldStartDomainRenewal(t *testing.T) {
|
||||
reset := func() {
|
||||
renewMu.Lock()
|
||||
defer renewMu.Unlock()
|
||||
maps.Clear(lastRenewCheck)
|
||||
}
|
||||
|
||||
mustMakePair := func(template *x509.Certificate) *TLSCertKeyPair {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
})
|
||||
|
||||
return &TLSCertKeyPair{
|
||||
Cached: false,
|
||||
CertPEM: certPEM,
|
||||
KeyPEM: []byte("unused"),
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Unix(1685714838, 0)
|
||||
subject := pkix.Name{
|
||||
Organization: []string{"Tailscale, Inc."},
|
||||
Country: []string{"CA"},
|
||||
Province: []string{"ON"},
|
||||
Locality: []string{"Toronto"},
|
||||
StreetAddress: []string{"290 Bremner Blvd"},
|
||||
PostalCode: []string{"M5V 3L9"},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
notBefore time.Time
|
||||
lifetime time.Duration
|
||||
want bool
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "should renew",
|
||||
notBefore: now.AddDate(0, 0, -89),
|
||||
lifetime: 90 * 24 * time.Hour,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "short-lived renewal",
|
||||
notBefore: now.AddDate(0, 0, -7),
|
||||
lifetime: 10 * 24 * time.Hour,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no renew",
|
||||
notBefore: now.AddDate(0, 0, -59), // 59 days ago == not 2/3rds of the way through 90 days yet
|
||||
lifetime: 90 * 24 * time.Hour,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reset()
|
||||
|
||||
ret, err := shouldStartDomainRenewal("example.com", now, mustMakePair(&x509.Certificate{
|
||||
SerialNumber: big.NewInt(2019),
|
||||
Subject: subject,
|
||||
NotBefore: tt.notBefore,
|
||||
NotAfter: tt.notBefore.Add(tt.lifetime),
|
||||
}))
|
||||
|
||||
if tt.wantErr != "" {
|
||||
if err == nil {
|
||||
t.Errorf("wanted error, got nil")
|
||||
} else if err.Error() != tt.wantErr {
|
||||
t.Errorf("got err=%q, want %q", err.Error(), tt.wantErr)
|
||||
}
|
||||
} else {
|
||||
if ret != tt.want {
|
||||
t.Errorf("got ret=%v, want %v", ret, tt.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,10 +138,11 @@ 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
|
||||
unregisterNetMon func()
|
||||
unregisterHealthWatch func()
|
||||
@@ -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,10 +292,7 @@ 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()
|
||||
if err != nil {
|
||||
logf("skipping portlist: %s", err)
|
||||
}
|
||||
portpoll := new(portlist.Poller)
|
||||
|
||||
b := &LocalBackend{
|
||||
ctx: ctx,
|
||||
@@ -301,10 +300,11 @@ 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,
|
||||
@@ -313,7 +313,8 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
|
||||
loginFlags: loginFlags,
|
||||
}
|
||||
|
||||
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, e.GetNetMon())
|
||||
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,7 +331,6 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
|
||||
b.statusChanged = sync.NewCond(&b.statusLock)
|
||||
b.e.SetStatusCallback(b.setWgengineStatus)
|
||||
|
||||
netMon := e.GetNetMon()
|
||||
b.prevIfState = netMon.InterfaceState()
|
||||
// Call our linkChange code once with the current state, and
|
||||
// then also whenever it changes:
|
||||
@@ -339,14 +339,9 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -464,6 +459,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
|
||||
}
|
||||
@@ -644,7 +640,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 {
|
||||
@@ -1315,8 +1311,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 +1374,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 +1396,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 +1418,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
HTTPTestClient: httpTestClient,
|
||||
DiscoPublicKey: discoPublic,
|
||||
DebugFlags: debugFlags,
|
||||
NetMon: b.e.GetNetMon(),
|
||||
NetMon: b.sys.NetMon.Get(),
|
||||
Pinger: b,
|
||||
PopBrowserURL: b.tellClientToBrowseToURL,
|
||||
OnClientVersion: b.onClientVersion,
|
||||
@@ -1813,11 +1808,30 @@ func dnsMapsEqual(new, old *netmap.NetworkMap) bool {
|
||||
// readPoller is a goroutine that receives service lists from
|
||||
// b.portpoll and propagates them into the controlclient's HostInfo.
|
||||
func (b *LocalBackend) readPoller() {
|
||||
n := 0
|
||||
isFirst := true
|
||||
ticker := time.NewTicker(portlist.PollInterval())
|
||||
defer ticker.Stop()
|
||||
initChan := make(chan struct{})
|
||||
close(initChan)
|
||||
for {
|
||||
ports, ok := <-b.portpoll.Updates()
|
||||
if !ok {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-b.ctx.Done():
|
||||
return
|
||||
case <-initChan:
|
||||
// Preserving old behavior: readPoller should
|
||||
// immediately poll the first time, then wait
|
||||
// for a tick after.
|
||||
initChan = nil
|
||||
}
|
||||
|
||||
ports, changed, err := b.portpoll.Poll()
|
||||
if err != nil {
|
||||
b.logf("error polling for open ports: %v", err)
|
||||
return
|
||||
}
|
||||
if !changed {
|
||||
continue
|
||||
}
|
||||
sl := []tailcfg.Service{}
|
||||
for _, p := range ports {
|
||||
@@ -1841,8 +1855,8 @@ func (b *LocalBackend) readPoller() {
|
||||
|
||||
b.doSetHostinfoFilterServices(hi)
|
||||
|
||||
n++
|
||||
if n == 1 {
|
||||
if isFirst {
|
||||
isFirst = false
|
||||
close(b.gotPortPollRes)
|
||||
}
|
||||
}
|
||||
@@ -2479,7 +2493,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 +3331,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 +3642,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 +4065,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 +4416,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 +4562,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)
|
||||
}
|
||||
|
||||
@@ -449,6 +449,8 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
|
||||
filtered[i] = b.tka.filtered[i].Clone()
|
||||
}
|
||||
|
||||
stateID1, _ := b.tka.authority.StateIDs()
|
||||
|
||||
return &ipnstate.NetworkLockStatus{
|
||||
Enabled: true,
|
||||
Head: &head,
|
||||
@@ -457,6 +459,7 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
|
||||
NodeKeySigned: selfAuthorized,
|
||||
TrustedKeys: outKeys,
|
||||
FilteredPeers: filtered,
|
||||
StateID: stateID1,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = ""
|
||||
}
|
||||
|
||||
@@ -1090,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 {
|
||||
@@ -1234,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,10 @@ import (
|
||||
"tailscale.com/util/winutil"
|
||||
)
|
||||
|
||||
var errAlreadyMigrated = errors.New("profile migration already completed")
|
||||
|
||||
var debug = envknob.RegisterBool("TS_DEBUG_PROFILES")
|
||||
|
||||
// profileManager is a wrapper around a StateStore that manages
|
||||
// multiple profiles and the current profile.
|
||||
type profileManager struct {
|
||||
@@ -40,6 +44,13 @@ type profileManager struct {
|
||||
isNewProfile bool
|
||||
}
|
||||
|
||||
func (pm *profileManager) dlogf(format string, args ...any) {
|
||||
if !debug() {
|
||||
return
|
||||
}
|
||||
pm.logf(format, args...)
|
||||
}
|
||||
|
||||
// CurrentUserID returns the current user ID. It is only non-empty on
|
||||
// Windows where we have a multi-user system.
|
||||
func (pm *profileManager) CurrentUserID() ipn.WindowsUserID {
|
||||
@@ -64,9 +75,11 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
|
||||
// Read the CurrentProfileKey from the store which stores
|
||||
// the selected profile for the current user.
|
||||
b, err := pm.store.ReadState(ipn.CurrentProfileKey(string(uid)))
|
||||
pm.dlogf("SetCurrentUserID: ReadState(%q) = %v, %v", string(uid), len(b), err)
|
||||
if err == ipn.ErrStateNotExist || len(b) == 0 {
|
||||
if runtime.GOOS == "windows" {
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil {
|
||||
pm.dlogf("SetCurrentUserID: windows: migrating from legacy preferences")
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil && !errors.Is(err, errAlreadyMigrated) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -79,6 +92,7 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
|
||||
pk := ipn.StateKey(string(b))
|
||||
prof := pm.findProfileByKey(pk)
|
||||
if prof == nil {
|
||||
pm.dlogf("SetCurrentUserID: no profile found for key: %q", pk)
|
||||
pm.NewProfile()
|
||||
return nil
|
||||
}
|
||||
@@ -544,8 +558,16 @@ 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.
|
||||
pm.dlogf("no known profiles; trying to migrate from legacy prefs")
|
||||
if err := pm.migrateFromLegacyPrefs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -562,13 +584,15 @@ 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)
|
||||
}
|
||||
pm.dlogf("loaded legacy preferences; sentinel=%q", sentinel)
|
||||
if err := pm.SetPrefs(prefs); err != nil {
|
||||
metricMigrationError.Add(1)
|
||||
return fmt.Errorf("migrating _daemon profile: %w", err)
|
||||
}
|
||||
pm.completeMigration(sentinel)
|
||||
pm.dlogf("completed legacy preferences migration with sentinel=%q", sentinel)
|
||||
metricMigrationSuccess.Add(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -41,6 +40,7 @@ func legacyPrefsDir(uid ipn.WindowsUserID) (string, error) {
|
||||
func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
|
||||
userLegacyPrefsDir, err := legacyPrefsDir(pm.currentUserID)
|
||||
if err != nil {
|
||||
pm.dlogf("no legacy preferences directory for %q: %v", pm.currentUserID, err)
|
||||
return "", ipn.PrefsView{}, err
|
||||
}
|
||||
|
||||
@@ -48,14 +48,20 @@ func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
|
||||
// verify that migration sentinel is not present
|
||||
_, err = os.Stat(migrationSentinel)
|
||||
if err == nil {
|
||||
pm.dlogf("migration sentinel %q already exists", migrationSentinel)
|
||||
return "", ipn.PrefsView{}, errAlreadyMigrated
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
pm.dlogf("os.Stat(%q) = %v", migrationSentinel, err)
|
||||
return "", ipn.PrefsView{}, err
|
||||
}
|
||||
|
||||
prefsPath := filepath.Join(userLegacyPrefsDir, legacyPrefsFile+legacyPrefsExt)
|
||||
prefs, err := ipn.LoadPrefs(prefsPath)
|
||||
pm.dlogf("ipn.LoadPrefs(%q) = %v, %v", prefsPath, prefs, err)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return "", ipn.PrefsView{}, errAlreadyMigrated
|
||||
}
|
||||
if err != nil {
|
||||
return "", ipn.PrefsView{}, err
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ func (s *serveListener) Run() {
|
||||
}
|
||||
|
||||
func (s *serveListener) shouldWarnAboutListenError(err error) bool {
|
||||
if !s.b.e.GetNetMon().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
|
||||
@@ -447,6 +447,8 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.Reverse
|
||||
Rewrite: func(r *httputil.ProxyRequest) {
|
||||
r.SetURL(u)
|
||||
r.Out.Host = r.In.Host
|
||||
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
|
||||
r.Out.Header.Set("X-Forwarded-Proto", "https")
|
||||
if c, ok := r.Out.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext); ok {
|
||||
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
|
||||
}
|
||||
|
||||
@@ -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,7 @@ import (
|
||||
type Server struct {
|
||||
lb atomic.Pointer[ipnlocal.LocalBackend]
|
||||
logf logger.Logf
|
||||
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
|
||||
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
|
||||
@@ -410,14 +410,15 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit
|
||||
}
|
||||
|
||||
// New returns a new Server.
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface
|
||||
// lookups.
|
||||
//
|
||||
// To start it, use the Server.Run method.
|
||||
//
|
||||
// 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, netMon *netmon.Monitor) *Server {
|
||||
if netMon == nil {
|
||||
panic("nil netMon")
|
||||
}
|
||||
return &Server{
|
||||
backendLogID: logID,
|
||||
logf: logf,
|
||||
|
||||
@@ -121,6 +121,11 @@ type NetworkLockStatus struct {
|
||||
// (i.e. no connectivity) because they failed tailnet lock
|
||||
// checks.
|
||||
FilteredPeers []*TKAFilteredPeer
|
||||
|
||||
// StateID is a nonce associated with the network lock authority,
|
||||
// generated upon enablement. This field is not populated if the
|
||||
// network lock is disabled.
|
||||
StateID uint64
|
||||
}
|
||||
|
||||
// NetworkLockUpdate describes a change to network-lock state.
|
||||
@@ -583,6 +588,8 @@ func osEmoji(os string) string {
|
||||
return "🖥️"
|
||||
case "iOS":
|
||||
return "📱"
|
||||
case "tvOS":
|
||||
return "🍎📺"
|
||||
case "android":
|
||||
return "🤖"
|
||||
case "freebsd":
|
||||
|
||||
@@ -930,8 +930,8 @@ func InUseOtherUserIPNStream(w http.ResponseWriter, r *http.Request, err error)
|
||||
}
|
||||
|
||||
func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "denied", http.StatusForbidden)
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "watch ipn bus access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
f, ok := w.(http.Flusher)
|
||||
|
||||
@@ -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.8.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/+/v0.6.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.6.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.8.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.10.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.8.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.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.17/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-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.8.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/+/v0.6.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.6.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.8.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.10.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.8.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.27.2/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.9.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.8.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/+/v0.6.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.6.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.8.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.10.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.8.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))
|
||||
|
||||
@@ -13,19 +13,19 @@ 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"
|
||||
@@ -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,
|
||||
|
||||
@@ -186,7 +182,6 @@ type Logger struct {
|
||||
flushPending atomic.Bool
|
||||
sentinel chan int32
|
||||
timeNow func() time.Time
|
||||
bo *backoff.Backoff
|
||||
zstdEncoder Encoder
|
||||
uploadCancel func()
|
||||
explainedRaw bool
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -143,10 +143,6 @@ func (r *Resolver) cloudHostResolver() (v *net.Resolver, ok bool) {
|
||||
switch runtime.GOOS {
|
||||
case "android", "ios", "darwin":
|
||||
return nil, false
|
||||
case "windows":
|
||||
// TODO(bradfitz): remove this restriction once we're using Go 1.19
|
||||
// which supports net.Resolver.PreferGo on Windows.
|
||||
return nil, false
|
||||
}
|
||||
ip := cloudenv.Get().ResolverIP()
|
||||
if ip == "" {
|
||||
|
||||
@@ -1321,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, c.NetMon)
|
||||
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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
|
||||
// Verify that we didn't just choose the Tailscale interface;
|
||||
// if so, we fall back to binding from the default.
|
||||
_, tsif, err2 := interfaces.Tailscale()
|
||||
if err2 == nil && tsif.Index == idx {
|
||||
if err2 == nil && tsif != nil && tsif.Index == idx {
|
||||
logf("[unexpected] netns: interfaceIndexFor returned Tailscale interface")
|
||||
return defaultIdx()
|
||||
}
|
||||
|
||||
171
net/ping/ping.go
171
net/ping/ping.go
@@ -11,16 +11,25 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"golang.org/x/net/ipv6"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/multierr"
|
||||
)
|
||||
|
||||
const (
|
||||
v4Type = "ip4:icmp"
|
||||
v6Type = "ip6:icmp"
|
||||
)
|
||||
|
||||
type response struct {
|
||||
@@ -33,12 +42,21 @@ type outstanding struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
// PacketListener defines the interface required to listen to packages
|
||||
// on an address.
|
||||
type ListenPacketer interface {
|
||||
ListenPacket(ctx context.Context, typ string, addr string) (net.PacketConn, error)
|
||||
}
|
||||
|
||||
// Pinger represents a set of ICMP echo requests to be sent at a single time.
|
||||
//
|
||||
// A new instance should be created for each concurrent set of ping requests;
|
||||
// this type should not be reused.
|
||||
type Pinger struct {
|
||||
c net.PacketConn
|
||||
lp ListenPacketer
|
||||
|
||||
// closed guards against send incrementing the waitgroup concurrently with close.
|
||||
closed atomic.Bool
|
||||
Logf logger.Logf
|
||||
Verbose bool
|
||||
timeNow func() time.Time
|
||||
@@ -46,16 +64,37 @@ type Pinger struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Following fields protected by mu
|
||||
mu sync.Mutex
|
||||
mu sync.Mutex
|
||||
// conns is a map of "type" to net.PacketConn, type is either
|
||||
// "ip4:icmp" or "ip6:icmp"
|
||||
conns map[string]net.PacketConn
|
||||
seq uint16 // uint16 per RFC 792
|
||||
pings map[uint16]outstanding
|
||||
}
|
||||
|
||||
// New creates a new Pinger. The Context provided will be used to create
|
||||
// network listeners, and to set an absolute deadline (if any) on the net.Conn
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func New(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) {
|
||||
p, err := newUnstarted(ctx, logf, netMon)
|
||||
func New(ctx context.Context, logf logger.Logf, lp ListenPacketer) *Pinger {
|
||||
var id [2]byte
|
||||
if _, err := io.ReadFull(rand.Reader, id[:]); err != nil {
|
||||
panic("net/ping: New:" + err.Error())
|
||||
}
|
||||
|
||||
return &Pinger{
|
||||
lp: lp,
|
||||
Logf: logf,
|
||||
timeNow: time.Now,
|
||||
id: binary.LittleEndian.Uint16(id[:]),
|
||||
pings: make(map[uint16]outstanding),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pinger) mkconn(ctx context.Context, typ, addr string) (net.PacketConn, error) {
|
||||
if p.closed.Load() {
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
|
||||
c, err := p.lp.ListenPacket(ctx, typ, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -64,35 +103,36 @@ func New(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger
|
||||
// applies to all future I/O, so we only need to do it once.
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok {
|
||||
if err := p.c.SetReadDeadline(deadline); err != nil {
|
||||
if err := c.SetReadDeadline(deadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
p.wg.Add(1)
|
||||
go p.run(ctx)
|
||||
return p, nil
|
||||
go p.run(ctx, c, typ)
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func newUnstarted(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) {
|
||||
var id [2]byte
|
||||
_, err := rand.Read(id[:])
|
||||
// getConn creates or returns a conn matching typ which is ip4:icmp
|
||||
// or ip6:icmp.
|
||||
func (p *Pinger) getConn(ctx context.Context, typ string) (net.PacketConn, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if c, ok := p.conns[typ]; ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var addr = "0.0.0.0"
|
||||
if typ == v6Type {
|
||||
addr = "::"
|
||||
}
|
||||
c, err := p.mkconn(ctx, typ, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := netns.Listener(logf, netMon).ListenPacket(ctx, "ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Pinger{
|
||||
c: conn,
|
||||
Logf: logf,
|
||||
timeNow: time.Now,
|
||||
id: binary.LittleEndian.Uint16(id[:]),
|
||||
pings: make(map[uint16]outstanding),
|
||||
}, nil
|
||||
mak.Set(&p.conns, typ, c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (p *Pinger) logf(format string, a ...any) {
|
||||
@@ -110,13 +150,34 @@ func (p *Pinger) vlogf(format string, a ...any) {
|
||||
}
|
||||
|
||||
func (p *Pinger) Close() error {
|
||||
err := p.c.Close()
|
||||
p.closed.Store(true)
|
||||
|
||||
p.mu.Lock()
|
||||
conns := p.conns
|
||||
p.conns = nil
|
||||
p.mu.Unlock()
|
||||
|
||||
var errors []error
|
||||
for _, c := range conns {
|
||||
if err := c.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
p.wg.Wait()
|
||||
return err
|
||||
p.cleanupOutstanding()
|
||||
|
||||
return multierr.New(errors...)
|
||||
}
|
||||
|
||||
func (p *Pinger) run(ctx context.Context) {
|
||||
func (p *Pinger) run(ctx context.Context, conn net.PacketConn, typ string) {
|
||||
defer p.wg.Done()
|
||||
defer func() {
|
||||
conn.Close()
|
||||
p.mu.Lock()
|
||||
delete(p.conns, typ)
|
||||
p.mu.Unlock()
|
||||
}()
|
||||
buf := make([]byte, 1500)
|
||||
|
||||
loop:
|
||||
@@ -127,7 +188,7 @@ loop:
|
||||
default:
|
||||
}
|
||||
|
||||
n, addr, err := p.c.ReadFrom(buf)
|
||||
n, _, err := conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
// Ignore temporary errors; everything else is fatal
|
||||
if netErr, ok := err.(net.Error); !ok || !netErr.Temporary() {
|
||||
@@ -136,10 +197,8 @@ loop:
|
||||
continue
|
||||
}
|
||||
|
||||
p.handleResponse(buf[:n], addr, p.timeNow())
|
||||
p.handleResponse(buf[:n], p.timeNow(), typ)
|
||||
}
|
||||
|
||||
p.cleanupOutstanding()
|
||||
}
|
||||
|
||||
func (p *Pinger) cleanupOutstanding() {
|
||||
@@ -151,16 +210,28 @@ func (p *Pinger) cleanupOutstanding() {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pinger) handleResponse(buf []byte, addr net.Addr, now time.Time) {
|
||||
const ProtocolICMP = 1
|
||||
m, err := icmp.ParseMessage(ProtocolICMP, buf)
|
||||
func (p *Pinger) handleResponse(buf []byte, now time.Time, typ string) {
|
||||
// We need to handle responding to both IPv4
|
||||
// and IPv6.
|
||||
var icmpType icmp.Type
|
||||
switch typ {
|
||||
case v4Type:
|
||||
icmpType = ipv4.ICMPTypeEchoReply
|
||||
case v6Type:
|
||||
icmpType = ipv6.ICMPTypeEchoReply
|
||||
default:
|
||||
p.vlogf("handleResponse: unknown icmp.Type")
|
||||
return
|
||||
}
|
||||
|
||||
m, err := icmp.ParseMessage(icmpType.Protocol(), buf)
|
||||
if err != nil {
|
||||
p.vlogf("handleResponse: invalid packet: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if m.Type != ipv4.ICMPTypeEchoReply {
|
||||
p.vlogf("handleResponse: wanted m.Type=%d; got %d", ipv4.ICMPTypeEchoReply, m.Type)
|
||||
if m.Type != icmpType {
|
||||
p.vlogf("handleResponse: wanted m.Type=%d; got %d", icmpType, m.Type)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -212,9 +283,27 @@ func (p *Pinger) Send(ctx context.Context, dest net.Addr, data []byte) (time.Dur
|
||||
seq := p.seq
|
||||
p.mu.Unlock()
|
||||
|
||||
// Check whether the address is IPv4 or IPv6 to
|
||||
// determine the icmp.Type and conn to use.
|
||||
var conn net.PacketConn
|
||||
var icmpType icmp.Type = ipv4.ICMPTypeEcho
|
||||
ap, err := netip.ParseAddr(dest.String())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ap.Is6() {
|
||||
icmpType = ipv6.ICMPTypeEchoRequest
|
||||
conn, err = p.getConn(ctx, v6Type)
|
||||
} else {
|
||||
conn, err = p.getConn(ctx, v4Type)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
m := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho,
|
||||
Code: 0,
|
||||
Type: icmpType,
|
||||
Code: icmpType.Protocol(),
|
||||
Body: &icmp.Echo{
|
||||
ID: int(p.id),
|
||||
Seq: int(seq),
|
||||
@@ -234,7 +323,7 @@ func (p *Pinger) Send(ctx context.Context, dest net.Addr, data []byte) (time.Dur
|
||||
p.mu.Unlock()
|
||||
|
||||
start := p.timeNow()
|
||||
n, err := p.c.WriteTo(b, dest)
|
||||
n, err := conn.WriteTo(b, dest)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if n != len(b) {
|
||||
|
||||
@@ -6,18 +6,20 @@ package ping
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
var (
|
||||
localhost = &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}
|
||||
localhostUDP = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 12345}
|
||||
localhost = &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}
|
||||
)
|
||||
|
||||
func TestPinger(t *testing.T) {
|
||||
@@ -35,7 +37,7 @@ func TestPinger(t *testing.T) {
|
||||
// Start a ping in the background
|
||||
r := make(chan time.Duration, 1)
|
||||
go func() {
|
||||
dur, err := p.Send(ctx, localhostUDP, bodyData)
|
||||
dur, err := p.Send(ctx, localhost, bodyData)
|
||||
if err != nil {
|
||||
t.Errorf("p.Send: %v", err)
|
||||
r <- 0
|
||||
@@ -49,7 +51,7 @@ func TestPinger(t *testing.T) {
|
||||
// Fake a response from ourself
|
||||
fakeResponse := mustMarshal(t, &icmp.Message{
|
||||
Type: ipv4.ICMPTypeEchoReply,
|
||||
Code: 0,
|
||||
Code: ipv4.ICMPTypeEchoReply.Protocol(),
|
||||
Body: &icmp.Echo{
|
||||
ID: 1234,
|
||||
Seq: 1,
|
||||
@@ -58,7 +60,65 @@ func TestPinger(t *testing.T) {
|
||||
})
|
||||
|
||||
const fakeDuration = 100 * time.Millisecond
|
||||
p.handleResponse(fakeResponse, localhost, clock.Now().Add(fakeDuration))
|
||||
p.handleResponse(fakeResponse, clock.Now().Add(fakeDuration), v4Type)
|
||||
|
||||
select {
|
||||
case dur := <-r:
|
||||
want := fakeDuration
|
||||
if dur != want {
|
||||
t.Errorf("wanted ping response time = %d; got %d", want, dur)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("did not get response by timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestV6Pinger(t *testing.T) {
|
||||
if c, err := net.ListenPacket("udp6", "::1"); err != nil {
|
||||
// skip test if we can't use IPv6.
|
||||
t.Skipf("IPv6 not supported: %s", err)
|
||||
} else {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
clock := &tstest.Clock{}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
p, closeP := mockPinger(t, clock)
|
||||
defer closeP()
|
||||
|
||||
bodyData := []byte("data goes here")
|
||||
|
||||
// Start a ping in the background
|
||||
r := make(chan time.Duration, 1)
|
||||
go func() {
|
||||
dur, err := p.Send(ctx, &net.IPAddr{IP: net.ParseIP("::")}, bodyData)
|
||||
if err != nil {
|
||||
t.Errorf("p.Send: %v", err)
|
||||
r <- 0
|
||||
} else {
|
||||
r <- dur
|
||||
}
|
||||
}()
|
||||
|
||||
p.waitOutstanding(t, ctx, 1)
|
||||
|
||||
// Fake a response from ourself
|
||||
fakeResponse := mustMarshal(t, &icmp.Message{
|
||||
Type: ipv6.ICMPTypeEchoReply,
|
||||
Code: ipv6.ICMPTypeEchoReply.Protocol(),
|
||||
Body: &icmp.Echo{
|
||||
ID: 1234,
|
||||
Seq: 1,
|
||||
Data: bodyData,
|
||||
},
|
||||
})
|
||||
|
||||
const fakeDuration = 100 * time.Millisecond
|
||||
p.handleResponse(fakeResponse, clock.Now().Add(fakeDuration), v6Type)
|
||||
|
||||
select {
|
||||
case dur := <-r:
|
||||
@@ -83,7 +143,7 @@ func TestPingerTimeout(t *testing.T) {
|
||||
// Send a ping in the background
|
||||
r := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := p.Send(ctx, localhostUDP, []byte("data goes here"))
|
||||
_, err := p.Send(ctx, localhost, []byte("data goes here"))
|
||||
r <- err
|
||||
}()
|
||||
|
||||
@@ -115,7 +175,7 @@ func TestPingerMismatch(t *testing.T) {
|
||||
// Start a ping in the background
|
||||
r := make(chan time.Duration, 1)
|
||||
go func() {
|
||||
dur, err := p.Send(ctx, localhostUDP, bodyData)
|
||||
dur, err := p.Send(ctx, localhost, bodyData)
|
||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Errorf("p.Send: %v", err)
|
||||
r <- 0
|
||||
@@ -185,11 +245,11 @@ func TestPingerMismatch(t *testing.T) {
|
||||
|
||||
for _, tt := range badPackets {
|
||||
fakeResponse := mustMarshal(t, tt.pkt)
|
||||
p.handleResponse(fakeResponse, localhost, tm)
|
||||
p.handleResponse(fakeResponse, tm, v4Type)
|
||||
}
|
||||
|
||||
// Also "receive" a packet that does not unmarshal as an ICMP packet
|
||||
p.handleResponse([]byte("foo"), localhost, tm)
|
||||
p.handleResponse([]byte("foo"), tm, v4Type)
|
||||
|
||||
select {
|
||||
case <-r:
|
||||
@@ -199,23 +259,59 @@ func TestPingerMismatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// udpingPacketConn will convert potentially ICMP destination addrs to UDP
|
||||
// destination addrs in WriteTo so that a test that is intending to send ICMP
|
||||
// traffic will instead send UDP traffic, without the higher level Pinger being
|
||||
// aware of this difference.
|
||||
type udpingPacketConn struct {
|
||||
net.PacketConn
|
||||
// destPort will be configured by the test to be the peer expected to respond to a ping.
|
||||
destPort uint16
|
||||
}
|
||||
|
||||
func (u *udpingPacketConn) WriteTo(body []byte, dest net.Addr) (int, error) {
|
||||
switch d := dest.(type) {
|
||||
case *net.IPAddr:
|
||||
udpAddr := &net.UDPAddr{
|
||||
IP: d.IP,
|
||||
Port: int(u.destPort),
|
||||
Zone: d.Zone,
|
||||
}
|
||||
return u.PacketConn.WriteTo(body, udpAddr)
|
||||
}
|
||||
return 0, fmt.Errorf("unimplemented udpingPacketConn for %T", dest)
|
||||
}
|
||||
|
||||
func mockPinger(t *testing.T, clock *tstest.Clock) (*Pinger, func()) {
|
||||
p := New(context.Background(), t.Logf, nil)
|
||||
p.timeNow = clock.Now
|
||||
p.Verbose = true
|
||||
p.id = 1234
|
||||
|
||||
// In tests, we use UDP so that we can test without being root; this
|
||||
// doesn't matter because we mock out the ICMP reply below to be a real
|
||||
// ICMP echo reply packet.
|
||||
conn, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||
conn4, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.ListenPacket: %v", err)
|
||||
}
|
||||
|
||||
p := &Pinger{
|
||||
c: conn,
|
||||
Logf: t.Logf,
|
||||
Verbose: true,
|
||||
timeNow: clock.Now,
|
||||
id: 1234,
|
||||
pings: make(map[uint16]outstanding),
|
||||
conn6, err := net.ListenPacket("udp6", "[::]:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.ListenPacket: %v", err)
|
||||
}
|
||||
|
||||
conn4 = &udpingPacketConn{
|
||||
destPort: 12345,
|
||||
PacketConn: conn4,
|
||||
}
|
||||
conn6 = &udpingPacketConn{
|
||||
PacketConn: conn6,
|
||||
destPort: 12345,
|
||||
}
|
||||
|
||||
mak.Set(&p.conns, v4Type, conn4)
|
||||
mak.Set(&p.conns, v6Type, conn6)
|
||||
done := func() {
|
||||
if err := p.Close(); err != nil {
|
||||
t.Errorf("error on close: %v", err)
|
||||
|
||||
@@ -325,6 +325,10 @@ type radioMonitor struct {
|
||||
// Usage is measured once per second, so this is the number of seconds of history to track.
|
||||
const radioSampleSize = 3600 // 1 hour
|
||||
|
||||
// initStallPeriod is the minimum amount of time in seconds to collect data before reporting.
|
||||
// Otherwise, all clients will report 100% radio usage on startup.
|
||||
var initStallPeriod int64 = 120 // 2 minutes
|
||||
|
||||
var radio = &radioMonitor{
|
||||
now: time.Now,
|
||||
startTime: time.Now().Unix(),
|
||||
@@ -375,7 +379,7 @@ func (rm *radioMonitor) radioHighPercent() int64 {
|
||||
}
|
||||
})
|
||||
|
||||
if periodLength == 0 {
|
||||
if periodLength < initStallPeriod {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -386,7 +390,7 @@ func (rm *radioMonitor) radioHighPercent() int64 {
|
||||
}
|
||||
|
||||
// forEachSample calls f for each sample in the past hour (or less if less time
|
||||
// has passed -- the evaluated period is returned)
|
||||
// has passed -- the evaluated period is returned, measured in seconds)
|
||||
func (rm *radioMonitor) forEachSample(f func(c int, isActive bool)) (periodLength int64) {
|
||||
now := rm.now().Unix()
|
||||
periodLength = radioSampleSize
|
||||
|
||||
@@ -33,6 +33,14 @@ func TestRadioMonitor(t *testing.T) {
|
||||
func(_ *testTime, _ *radioMonitor) {},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"active less than init stall period",
|
||||
func(tt *testTime, rm *radioMonitor) {
|
||||
rm.active()
|
||||
tt.Add(1 * time.Second)
|
||||
},
|
||||
0, // radio on, but not long enough to report data
|
||||
},
|
||||
{
|
||||
"active, 10 sec idle",
|
||||
func(tt *testTime, rm *radioMonitor) {
|
||||
@@ -42,13 +50,13 @@ func TestRadioMonitor(t *testing.T) {
|
||||
50, // radio on 5 seconds of 10 seconds
|
||||
},
|
||||
{
|
||||
"active, spanning two seconds",
|
||||
"active, spanning three seconds",
|
||||
func(tt *testTime, rm *radioMonitor) {
|
||||
rm.active()
|
||||
tt.Add(1100 * time.Millisecond)
|
||||
tt.Add(2100 * time.Millisecond)
|
||||
rm.active()
|
||||
},
|
||||
100, // radio on for 2 seconds
|
||||
100, // radio on for 3 seconds
|
||||
},
|
||||
{
|
||||
"400 iterations: 2 sec active, 1 min idle",
|
||||
@@ -66,13 +74,17 @@ func TestRadioMonitor(t *testing.T) {
|
||||
{
|
||||
"activity at end of time window",
|
||||
func(tt *testTime, rm *radioMonitor) {
|
||||
tt.Add(1 * time.Second)
|
||||
tt.Add(3 * time.Second)
|
||||
rm.active()
|
||||
},
|
||||
50,
|
||||
25,
|
||||
},
|
||||
}
|
||||
|
||||
oldStallPeriod := initStallPeriod
|
||||
initStallPeriod = 3
|
||||
t.Cleanup(func() { initStallPeriod = oldStallPeriod })
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tm := &testTime{time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)}
|
||||
|
||||
@@ -47,8 +47,11 @@ func (t *fakeTUN) Write(b [][]byte, n int) (int, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// FakeTUNName is the name of the fake TUN device.
|
||||
const FakeTUNName = "FakeTUN"
|
||||
|
||||
func (t *fakeTUN) Flush() error { return nil }
|
||||
func (t *fakeTUN) MTU() (int, error) { return 1500, nil }
|
||||
func (t *fakeTUN) Name() (string, error) { return "FakeTUN", nil }
|
||||
func (t *fakeTUN) Name() (string, error) { return FakeTUNName, nil }
|
||||
func (t *fakeTUN) Events() <-chan tun.Event { return t.evchan }
|
||||
func (t *fakeTUN) BatchSize() int { return 1 }
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"go4.org/mem"
|
||||
"golang.org/x/exp/slices"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/net/connstats"
|
||||
@@ -590,16 +591,33 @@ func natConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
|
||||
dstMasqAddrs map[key.NodePublic]netip.Addr
|
||||
listenAddrs map[netip.Addr]struct{}
|
||||
)
|
||||
|
||||
// When using an exit node that requires masquerading, we need to
|
||||
// fill out the routing table with all peers not just the ones that
|
||||
// require masquerading.
|
||||
exitNodeRequiresMasq := false // true if using an exit node and it requires masquerading
|
||||
for _, p := range wcfg.Peers {
|
||||
isExitNode := slices.Contains(p.AllowedIPs, tsaddr.AllIPv4()) || slices.Contains(p.AllowedIPs, tsaddr.AllIPv6())
|
||||
if isExitNode && p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
|
||||
exitNodeRequiresMasq = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := range wcfg.Peers {
|
||||
p := &wcfg.Peers[i]
|
||||
if p.V4MasqAddr == nil || !p.V4MasqAddr.IsValid() {
|
||||
var addrToUse netip.Addr
|
||||
if p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
|
||||
addrToUse = *p.V4MasqAddr
|
||||
mak.Set(&listenAddrs, addrToUse, struct{}{})
|
||||
} else if exitNodeRequiresMasq {
|
||||
addrToUse = nativeAddr
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
rt.InsertOrReplace(p.PublicKey, p.AllowedIPs...)
|
||||
mak.Set(&dstMasqAddrs, p.PublicKey, *p.V4MasqAddr)
|
||||
mak.Set(&listenAddrs, *p.V4MasqAddr, struct{}{})
|
||||
mak.Set(&dstMasqAddrs, p.PublicKey, addrToUse)
|
||||
}
|
||||
if len(listenAddrs) == 0 || len(dstMasqAddrs) == 0 {
|
||||
if len(listenAddrs) == 0 && len(dstMasqAddrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &natV4Config{
|
||||
|
||||
@@ -602,13 +602,13 @@ func TestFilterDiscoLoop(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNATCfg(t *testing.T) {
|
||||
node := func(ip, eip netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
|
||||
node := func(ip, masqIP netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
|
||||
p := wgcfg.Peer{
|
||||
PublicKey: key.NewNode().Public(),
|
||||
AllowedIPs: []netip.Prefix{
|
||||
netip.PrefixFrom(ip, ip.BitLen()),
|
||||
},
|
||||
V4MasqAddr: ptr.To(eip),
|
||||
V4MasqAddr: ptr.To(masqIP),
|
||||
}
|
||||
p.AllowedIPs = append(p.AllowedIPs, otherAllowedIPs...)
|
||||
return p
|
||||
@@ -619,13 +619,16 @@ func TestNATCfg(t *testing.T) {
|
||||
selfNativeIP = netip.MustParseAddr("100.64.0.1")
|
||||
selfEIP1 = netip.MustParseAddr("100.64.1.1")
|
||||
selfEIP2 = netip.MustParseAddr("100.64.1.2")
|
||||
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
|
||||
|
||||
peer1IP = netip.MustParseAddr("100.64.0.2")
|
||||
peer2IP = netip.MustParseAddr("100.64.0.3")
|
||||
|
||||
subnet = netip.MustParseAddr("192.168.0.1")
|
||||
subnet = netip.MustParsePrefix("192.168.0.0/24")
|
||||
subnetIP = netip.MustParseAddr("192.168.0.1")
|
||||
|
||||
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
|
||||
exitRoute = netip.MustParsePrefix("0.0.0.0/0")
|
||||
publicIP = netip.MustParseAddr("8.8.8.8")
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
@@ -638,9 +641,9 @@ func TestNATCfg(t *testing.T) {
|
||||
name: "no-cfg",
|
||||
wcfg: nil,
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
subnet: selfNativeIP,
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
subnetIP: selfNativeIP,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
@@ -658,15 +661,15 @@ func TestNATCfg(t *testing.T) {
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfEIP1,
|
||||
subnet: selfNativeIP,
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfEIP1,
|
||||
subnetIP: selfNativeIP,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfNativeIP,
|
||||
selfEIP2: selfEIP2,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -679,15 +682,15 @@ func TestNATCfg(t *testing.T) {
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
subnet: selfNativeIP,
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
subnetIP: selfNativeIP,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfNativeIP,
|
||||
selfEIP2: selfNativeIP,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -696,19 +699,19 @@ func TestNATCfg(t *testing.T) {
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2, netip.MustParsePrefix("192.168.0.0/24")),
|
||||
node(peer2IP, selfEIP2, subnet),
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
subnet: selfEIP2,
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
subnetIP: selfEIP2,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfNativeIP,
|
||||
selfEIP2: selfNativeIP,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -717,19 +720,19 @@ func TestNATCfg(t *testing.T) {
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2, netip.MustParsePrefix("0.0.0.0/0")),
|
||||
node(peer2IP, selfEIP2, exitRoute),
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
netip.MustParseAddr("8.8.8.8"): selfEIP2,
|
||||
peer1IP: selfEIP1,
|
||||
peer2IP: selfEIP2,
|
||||
publicIP: selfEIP2,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfNativeIP,
|
||||
selfEIP2: selfNativeIP,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -742,15 +745,35 @@ func TestNATCfg(t *testing.T) {
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
subnet: selfNativeIP,
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
subnetIP: selfNativeIP,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP1: selfEIP1,
|
||||
selfEIP2: selfEIP2,
|
||||
subnet: subnet,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exit-node-require-nat-peer-doesnt",
|
||||
wcfg: &wgcfg.Config{
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, noIP),
|
||||
node(peer2IP, selfEIP2, exitRoute),
|
||||
},
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfEIP2,
|
||||
publicIP: selfEIP2,
|
||||
},
|
||||
dnatMap: map[netip.Addr]netip.Addr{
|
||||
selfNativeIP: selfNativeIP,
|
||||
selfEIP2: selfNativeIP,
|
||||
subnetIP: subnetIP,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ type nothing struct{}
|
||||
// Unfortunately, options to filter by proto or state are non-portable,
|
||||
// so we'll filter for ourselves.
|
||||
// Nowadays, though, we only use it for macOS as of 2022-11-04.
|
||||
func appendParsePortsNetstat(base []Port, br *bufio.Reader) ([]Port, error) {
|
||||
func appendParsePortsNetstat(base []Port, br *bufio.Reader, includeLocalhost bool) ([]Port, error) {
|
||||
ret := base
|
||||
var fieldBuf [10]mem.RO
|
||||
for {
|
||||
@@ -99,7 +99,7 @@ func appendParsePortsNetstat(base []Port, br *bufio.Reader) ([]Port, error) {
|
||||
// not interested in non-listener sockets
|
||||
continue
|
||||
}
|
||||
if isLoopbackAddr(laddr) {
|
||||
if !includeLocalhost && isLoopbackAddr(laddr) {
|
||||
// not interested in loopback-bound listeners
|
||||
continue
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func appendParsePortsNetstat(base []Port, br *bufio.Reader) ([]Port, error) {
|
||||
proto = "udp"
|
||||
laddr = cols[len(cols)-2]
|
||||
raddr = cols[len(cols)-1]
|
||||
if isLoopbackAddr(laddr) {
|
||||
if !includeLocalhost && isLoopbackAddr(laddr) {
|
||||
// not interested in loopback-bound listeners
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package portlist
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -52,30 +53,40 @@ udp46 0 0 *.146 *.*
|
||||
`
|
||||
|
||||
func TestParsePortsNetstat(t *testing.T) {
|
||||
want := List{
|
||||
Port{"tcp", 23, ""},
|
||||
Port{"tcp", 24, ""},
|
||||
Port{"udp", 104, ""},
|
||||
Port{"udp", 106, ""},
|
||||
Port{"udp", 146, ""},
|
||||
Port{"tcp", 8185, ""}, // but not 8186, 8187, 8188 on localhost
|
||||
}
|
||||
|
||||
pl, err := appendParsePortsNetstat(nil, bufio.NewReader(strings.NewReader(netstatOutput)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pl = sortAndDedup(pl)
|
||||
jgot, _ := json.MarshalIndent(pl, "", "\t")
|
||||
jwant, _ := json.MarshalIndent(want, "", "\t")
|
||||
if len(pl) != len(want) {
|
||||
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
|
||||
}
|
||||
for i := range pl {
|
||||
if pl[i] != want[i] {
|
||||
t.Errorf("row#%d\n got: %+v\n\nwant: %+v\n",
|
||||
i, pl[i], want[i])
|
||||
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
|
||||
}
|
||||
for _, loopBack := range [...]bool{false, true} {
|
||||
t.Run(fmt.Sprintf("loopback_%v", loopBack), func(t *testing.T) {
|
||||
want := List{
|
||||
{"tcp", 23, "", 0},
|
||||
{"tcp", 24, "", 0},
|
||||
{"udp", 104, "", 0},
|
||||
{"udp", 106, "", 0},
|
||||
{"udp", 146, "", 0},
|
||||
{"tcp", 8185, "", 0}, // but not 8186, 8187, 8188 on localhost, when loopback is false
|
||||
}
|
||||
if loopBack {
|
||||
want = append(want,
|
||||
Port{"tcp", 8186, "", 0},
|
||||
Port{"tcp", 8187, "", 0},
|
||||
Port{"tcp", 8188, "", 0},
|
||||
)
|
||||
}
|
||||
pl, err := appendParsePortsNetstat(nil, bufio.NewReader(strings.NewReader(netstatOutput)), loopBack)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pl = sortAndDedup(pl)
|
||||
jgot, _ := json.MarshalIndent(pl, "", "\t")
|
||||
jwant, _ := json.MarshalIndent(want, "", "\t")
|
||||
if len(pl) != len(want) {
|
||||
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
|
||||
}
|
||||
for i := range pl {
|
||||
if pl[i] != want[i] {
|
||||
t.Errorf("row#%d\n got: %+v\n\nwant: %+v\n",
|
||||
i, pl[i], want[i])
|
||||
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
package portlist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -17,14 +17,25 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
)
|
||||
|
||||
var pollInterval = 5 * time.Second // default; changed by some OS-specific init funcs
|
||||
var (
|
||||
newOSImpl func(includeLocalhost bool) osImpl // if non-nil, constructs a new osImpl.
|
||||
pollInterval = 5 * time.Second // default; changed by some OS-specific init funcs
|
||||
debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
|
||||
)
|
||||
|
||||
var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
|
||||
// PollInterval is the recommended OS-specific interval
|
||||
// to wait between *Poller.Poll method calls.
|
||||
func PollInterval() time.Duration {
|
||||
return pollInterval
|
||||
}
|
||||
|
||||
// Poller scans the systems for listening ports periodically and sends
|
||||
// the results to C.
|
||||
type Poller struct {
|
||||
c chan List // unbuffered
|
||||
// IncludeLocalhost controls whether services bound to localhost are included.
|
||||
//
|
||||
// This field should only be changed before calling Run.
|
||||
IncludeLocalhost bool
|
||||
|
||||
// os, if non-nil, is an OS-specific implementation of the portlist getting
|
||||
// code. When non-nil, it's responsible for getting the complete list of
|
||||
@@ -32,14 +43,9 @@ type Poller struct {
|
||||
// addProcesses is not used.
|
||||
// A nil values means we don't have code for getting the list on the current
|
||||
// operating system.
|
||||
os osImpl
|
||||
osOnce sync.Once // guards init of os
|
||||
|
||||
// closeCtx is the context that's canceled on Close.
|
||||
closeCtx context.Context
|
||||
closeCtxCancel context.CancelFunc
|
||||
|
||||
runDone chan struct{} // closed when Run completes
|
||||
os osImpl
|
||||
initOnce sync.Once // guards init of os
|
||||
initErr error
|
||||
|
||||
// scatch is memory for Poller.getList to reuse between calls.
|
||||
scratch []Port
|
||||
@@ -61,123 +67,55 @@ type osImpl interface {
|
||||
AppendListeningPorts(base []Port) ([]Port, error)
|
||||
}
|
||||
|
||||
// newOSImpl, if non-nil, constructs a new osImpl.
|
||||
var newOSImpl func() osImpl
|
||||
|
||||
var errUnimplemented = errors.New("portlist poller not implemented on " + runtime.GOOS)
|
||||
|
||||
// NewPoller returns a new portlist Poller. It returns an error
|
||||
// if the portlist couldn't be obtained.
|
||||
func NewPoller() (*Poller, error) {
|
||||
if debugDisablePortlist() {
|
||||
return nil, errors.New("portlist disabled by envknob")
|
||||
}
|
||||
p := &Poller{
|
||||
c: make(chan List),
|
||||
runDone: make(chan struct{}),
|
||||
}
|
||||
p.closeCtx, p.closeCtxCancel = context.WithCancel(context.Background())
|
||||
p.osOnce.Do(p.initOSField)
|
||||
if p.os == nil {
|
||||
return nil, errUnimplemented
|
||||
}
|
||||
|
||||
// Do one initial poll synchronously so we can return an error
|
||||
// early.
|
||||
if pl, err := p.getList(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
p.setPrev(pl)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Poller) setPrev(pl List) {
|
||||
// Make a copy, as the pass in pl slice aliases pl.scratch and we don't want
|
||||
// that to except to the caller.
|
||||
p.prev = slices.Clone(pl)
|
||||
}
|
||||
|
||||
func (p *Poller) initOSField() {
|
||||
if newOSImpl != nil {
|
||||
p.os = newOSImpl()
|
||||
// init initializes the Poller by ensuring it has an underlying
|
||||
// OS implementation and is not turned off by envknob.
|
||||
func (p *Poller) init() {
|
||||
switch {
|
||||
case debugDisablePortlist():
|
||||
p.initErr = errors.New("portlist disabled by envknob")
|
||||
case newOSImpl == nil:
|
||||
p.initErr = errors.New("portlist poller not implemented on " + runtime.GOOS)
|
||||
default:
|
||||
p.os = newOSImpl(p.IncludeLocalhost)
|
||||
}
|
||||
}
|
||||
|
||||
// Updates return the channel that receives port list updates.
|
||||
//
|
||||
// The channel is closed when the Poller is closed.
|
||||
func (p *Poller) Updates() <-chan List { return p.c }
|
||||
|
||||
// Close closes the Poller.
|
||||
// Run will return with a nil error.
|
||||
func (p *Poller) Close() error {
|
||||
p.closeCtxCancel()
|
||||
<-p.runDone
|
||||
if p.os != nil {
|
||||
p.os.Close()
|
||||
if p.initErr != nil {
|
||||
return p.initErr
|
||||
}
|
||||
return nil
|
||||
if p.os == nil {
|
||||
return nil
|
||||
}
|
||||
return p.os.Close()
|
||||
}
|
||||
|
||||
// send sends pl to p.c and returns whether it was successfully sent.
|
||||
func (p *Poller) send(ctx context.Context, pl List) (sent bool, err error) {
|
||||
select {
|
||||
case p.c <- pl:
|
||||
return true, nil
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
case <-p.closeCtx.Done():
|
||||
return false, nil
|
||||
// Poll returns the list of listening ports, if changed from
|
||||
// a previous call as indicated by the changed result.
|
||||
func (p *Poller) Poll() (ports []Port, changed bool, err error) {
|
||||
p.initOnce.Do(p.init)
|
||||
if p.initErr != nil {
|
||||
return nil, false, fmt.Errorf("error initializing poller: %w", p.initErr)
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the Poller periodically until either the context
|
||||
// is done, or the Close is called.
|
||||
//
|
||||
// Run may only be called once.
|
||||
func (p *Poller) Run(ctx context.Context) error {
|
||||
tick := time.NewTicker(pollInterval)
|
||||
defer tick.Stop()
|
||||
return p.runWithTickChan(ctx, tick.C)
|
||||
}
|
||||
|
||||
func (p *Poller) runWithTickChan(ctx context.Context, tickChan <-chan time.Time) error {
|
||||
defer close(p.runDone)
|
||||
defer close(p.c)
|
||||
|
||||
// Send out the pre-generated initial value.
|
||||
if sent, err := p.send(ctx, p.prev); !sent {
|
||||
return err
|
||||
pl, err := p.getList()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tickChan:
|
||||
pl, err := p.getList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pl.equal(p.prev) {
|
||||
continue
|
||||
}
|
||||
p.setPrev(pl)
|
||||
if sent, err := p.send(ctx, p.prev); !sent {
|
||||
return err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-p.closeCtx.Done():
|
||||
return nil
|
||||
}
|
||||
if pl.equal(p.prev) {
|
||||
return nil, false, nil
|
||||
}
|
||||
p.setPrev(pl)
|
||||
return p.prev, true, nil
|
||||
}
|
||||
|
||||
func (p *Poller) getList() (List, error) {
|
||||
if debugDisablePortlist() {
|
||||
return nil, nil
|
||||
}
|
||||
p.osOnce.Do(p.initOSField)
|
||||
var err error
|
||||
p.scratch, err = p.os.AppendListeningPorts(p.scratch[:0])
|
||||
return p.scratch, err
|
||||
|
||||
@@ -18,6 +18,7 @@ type Port struct {
|
||||
Proto string // "tcp" or "udp"
|
||||
Port uint16 // port number
|
||||
Process string // optional process name, if found
|
||||
Pid int // process id, if known
|
||||
}
|
||||
|
||||
// List is a list of Ports.
|
||||
@@ -69,12 +70,11 @@ func sortAndDedup(ps List) List {
|
||||
out := ps[:0]
|
||||
var last Port
|
||||
for _, p := range ps {
|
||||
protoPort := Port{Proto: p.Proto, Port: p.Port}
|
||||
if last == protoPort {
|
||||
if last.Proto == p.Proto && last.Port == p.Port {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
last = protoPort
|
||||
last = p
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -35,25 +35,28 @@ type linuxImpl struct {
|
||||
procNetFiles []*os.File // seeked to start & reused between calls
|
||||
readlinkPathBuf []byte
|
||||
|
||||
known map[string]*portMeta // inode string => metadata
|
||||
br *bufio.Reader
|
||||
known map[string]*portMeta // inode string => metadata
|
||||
br *bufio.Reader
|
||||
includeLocalhost bool
|
||||
}
|
||||
|
||||
type portMeta struct {
|
||||
port Port
|
||||
pid int
|
||||
keep bool
|
||||
needsProcName bool
|
||||
}
|
||||
|
||||
func newLinuxImplBase() *linuxImpl {
|
||||
func newLinuxImplBase(includeLocalhost bool) *linuxImpl {
|
||||
return &linuxImpl{
|
||||
br: bufio.NewReader(eofReader),
|
||||
known: map[string]*portMeta{},
|
||||
br: bufio.NewReader(eofReader),
|
||||
known: map[string]*portMeta{},
|
||||
includeLocalhost: includeLocalhost,
|
||||
}
|
||||
}
|
||||
|
||||
func newLinuxImpl() osImpl {
|
||||
li := newLinuxImplBase()
|
||||
func newLinuxImpl(includeLocalhost bool) osImpl {
|
||||
li := newLinuxImplBase(includeLocalhost)
|
||||
for _, name := range []string{
|
||||
"/proc/net/tcp",
|
||||
"/proc/net/tcp6",
|
||||
@@ -220,7 +223,7 @@ func (li *linuxImpl) parseProcNetFile(r *bufio.Reader, fileBase string) error {
|
||||
// If a port is bound to localhost, ignore it.
|
||||
// TODO: localhost is bigger than 1 IP, we need to ignore
|
||||
// more things.
|
||||
if mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost)) {
|
||||
if !li.includeLocalhost && (mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost))) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -315,6 +318,9 @@ func (li *linuxImpl) findProcessNames(need map[string]*portMeta) error {
|
||||
}
|
||||
|
||||
argv := strings.Split(strings.TrimSuffix(string(bs), "\x00"), "\x00")
|
||||
if p, err := mem.ParseInt(pid, 10, 0); err == nil {
|
||||
pe.pid = int(p)
|
||||
}
|
||||
pe.port.Process = argvSubject(argv...)
|
||||
pe.needsProcName = false
|
||||
delete(need, string(targetBuf[:n]))
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestParsePorts(t *testing.T) {
|
||||
if tt.file != "" {
|
||||
file = tt.file
|
||||
}
|
||||
li := newLinuxImplBase()
|
||||
li := newLinuxImplBase(false)
|
||||
err := li.parseProcNetFile(r, file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -118,7 +118,7 @@ func BenchmarkParsePorts(b *testing.B) {
|
||||
contents.WriteString(" 3: 69050120005716BC64906EBE009ECD4D:D506 0047062600000000000000006E171268:01BB 01 00000000:00000000 02:0000009E 00000000 1000 0 151042856 2 0000000000000000 21 4 28 10 -1\n")
|
||||
}
|
||||
|
||||
li := newLinuxImplBase()
|
||||
li := newLinuxImplBase(false)
|
||||
|
||||
r := bytes.NewReader(contents.Bytes())
|
||||
br := bufio.NewReader(&contents)
|
||||
|
||||
@@ -29,8 +29,9 @@ type macOSImpl struct {
|
||||
known map[protoPort]*portMeta // inode string => metadata
|
||||
netstatPath string // lazily populated
|
||||
|
||||
br *bufio.Reader // reused
|
||||
portsBuf []Port
|
||||
br *bufio.Reader // reused
|
||||
portsBuf []Port
|
||||
includeLocalhost bool
|
||||
}
|
||||
|
||||
type protoPort struct {
|
||||
@@ -43,10 +44,11 @@ type portMeta struct {
|
||||
keep bool
|
||||
}
|
||||
|
||||
func newMacOSImpl() osImpl {
|
||||
func newMacOSImpl(includeLocalhost bool) osImpl {
|
||||
return &macOSImpl{
|
||||
known: map[protoPort]*portMeta{},
|
||||
br: bufio.NewReader(bytes.NewReader(nil)),
|
||||
known: map[protoPort]*portMeta{},
|
||||
br: bufio.NewReader(bytes.NewReader(nil)),
|
||||
includeLocalhost: includeLocalhost,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +121,7 @@ func (im *macOSImpl) appendListeningPortsNetstat(base []Port) ([]Port, error) {
|
||||
defer cmd.Process.Wait()
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
return appendParsePortsNetstat(base, im.br)
|
||||
return appendParsePortsNetstat(base, im.br, im.includeLocalhost)
|
||||
}
|
||||
|
||||
var lsofFailed atomic.Bool
|
||||
@@ -170,6 +172,7 @@ func (im *macOSImpl) addProcesses() error {
|
||||
im.br.Reset(outPipe)
|
||||
|
||||
var cmd, proto string
|
||||
var pid int
|
||||
for {
|
||||
line, err := im.br.ReadBytes('\n')
|
||||
if err != nil {
|
||||
@@ -184,6 +187,10 @@ func (im *macOSImpl) addProcesses() error {
|
||||
// starting a new process
|
||||
cmd = ""
|
||||
proto = ""
|
||||
pid = 0
|
||||
if p, err := mem.ParseInt(mem.B(val), 10, 0); err == nil {
|
||||
pid = int(p)
|
||||
}
|
||||
case 'c':
|
||||
cmd = string(val) // TODO(bradfitz): avoid garbage; cache process names between runs?
|
||||
case 'P':
|
||||
@@ -202,6 +209,7 @@ func (im *macOSImpl) addProcesses() error {
|
||||
switch {
|
||||
case m != nil:
|
||||
m.port.Process = cmd
|
||||
m.port.Pid = pid
|
||||
default:
|
||||
// ignore: processes and ports come and go
|
||||
}
|
||||
|
||||
@@ -4,13 +4,8 @@
|
||||
package portlist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"tailscale.com/tstest"
|
||||
)
|
||||
@@ -19,14 +14,14 @@ func TestGetList(t *testing.T) {
|
||||
tstest.ResourceCheck(t)
|
||||
|
||||
var p Poller
|
||||
pl, err := p.getList()
|
||||
pl, _, err := p.Poll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, p := range pl {
|
||||
t.Logf("[%d] %+v", i, p)
|
||||
}
|
||||
t.Logf("As String: %v", pl.String())
|
||||
t.Logf("As String: %s", List(pl))
|
||||
}
|
||||
|
||||
func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
||||
@@ -40,7 +35,7 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
||||
ta := ln.Addr().(*net.TCPAddr)
|
||||
port := ta.Port
|
||||
var p Poller
|
||||
pl, err := p.getList()
|
||||
pl, _, err := p.Poll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -51,27 +46,20 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var flagRunUnspecTests = flag.Bool("run-unspec-tests",
|
||||
runtime.GOOS == "linux", // other OSes have annoying firewall GUI confirmation dialogs
|
||||
"run tests that require listening on the the unspecified address")
|
||||
|
||||
func TestChangesOverTime(t *testing.T) {
|
||||
if !*flagRunUnspecTests {
|
||||
t.Skip("skipping test without --run-unspec-tests")
|
||||
}
|
||||
|
||||
func TestPoller(t *testing.T) {
|
||||
var p Poller
|
||||
p.IncludeLocalhost = true
|
||||
get := func(t *testing.T) []Port {
|
||||
t.Helper()
|
||||
s, err := p.getList()
|
||||
s, _, err := p.Poll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return append([]Port(nil), s...)
|
||||
return s
|
||||
}
|
||||
|
||||
p1 := get(t)
|
||||
ln, err := net.Listen("tcp", ":0")
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Skipf("failed to bind: %v", err)
|
||||
}
|
||||
@@ -184,68 +172,21 @@ func TestEqualLessThan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoller(t *testing.T) {
|
||||
p, err := NewPoller()
|
||||
func TestClose(t *testing.T) {
|
||||
var p Poller
|
||||
err := p.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p = Poller{}
|
||||
_, _, err = p.Poll()
|
||||
if err != nil {
|
||||
t.Skipf("skipping due to poll error: %v", err)
|
||||
}
|
||||
err = p.Close()
|
||||
if err != nil {
|
||||
t.Skipf("not running test: %v", err)
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
gotUpdate := make(chan bool, 16)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for pl := range p.Updates() {
|
||||
// Look at all the pl slice memory to maximize
|
||||
// chance of race detector seeing violations.
|
||||
for _, v := range pl {
|
||||
if v == (Port{}) {
|
||||
// Force use
|
||||
panic("empty port")
|
||||
}
|
||||
}
|
||||
select {
|
||||
case gotUpdate <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
tick := make(chan time.Time, 16)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := p.runWithTickChan(context.Background(), tick); err != nil {
|
||||
t.Error("runWithTickChan:", err)
|
||||
}
|
||||
}()
|
||||
for i := 0; i < 10; i++ {
|
||||
ln, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
tick <- time.Time{}
|
||||
|
||||
select {
|
||||
case <-gotUpdate:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timed out waiting for update")
|
||||
}
|
||||
}
|
||||
|
||||
// And a bunch of ticks without waiting for updates,
|
||||
// to make race tests more likely to fail, if any present.
|
||||
for i := 0; i < 10; i++ {
|
||||
tick <- time.Time{}
|
||||
}
|
||||
|
||||
if err := p.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkGetList(b *testing.B) {
|
||||
@@ -259,6 +200,11 @@ func BenchmarkGetListIncremental(b *testing.B) {
|
||||
func benchmarkGetList(b *testing.B, incremental bool) {
|
||||
b.ReportAllocs()
|
||||
var p Poller
|
||||
p.init()
|
||||
if p.initErr != nil {
|
||||
b.Skip(p.initErr)
|
||||
}
|
||||
b.Cleanup(func() { p.Close() })
|
||||
for i := 0; i < b.N; i++ {
|
||||
pl, err := p.getList()
|
||||
if err != nil {
|
||||
|
||||
@@ -25,7 +25,8 @@ type famPort struct {
|
||||
}
|
||||
|
||||
type windowsImpl struct {
|
||||
known map[famPort]*portMeta // inode string => metadata
|
||||
known map[famPort]*portMeta // inode string => metadata
|
||||
includeLocalhost bool
|
||||
}
|
||||
|
||||
type portMeta struct {
|
||||
@@ -33,9 +34,10 @@ type portMeta struct {
|
||||
keep bool
|
||||
}
|
||||
|
||||
func newWindowsImpl() osImpl {
|
||||
func newWindowsImpl(includeLocalhost bool) osImpl {
|
||||
return &windowsImpl{
|
||||
known: map[famPort]*portMeta{},
|
||||
known: map[famPort]*portMeta{},
|
||||
includeLocalhost: includeLocalhost,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +60,7 @@ func (im *windowsImpl) AppendListeningPorts(base []Port) ([]Port, error) {
|
||||
if e.State != "LISTEN" {
|
||||
continue
|
||||
}
|
||||
if !e.Local.Addr().IsUnspecified() {
|
||||
if !im.includeLocalhost && !e.Local.Addr().IsUnspecified() {
|
||||
continue
|
||||
}
|
||||
fp := famPort{
|
||||
@@ -83,6 +85,7 @@ func (im *windowsImpl) AppendListeningPorts(base []Port) ([]Port, error) {
|
||||
Proto: "tcp",
|
||||
Port: e.Local.Port(),
|
||||
Process: process,
|
||||
Pid: e.Pid,
|
||||
},
|
||||
}
|
||||
im.known[fp] = pm
|
||||
|
||||
7
release/dist/cli/cli.go
vendored
7
release/dist/cli/cli.go
vendored
@@ -124,10 +124,11 @@ func runBuild(ctx context.Context, filters []string, targets []dist.Target) erro
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting absolute path of manifest: %w", err)
|
||||
}
|
||||
fmt.Println(manifest)
|
||||
fmt.Println(filepath.Join(b.Out, out[0]))
|
||||
for i := range out {
|
||||
rel, err := filepath.Rel(filepath.Dir(manifest), filepath.Join(b.Out, out[i]))
|
||||
if !filepath.IsAbs(out[i]) {
|
||||
out[i] = filepath.Join(b.Out, out[i])
|
||||
}
|
||||
rel, err := filepath.Rel(filepath.Dir(manifest), out[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("making path relative: %w", err)
|
||||
}
|
||||
|
||||
7
release/dist/dist.go
vendored
7
release/dist/dist.go
vendored
@@ -17,6 +17,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/version/mkversion"
|
||||
@@ -44,6 +45,8 @@ type Build struct {
|
||||
Go string
|
||||
// Version is the version info of the build.
|
||||
Version mkversion.VersionInfo
|
||||
// Time is the timestamp of the build.
|
||||
Time time.Time
|
||||
|
||||
// once is a cache of function invocations that should run once per process
|
||||
// (for example building a helper docker container)
|
||||
@@ -86,6 +89,7 @@ func NewBuild(repo, out string) (*Build, error) {
|
||||
Out: out,
|
||||
Go: goTool,
|
||||
Version: mkversion.Info(),
|
||||
Time: time.Now().UTC(),
|
||||
extra: map[any]any{},
|
||||
goBuildLimit: make(chan struct{}, runtime.NumCPU()),
|
||||
}
|
||||
@@ -114,6 +118,9 @@ func (b *Build) Build(targets []Target) (files []string, err error) {
|
||||
go func(i int, t Target) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s: %w", t, err)
|
||||
}
|
||||
errs[i] = err
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
BIN
release/dist/synology/files/PACKAGE_ICON.PNG
vendored
Normal file
BIN
release/dist/synology/files/PACKAGE_ICON.PNG
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
BIN
release/dist/synology/files/PACKAGE_ICON_256.PNG
vendored
Normal file
BIN
release/dist/synology/files/PACKAGE_ICON_256.PNG
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
6
release/dist/synology/files/Tailscale.sc
vendored
Normal file
6
release/dist/synology/files/Tailscale.sc
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[Tailscale]
|
||||
title="Tailscale"
|
||||
desc="Tailscale VPN"
|
||||
port_forward="no"
|
||||
src.ports="41641/udp"
|
||||
dst.ports="41641/udp"
|
||||
12
release/dist/synology/files/config
vendored
Normal file
12
release/dist/synology/files/config
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
".url": {
|
||||
"SYNO.SDS.Tailscale": {
|
||||
"type": "url",
|
||||
"version": "1.8.3",
|
||||
"title": "Tailscale",
|
||||
"icon": "PACKAGE_ICON_256.PNG",
|
||||
"url": "webman/3rdparty/Tailscale/",
|
||||
"urlTarget": "_syno_tailscale"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
release/dist/synology/files/index.cgi
vendored
Executable file
2
release/dist/synology/files/index.cgi
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
#! /bin/sh
|
||||
exec /var/packages/Tailscale/target/bin/tailscale web -cgi
|
||||
8
release/dist/synology/files/logrotate-dsm6
vendored
Normal file
8
release/dist/synology/files/logrotate-dsm6
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/var/packages/Tailscale/etc/tailscaled.stdout.log {
|
||||
size 10M
|
||||
rotate 3
|
||||
missingok
|
||||
copytruncate
|
||||
compress
|
||||
notifempty
|
||||
}
|
||||
8
release/dist/synology/files/logrotate-dsm7
vendored
Normal file
8
release/dist/synology/files/logrotate-dsm7
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/var/packages/Tailscale/var/tailscaled.stdout.log {
|
||||
size 10M
|
||||
rotate 3
|
||||
missingok
|
||||
copytruncate
|
||||
compress
|
||||
notifempty
|
||||
}
|
||||
7
release/dist/synology/files/privilege-dsm6
vendored
Normal file
7
release/dist/synology/files/privilege-dsm6
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"defaults":{
|
||||
"run-as": "root"
|
||||
},
|
||||
"username": "tailscale",
|
||||
"groupname": "tailscale"
|
||||
}
|
||||
7
release/dist/synology/files/privilege-dsm7
vendored
Normal file
7
release/dist/synology/files/privilege-dsm7
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"defaults":{
|
||||
"run-as": "package"
|
||||
},
|
||||
"username": "tailscale",
|
||||
"groupname": "tailscale"
|
||||
}
|
||||
13
release/dist/synology/files/privilege-dsm7.for-package-center
vendored
Normal file
13
release/dist/synology/files/privilege-dsm7.for-package-center
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"defaults":{
|
||||
"run-as": "package"
|
||||
},
|
||||
"username": "tailscale",
|
||||
"groupname": "tailscale",
|
||||
"tool": [{
|
||||
"relpath": "bin/tailscaled",
|
||||
"user": "package",
|
||||
"group": "package",
|
||||
"capabilities": "cap_net_admin,cap_chown,cap_net_raw"
|
||||
}]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user