Compare commits

..

3 Commits

Author SHA1 Message Date
David Anderson 67a5f45817 wip almost 2023-05-11 08:14:06 -07:00
David Anderson fe3604c47c wip IT WORKS DONT OVERWRITE
Signed-off-by: David Anderson <danderson@tailscale.com>
2023-05-10 10:49:53 -07:00
David Anderson 8620422166 net/art: make each strideTable track the IP prefix it represents
This is a prerequisite for path compression, so that insert/delete
can determine when compression occurred.

Updates #7781

Signed-off-by: David Anderson <danderson@tailscale.com>
2023-04-13 09:05:03 -07:00
337 changed files with 5443 additions and 16496 deletions

View File

@ -1,15 +0,0 @@
name: "Dockerfile build"
on:
push:
branches:
- main
pull_request:
branches:
- "*"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Build Docker image"
run: docker build .

View File

@ -50,11 +50,11 @@ jobs:
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
- name: Send pull request
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 #v4.2.4
with:
token: ${{ steps.generate-token.outputs.token }}
author: License Updater <noreply+license-updater@tailscale.com>
committer: License Updater <noreply+license-updater@tailscale.com>
author: License Updater <noreply@tailscale.com>
committer: License Updater <noreply@tailscale.com>
branch: licenses/cli
commit-message: "licenses: update tailscale{,d} licenses"
title: "licenses: update tailscale{,d} licenses"

View File

@ -1,40 +0,0 @@
name: golangci-lint
on:
# For now, only lint pull requests, not the main branches.
pull_request:
# TODO(andrew): enable for main branch after an initial waiting period.
#push:
# branches:
# - main
workflow_dispatch:
permissions:
contents: read
pull-requests: read
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version-file: go.mod
cache: false
- name: golangci-lint
# Note: this is the 'v3' tag as of 2023-04-17
uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299
with:
version: v1.52.2
# Show only new issues if it's a pull request.
only-new-issues: true

View File

@ -1,102 +0,0 @@
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

View File

@ -46,31 +46,14 @@ jobs:
include:
- goarch: amd64
- goarch: amd64
buildflags: "-race"
variant: race
- goarch: "386" # thanks yaml
runs-on: ubuntu-22.04
steps:
- name: checkout
uses: actions/checkout@v3
- name: Restore Cache
uses: actions/cache@v3
with:
# Note: unlike the other setups, this is only grabbing the mod download
# cache, rather than the whole mod directory, as the download cache
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/.cache/go-build
~/go/pkg/mod/cache
~\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') }}-${{ 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}} ./...
run: ./tool/go build ./...
env:
GOARCH: ${{ matrix.goarch }}
- name: build variant CLIs
@ -90,11 +73,13 @@ jobs:
- name: build test wrapper
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
- name: test all
run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}}
if: matrix.variant != 'race'
run: ./tool/go test -exec=/tmp/testwrapper -bench=. -benchtime=1x ./...
env:
GOARCH: ${{ matrix.goarch }}
- name: bench all
run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}} -bench=. -benchtime=1x -run=^$
- name: test all (race)
if: matrix.variant == 'race'
run: ./tool/go test -race -exec=/tmp/testwrapper -bench=. -benchtime=1x ./...
env:
GOARCH: ${{ matrix.goarch }}
- name: check that no tracked files changed
@ -116,13 +101,6 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
go-version-file: go.mod
cache: false
- name: Restore Cache
uses: actions/cache@v3
with:
@ -131,20 +109,17 @@ jobs:
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/.cache/go-build
~/go/pkg/mod/cache
~\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') }}-${{ github.run_id }}
restore-keys: |
${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
${{ github.job }}-${{ runner.os }}-go-2-
# TODO(raggi): add a go version here.
key: ${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
- name: test
# Don't use -bench=. -benchtime=1x.
# Somewhere in the layers (powershell?)
# the equals signs cause great confusion.
run: go test -bench . -benchtime 1x ./...
run: ./tool/go test -bench . -benchtime 1x ./...
vm:
runs-on: ["self-hosted", "linux", "vm"]
@ -199,23 +174,6 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
- name: Restore Cache
uses: actions/cache@v3
with:
# Note: unlike the other setups, this is only grabbing the mod download
# cache, rather than the whole mod directory, as the download cache
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/.cache/go-build
~/go/pkg/mod/cache
~\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') }}-${{ 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/...
env:
@ -265,23 +223,6 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
- name: Restore Cache
uses: actions/cache@v3
with:
# Note: unlike the other setups, this is only grabbing the mod download
# cache, rather than the whole mod directory, as the download cache
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/.cache/go-build
~/go/pkg/mod/cache
~\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') }}-${{ 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
env:
@ -458,7 +399,7 @@ jobs:
# By having the job always run, but skipping its only step as needed, we
# let the CI output collapse nicely in PRs.
if: failure() && github.event_name == 'push'
uses: ruby/action-slack@v3.2.1
uses: ruby/action-slack@v3.0.0
with:
payload: |
{

View File

@ -35,11 +35,11 @@ jobs:
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
- name: Send pull request
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 #v4.2.4
with:
token: ${{ steps.generate-token.outputs.token }}
author: Flakes Updater <noreply+flakes-updater@tailscale.com>
committer: Flakes Updater <noreply+flakes-updater@tailscale.com>
author: Flakes Updater <noreply@tailscale.com>
committer: Flakes Updater <noreply@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"

View File

@ -1,61 +0,0 @@
linters:
# Don't enable any linters by default; just the ones that we explicitly
# enable in the list below.
disable-all: true
enable:
- bidichk
- gofmt
- goimports
- misspell
- revive
# Configuration for how we run golangci-lint
run:
timeout: 5m
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
# These are forks of an upstream package and thus are exempt from stylistic
# changes that would make pulling in upstream changes harder.
- path: tempfork/.*\.go
text: "File is not `gofmt`-ed with `-s` `-r 'interface{} -> any'`"
- path: util/singleflight/.*\.go
text: "File is not `gofmt`-ed with `-s` `-r 'interface{} -> any'`"
# Per-linter settings are contained in this top-level key
linters-settings:
# Enable all rules by default; we don't use invisible unicode runes.
bidichk:
gofmt:
rewrite-rules:
- pattern: 'interface{}'
replacement: 'any'
goimports:
misspell:
revive:
enable-all-rules: false
ignore-generated-header: true
rules:
- name: atomic
- name: context-keys-type
- name: defer
arguments: [[
# Calling 'recover' at the time a defer is registered (i.e. "defer recover()") has no effect.
"immediate-recover",
# Calling 'recover' outside of a deferred function has no effect
"recover",
# Returning values from a deferred function has no effect
"return",
]]
- name: duplicated-imports
- name: errorf
- name: string-of-int
- name: time-equal
- name: unconditional-recursion
- name: useless-break
- name: waitgroup-by-value

View File

@ -47,7 +47,8 @@ RUN go install \
golang.org/x/crypto/ssh \
golang.org/x/crypto/acme \
nhooyr.io/websocket \
github.com/mdlayher/netlink
github.com/mdlayher/netlink \
golang.zx2c4.com/wireguard/device
COPY . .
@ -72,4 +73,4 @@ RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables
COPY --from=build-env /go/bin/* /usr/local/bin/
# For compat with the previous run.sh, although ideally you should be
# using build_docker.sh which sets an entrypoint for the image.
RUN mkdir /tailscale && ln -s /usr/local/bin/containerboot /tailscale/run.sh
RUN ln -s /usr/local/bin/containerboot /tailscale/run.sh

View File

@ -2,4 +2,4 @@
# SPDX-License-Identifier: BSD-3-Clause
FROM alpine:3.16
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables iputils
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables

View File

@ -48,10 +48,11 @@ 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
./tool/go run ./cmd/dist build synology/dsm${SYNO_DSM}/${SYNO_ARCH}
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o tailscale.spk --source=. --goarch=${SYNO_ARCH} --dsm-version=${SYNO_DSM}
spkall: ## Build synology packages for all architectures and DSM versions
./tool/go run ./cmd/dist build synology
mkdir -p spks
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o spks --source=. --goarch=all --dsm-version=all
pushspk: spk ## Push and install synology package on ${SYNO_HOST} host
echo "Pushing SPK to root@${SYNO_HOST} (env var SYNO_HOST) ..."

View File

@ -1 +1 @@
1.45.0
1.39.0

30
api.md
View File

@ -101,8 +101,8 @@ You can also [list all devices in the tailnet](#list-tailnet-devices) to get the
``` jsonc
{
// addresses (array of strings) is a list of Tailscale IP
// addresses for the device, including both IPv4 (formatted as 100.x.y.z)
// and IPv6 (formatted as fd7a:115c:a1e0:a:b:c:d:e) addresses.
// addresses for the device, including both ipv4 (formatted as 100.x.y.z)
// and ipv6 (formatted as fd7a:115c:a1e0:a:b:c:d:e) addresses.
"addresses": [
"100.87.74.78",
"fd7a:115c:a1e0:ac82:4843:ca90:697d:c36e"
@ -503,8 +503,7 @@ 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 or revokes its authorization for tailnets where device authorization is required, according to the `authorized` field in the payload.
Authorize a device. This call marks a device as authorized for tailnets where device authorization is required.
This returns a successful 2xx response with an empty JSON object in the response body.
@ -516,7 +515,7 @@ The ID of the device.
#### `authorized` (required in `POST` body)
Specify whether the device is authorized.
Specify whether the device is authorized. Only 'true' is currently supported.
``` jsonc
{
@ -1222,11 +1221,6 @@ The remaining three methods operate on auth keys and API access tokens.
// expirySeconds (int) is the duration in seconds a new key is valid.
"expirySeconds": 86400
// description (string) is an optional short phrase that describes what
// this key is used for. It can be a maximum of 50 alphanumeric characters.
// Hyphens and underscores are also allowed.
"description": "short description of key purpose"
}
```
@ -1313,9 +1307,6 @@ Note the following about required vs. optional values:
Specifies the duration in seconds until the key should expire.
Defaults to 90 days if not supplied.
- **`description`:** Optional in `POST` body.
A short string specifying the purpose of the key. Can be a maximum of 50 alphanumeric characters. Hyphens and spaces are also allowed.
### Request example
``` jsonc
@ -1333,8 +1324,7 @@ curl "https://api.tailscale.com/api/v2/tailnet/example.com/keys" \
}
}
},
"expirySeconds": 86400,
"description": "dev access"
"expirySeconds": 86400
}'
```
@ -1346,8 +1336,8 @@ It holds the capabilities specified in the request and can no longer be retrieve
``` jsonc
{
"id": "k123456CNTRL",
"key": "tskey-auth-k123456CNTRL-abcdefghijklmnopqrstuvwxyz",
"id": "XXXX456CNTRL",
"key": "tskey-k123456CNTRL-abcdefghijklmnopqrstuvwxyz",
"created": "2021-12-09T23:22:39Z",
"expires": "2022-03-09T23:22:39Z",
"revoked": "2022-03-12T23:22:39Z",
@ -1358,10 +1348,9 @@ It holds the capabilities specified in the request and can no longer be retrieve
"ephemeral": false,
"preauthorized": false,
"tags": [ "tag:example" ]
}
}
}
},
"description": "dev access"
}
```
@ -1413,8 +1402,7 @@ The response is a JSON object with information about the key supplied.
]
}
}
},
"description": "dev access"
}
}
```

View File

@ -30,7 +30,7 @@ func TestDoesNotOverwriteIrregularFiles(t *testing.T) {
}
// The least troublesome thing to make that is not a file is a unix socket.
// Making a null device sadly requires root.
// Making a null device sadly requries root.
l, err := net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
if err != nil {
t.Fatal(err)

View File

@ -16,7 +16,7 @@ if [ -n "${TS_USE_TOOLCHAIN:-}" ]; then
go="./tool/go"
fi
eval `CGO_ENABLED=0 GOOS=$($go env GOHOSTOS) GOARCH=$($go env GOHOSTARCH) $go run ./cmd/mkversion`
eval `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 $go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"
exec ./tool/go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"

View File

@ -436,7 +436,7 @@ func (c *Client) ValidateACLJSON(ctx context.Context, source, dest string) (test
}
}()
tests := []ACLTest{{User: source, Allow: []string{dest}}}
tests := []ACLTest{ACLTest{User: source, Allow: []string{dest}}}
postData, err := json.Marshal(tests)
if err != nil {
return nil, err

View File

@ -12,6 +12,7 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"tailscale.com/types/opt"
)
@ -212,20 +213,8 @@ 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, bytes.NewBuffer(data))
req, err := http.NewRequestWithContext(ctx, "POST", path, strings.NewReader(`{"authorized":true}`))
if err != nil {
return err
}

View File

@ -63,7 +63,7 @@ func (c *Client) dnsGETRequest(ctx context.Context, endpoint string) ([]byte, er
return b, nil
}
func (c *Client) dnsPOSTRequest(ctx context.Context, endpoint string, postData any) ([]byte, error) {
func (c *Client) dnsPOSTRequest(ctx context.Context, endpoint string, postData interface{}) ([]byte, error) {
path := fmt.Sprintf("%s/api/v2/tailnet/%s/dns/%s", c.baseURL(), c.tailnet, endpoint)
data, err := json.Marshal(&postData)
if err != nil {

View File

@ -68,32 +68,12 @@ 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. It returns the secret key itself, which cannot be retrieved again
// can be created. Returns the key itself, which cannot be retrieved again
// later, and the key metadata.
//
// 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")
}
func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (string, *Key, error) {
keyRequest := struct {
Capabilities KeyCapabilities `json:"capabilities"`
ExpirySeconds int64 `json:"expirySeconds,omitempty"`
}{caps, int64(expirySeconds)}
Capabilities KeyCapabilities `json:"capabilities"`
}{caps}
bs, err := json.Marshal(keyRequest)
if err != nil {
return "", nil, err

View File

@ -946,21 +946,6 @@ func (lc *LocalClient) NetworkLockForceLocalDisable(ctx context.Context) error {
return nil
}
// NetworkLockVerifySigningDeeplink verifies the network lock deeplink contained
// in url and returns information extracted from it.
func (lc *LocalClient) NetworkLockVerifySigningDeeplink(ctx context.Context, url string) (*tka.DeeplinkValidationResult, error) {
vr := struct {
URL string
}{url}
body, err := lc.send(ctx, "POST", "/localapi/v0/tka/verify-deeplink", 200, jsonBody(vr))
if err != nil {
return nil, fmt.Errorf("sending verify-deeplink: %w", err)
}
return decodeJSON[*tka.DeeplinkValidationResult](body)
}
// SetServeConfig sets or replaces the serving settings.
// If config is nil, settings are cleared and serving is disabled.
func (lc *LocalClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error {

View File

@ -72,7 +72,7 @@ func NewManualCertManager(certdir, hostname string) (certProvider, error) {
return nil, fmt.Errorf("can not load cert: %w", err)
}
if err := x509Cert.VerifyHostname(hostname); err != nil {
// return nil, fmt.Errorf("cert invalid for hostname %q: %w", hostname, err)
return nil, fmt.Errorf("cert invalid for hostname %q: %w", hostname, err)
}
return &manualCertManager{cert: &cert, hostname: hostname}, nil
}
@ -81,7 +81,7 @@ func (m *manualCertManager) TLSConfig() *tls.Config {
return &tls.Config{
Certificates: nil,
NextProtos: []string{
"http/1.1",
"h2", "http/1.1", // enable HTTP/2
},
GetCertificate: m.getCertificate,
}
@ -89,7 +89,7 @@ func (m *manualCertManager) TLSConfig() *tls.Config {
func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
if hi.ServerName != m.hostname {
//return nil, fmt.Errorf("cert mismatch with hostname: %q", hi.ServerName)
return nil, fmt.Errorf("cert mismatch with hostname: %q", hi.ServerName)
}
// Return a shallow copy of the cert so the caller can append to its

View File

@ -3,34 +3,25 @@ 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
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
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+
L github.com/google/nftables from tailscale.com/util/linuxfw
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
L github.com/google/nftables/expr from github.com/google/nftables+
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
L github.com/google/nftables/xt from github.com/google/nftables/expr+
github.com/golang/protobuf/ptypes/timestamp from github.com/prometheus/client_model/go
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress/flate from nhooyr.io/websocket
github.com/matttproud/golang_protobuf_extensions/pbutil from github.com/prometheus/common/expfmt
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/netlink/nltest from github.com/google/nftables
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz
@ -42,9 +33,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
LD github.com/prometheus/procfs from github.com/prometheus/client_golang/prometheus
LD github.com/prometheus/procfs/internal/fs from github.com/prometheus/procfs
LD github.com/prometheus/procfs/internal/util from github.com/prometheus/procfs
L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
L github.com/vishvananda/netns from github.com/tailscale/netlink+
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/client/tailscale+
go4.org/netipx from tailscale.com/wgengine/filter
@ -76,21 +64,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/prometheus/client_golang/prometheus+
L gvisor.dev/gvisor/pkg/abi from gvisor.dev/gvisor/pkg/abi/linux
L 💣 gvisor.dev/gvisor/pkg/abi/linux from tailscale.com/util/linuxfw
L gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/abi/linux
L gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/abi/linux
L 💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/abi/linux+
L 💣 gvisor.dev/gvisor/pkg/hostarch from gvisor.dev/gvisor/pkg/abi/linux+
L gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
L gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context
L gvisor.dev/gvisor/pkg/marshal from gvisor.dev/gvisor/pkg/abi/linux+
L 💣 gvisor.dev/gvisor/pkg/marshal/primitive from gvisor.dev/gvisor/pkg/abi/linux
L 💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/abi/linux+
L gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
L 💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/linewriter+
L gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context
google.golang.org/protobuf/types/known/timestamppb from github.com/golang/protobuf/ptypes/timestamp+
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
@ -112,13 +86,11 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
💣 tailscale.com/net/interfaces from tailscale.com/net/netns+
tailscale.com/net/netaddr from tailscale.com/ipn+
tailscale.com/net/netknob from tailscale.com/net/netns
tailscale.com/net/netmon from tailscale.com/net/sockstats+
tailscale.com/net/netns from tailscale.com/derp/derphttp
tailscale.com/net/netutil from tailscale.com/client/tailscale
tailscale.com/net/packet from tailscale.com/wgengine/filter
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
tailscale.com/net/stun from tailscale.com/cmd/derper
L tailscale.com/net/tcpinfo from tailscale.com/derp
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
tailscale.com/net/tsaddr from tailscale.com/ipn+
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
@ -151,15 +123,13 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
W tailscale.com/util/clientmetric from tailscale.com/net/tshttpproxy
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
tailscale.com/util/cmpx from tailscale.com/cmd/derper+
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
tailscale.com/util/dnsname from tailscale.com/hostinfo+
tailscale.com/util/httpm from tailscale.com/client/tailscale
tailscale.com/util/lineread from tailscale.com/hostinfo+
L 💣 tailscale.com/util/linuxfw from tailscale.com/net/netns
tailscale.com/util/mak from tailscale.com/syncs+
tailscale.com/util/multierr from tailscale.com/health+
tailscale.com/util/set from tailscale.com/health+
tailscale.com/util/multierr from tailscale.com/health
tailscale.com/util/set from tailscale.com/health
tailscale.com/util/singleflight from tailscale.com/net/dnscache
tailscale.com/util/slicesx from tailscale.com/cmd/derper+
tailscale.com/util/vizerror from tailscale.com/tsweb
@ -182,7 +152,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/exp/constraints from golang.org/x/exp/slices
golang.org/x/exp/maps from tailscale.com/types/views
golang.org/x/exp/slices from tailscale.com/net/tsaddr+
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+
@ -208,7 +177,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
bytes from bufio+
compress/flate from compress/gzip+
compress/gzip from internal/profile+
L compress/zlib from debug/elf
container/list from crypto/tls+
context from crypto/tls+
crypto from crypto/ecdsa+
@ -232,8 +200,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
crypto/tls from golang.org/x/crypto/acme+
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+
L debug/dwarf from debug/elf
L debug/elf from golang.org/x/sys/unix
embed from crypto/internal/nistec+
encoding from encoding/json+
encoding/asn1 from crypto/x509+
@ -249,7 +215,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
fmt from compress/flate+
go/token from google.golang.org/protobuf/internal/strs
hash from crypto+
L hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+
hash/fnv from google.golang.org/protobuf/internal/detrand
hash/maphash from go4.org/mem
@ -258,7 +223,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
io/fs from crypto/x509+
io/ioutil from github.com/mitchellh/go-ps+
log from expvar+
log/internal from log
math from compress/flate+
math/big from crypto/dsa+
math/bits from compress/flate+

View File

@ -33,7 +33,6 @@ import (
"tailscale.com/net/stun"
"tailscale.com/tsweb"
"tailscale.com/types/key"
"tailscale.com/util/cmpx"
)
var (
@ -437,7 +436,11 @@ func defaultMeshPSKFile() string {
}
func rateLimitedListenAndServeTLS(srv *http.Server) error {
ln, err := net.Listen("tcp", cmpx.Or(srv.Addr, ":https"))
addr := srv.Addr
if addr == "" {
addr = ":https"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}

25
cmd/dist/dist.go vendored
View File

@ -13,38 +13,15 @@ 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) {
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
return unixpkgs.Targets(), 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)
}

View File

@ -16,7 +16,6 @@ import (
"golang.org/x/oauth2/clientcredentials"
"tailscale.com/client/tailscale"
"tailscale.com/util/cmpx"
)
func main() {
@ -30,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")
}
@ -40,19 +39,22 @@ func main() {
log.Fatal("at least one tag must be specified")
}
baseURL := cmpx.Or(os.Getenv("TS_BASE_URL"), "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{

View File

@ -25,6 +25,7 @@ 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"
@ -37,6 +38,7 @@ 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"
@ -46,7 +48,6 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/util/dnsname"
"tailscale.com/version"
)
func main() {
@ -63,7 +64,6 @@ 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,33 +183,32 @@ 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.ByObject{
Field: client.InNamespace(tsNamespace).AsSelector(),
nsFilter := cache.ObjectSelector{
Field: fields.SelectorFromSet(fields.Set{"metadata.namespace": tsNamespace}),
}
restConfig := config.GetConfigOrDie()
mgr, err := manager.New(restConfig, manager.Options{
Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{
NewCache: cache.BuilderWithOptions(cache.Options{
SelectorsByObject: map[client.Object]cache.ObjectSelector{
&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,
proxyPriorityClassName: priorityClassName,
logger: zlog.Named("service-reconciler"),
Client: mgr.GetClient(),
tsClient: tsClient,
defaultTags: strings.Split(tags, ","),
operatorNamespace: tsNamespace,
proxyImage: image,
logger: zlog.Named("service-reconciler"),
}
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(_ context.Context, o client.Object) []reconcile.Request {
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request {
ls := o.GetLabels()
if ls[LabelManaged] != "true" {
return nil
@ -229,14 +228,14 @@ waitOnline:
err = builder.
ControllerManagedBy(mgr).
For(&corev1.Service{}).
Watches(&appsv1.StatefulSet{}, reconcileFilter).
Watches(&corev1.Secret{}, reconcileFilter).
Watches(&source.Kind{Type: &appsv1.StatefulSet{}}, reconcileFilter).
Watches(&source.Kind{Type: &corev1.Secret{}}, reconcileFilter).
Complete(sr)
if err != nil {
startlog.Fatalf("could not create controller: %v", err)
}
startlog.Infof("Startup complete, operator running, version: %s", version.Long())
startlog.Infof("Startup complete, operator running")
if shouldRunAuthProxy {
cfg, err := restConfig.TransportConfig()
if err != nil {
@ -279,12 +278,11 @@ const (
// ServiceReconciler is a simple ControllerManagedBy example implementation.
type ServiceReconciler struct {
client.Client
tsClient tsClient
defaultTags []string
operatorNamespace string
proxyImage string
proxyPriorityClassName string
logger *zap.SugaredLogger
tsClient tsClient
defaultTags []string
operatorNamespace string
proxyImage string
logger *zap.SugaredLogger
}
type tsClient interface {
@ -568,9 +566,6 @@ 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
@ -594,7 +589,6 @@ func (a *ServiceReconciler) newAuthKey(ctx context.Context, tags []string) (stri
},
},
}
key, _, err := a.tsClient.CreateKey(ctx, caps)
if err != nil {
return "", err
@ -639,7 +633,6 @@ 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 })
}

View File

@ -14,6 +14,7 @@ import (
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
@ -64,7 +65,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,8 +111,6 @@ 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.
@ -187,7 +186,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",
@ -284,7 +283,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
@ -328,7 +327,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{
@ -400,7 +399,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
@ -449,8 +448,6 @@ 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.
@ -459,7 +456,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{
@ -526,7 +523,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",
@ -585,51 +582,6 @@ 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{
@ -678,7 +630,7 @@ func expectedHeadlessService(name string) *corev1.Service {
}
}
func expectedSTS(stsName, secretName, hostname, priorityClassName string) *appsv1.StatefulSet {
func expectedSTS(stsName, secretName, hostname string) *appsv1.StatefulSet {
return &appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{
Kind: "StatefulSet",
@ -707,7 +659,6 @@ func expectedSTS(stsName, secretName, hostname, priorityClassName string) *appsv
},
Spec: corev1.PodSpec{
ServiceAccountName: "proxies",
PriorityClassName: priorityClassName,
InitContainers: []corev1.Container{
{
Name: "sysctler",
@ -719,11 +670,11 @@ func expectedSTS(stsName, secretName, hostname, priorityClassName string) *appsv
},
},
},
Containers: []corev1.Container{
Containers: []v1.Container{
{
Name: "tailscale",
Image: "tailscale/tailscale",
Env: []corev1.EnvVar{
Env: []v1.EnvVar{
{Name: "TS_USERSPACE", Value: "false"},
{Name: "TS_AUTH_ONCE", Value: "true"},
{Name: "TS_DEST_IP", Value: "10.20.30.40"},
@ -781,21 +732,6 @@ 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))
@ -879,6 +815,7 @@ 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

View File

@ -272,7 +272,7 @@ func (p *proxy) serve(sessionID int64, c net.Conn) error {
}
if buf[0] != 'S' {
p.errors.Add("upstream-bad-protocol", 1)
return fmt.Errorf("upstream didn't acknowledge start-ssl, said %q", buf[0])
return fmt.Errorf("upstream didn't acknowldge start-ssl, said %q", buf[0])
}
tlsConf := &tls.Config{
ServerName: p.upstreamHost,

View File

@ -18,7 +18,6 @@ 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"
@ -26,7 +25,6 @@ import (
var (
ports = flag.String("ports", "443", "comma-separated list of ports to proxy")
wgPort = flag.Int("wg-listen-port", 0, "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
promoteHTTPS = flag.Bool("promote-https", true, "promote HTTP to HTTPS")
)
@ -38,10 +36,7 @@ func main() {
log.Fatal("no ports")
}
hostinfo.SetApp("sniproxy")
var s server
s.ts.Port = uint16(*wgPort)
defer s.ts.Close()
lc, err := s.ts.LocalClient()

View File

@ -22,7 +22,6 @@ import (
"tailscale.com/tstest"
"tailscale.com/types/persist"
"tailscale.com/types/preftype"
"tailscale.com/util/cmpx"
"tailscale.com/version/distro"
)
@ -720,7 +719,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var warnBuf tstest.MemLogger
goos := cmpx.Or(tt.goos, "linux")
goos := tt.goos
if goos == "" {
goos = "linux"
}
st := tt.st
if st == nil {
st = new(ipnstate.Status)

View File

@ -66,7 +66,7 @@ func isSystemdSystem() bool {
return false
}
switch distro.Get() {
case distro.QNAP, distro.Gokrazy, distro.Synology, distro.Unraid:
case distro.QNAP, distro.Gokrazy, distro.Synology:
return false
}
_, err := exec.LookPath("systemctl")

View File

@ -30,10 +30,10 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
return &ffcli.Command{
Name: "funnel",
ShortHelp: "Turn on/off Funnel service",
ShortUsage: strings.Join([]string{
"funnel <serve-port> {on|off}",
"funnel status [--json]",
}, "\n "),
ShortUsage: strings.TrimSpace(`
funnel <serve-port> {on|off}
funnel status [--json]
`),
LongHelp: strings.Join([]string{
"Funnel allows you to publish a 'tailscale serve'",
"server publicly, open to the entire internet.",

View File

@ -19,7 +19,6 @@ import (
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/net/netcheck"
"tailscale.com/net/netmon"
"tailscale.com/net/portmapper"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
@ -46,15 +45,9 @@ var netcheckArgs struct {
}
func runNetcheck(ctx context.Context, args []string) error {
logf := logger.WithPrefix(log.Printf, "portmap: ")
netMon, err := netmon.New(logf)
if err != nil {
return err
}
c := &netcheck.Client{
UDPBindAddr: envknob.String("TS_DEBUG_NETCHECK_UDP_BIND"),
PortMapper: portmapper.NewClient(logf, netMon, nil, nil),
UseDNSCache: false, // always resolve, don't cache
PortMapper: portmapper.NewClient(logger.WithPrefix(log.Printf, "portmap: "), nil, nil),
}
if netcheckArgs.verbose {
c.Logf = logger.WithPrefix(log.Printf, "netcheck: ")
@ -103,6 +96,7 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
var err error
switch netcheckArgs.format {
case "":
break
case "json":
j, err = json.MarshalIndent(report, "", "\t")
case "json-line":

View File

@ -465,16 +465,7 @@ func runNetworkLockSign(ctx context.Context, args []string) error {
}
}
err := localClient.NetworkLockSign(ctx, nodeKey, []byte(rotationKey.Verifier()))
// Provide a better help message for when someone clicks through the signing flow
// on the wrong device.
if err != nil && strings.Contains(err.Error(), "this node is not trusted by network lock") {
fmt.Fprintln(os.Stderr, "Error: Signing is not available on this device because it does not have a trusted tailnet lock key.")
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, "Try again on a signing device instead. Tailnet admins can see signing devices on the admin panel.")
fmt.Fprintln(os.Stderr)
}
return err
return localClient.NetworkLockSign(ctx, nodeKey, []byte(rotationKey.Verifier()))
}
var nlDisableCmd = &ffcli.Command{

View File

@ -51,7 +51,7 @@ relay node.
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through WireGuard, but not either host OS stack)")
fs.BoolVar(&pingArgs.icmp, "icmp", false, "do a ICMP-level ping (through WireGuard, but not the local host OS stack)")
fs.BoolVar(&pingArgs.peerAPI, "peerapi", false, "try hitting the peer's peerapi HTTP server")
fs.IntVar(&pingArgs.num, "c", 10, "max number of pings to send. 0 for infinity.")
fs.IntVar(&pingArgs.num, "c", 10, "max number of pings to send")
fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
return fs
})(),

View File

@ -16,7 +16,6 @@ import (
"path"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
@ -35,14 +34,12 @@ func newServeCommand(e *serveEnv) *ffcli.Command {
return &ffcli.Command{
Name: "serve",
ShortHelp: "Serve content and local servers",
ShortUsage: strings.Join([]string{
"serve http:<port> <mount-point> <source> [off]",
"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",
}, "\n "),
ShortUsage: strings.TrimSpace(`
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]
`),
LongHelp: strings.TrimSpace(`
*** BETA; all of this is subject to change ***
@ -59,8 +56,8 @@ EXAMPLES
- To proxy requests to a web server at 127.0.0.1:3000:
$ tailscale serve https:443 / http://127.0.0.1:3000
Or, using the default port (443):
$ tailscale serve https / http://127.0.0.1:3000
Or, using the default port:
$ tailscale serve https / http://127.0.0.1:3000
- To serve a single file or a directory of files:
$ tailscale serve https / /home/alice/blog/index.html
@ -69,12 +66,6 @@ EXAMPLES
- To serve simple static text:
$ tailscale serve https:8080 / text:"Hello, world!"
- To serve over HTTP (tailnet only):
$ tailscale serve http:80 / http://127.0.0.1:3000
Or, using the default port (80):
$ tailscale serve http / http://127.0.0.1:3000
- To forward incoming TCP connections on port 2222 to a local TCP server on
port 22 (e.g. to run OpenSSH in parallel with Tailscale SSH):
$ tailscale serve tcp:2222 tcp://localhost:22
@ -95,13 +86,6 @@ EXAMPLES
}),
UsageFunc: usageFunc,
},
{
Name: "reset",
Exec: e.runServeReset,
ShortHelp: "reset current serve/funnel config",
FlagSet: e.newFlags("serve-reset", nil),
UsageFunc: usageFunc,
},
},
}
}
@ -182,7 +166,6 @@ func (e *serveEnv) getLocalClientStatus(ctx context.Context) (*ipnstate.Status,
// serve config types like proxy, path, and text.
//
// Examples:
// - tailscale serve http / http://localhost:3000
// - tailscale serve https / http://localhost:3000
// - tailscale serve https /images/ /var/www/images/
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
@ -207,14 +190,19 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
return e.lc.SetServeConfig(ctx, sc)
}
parsePort := func(portStr string) (uint16, error) {
port64, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return 0, err
}
return uint16(port64), nil
}
srcType, srcPortStr, found := strings.Cut(args[0], ":")
if !found {
if srcType == "https" && srcPortStr == "" {
// Default https port to 443.
srcPortStr = "443"
} else if srcType == "http" && srcPortStr == "" {
// Default http port to 80.
srcPortStr = "80"
} else {
return flag.ErrHelp
}
@ -222,18 +210,18 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
turnOff := "off" == args[len(args)-1]
if len(args) < 2 || ((srcType == "https" || srcType == "http") && !turnOff && len(args) < 3) {
if len(args) < 2 || (srcType == "https" && !turnOff && len(args) < 3) {
fmt.Fprintf(os.Stderr, "error: invalid number of arguments\n\n")
return flag.ErrHelp
}
srcPort, err := parseServePort(srcPortStr)
srcPort, err := parsePort(srcPortStr)
if err != nil {
return fmt.Errorf("invalid port %q: %w", srcPortStr, err)
return err
}
switch srcType {
case "https", "http":
case "https":
mount, err := cleanMountPoint(args[1])
if err != nil {
return err
@ -241,8 +229,7 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
if turnOff {
return e.handleWebServeRemove(ctx, srcPort, mount)
}
useTLS := srcType == "https"
return e.handleWebServe(ctx, srcPort, useTLS, mount, args[2])
return e.handleWebServe(ctx, srcPort, mount, args[2])
case "tcp", "tls-terminated-tcp":
if turnOff {
return e.handleTCPServeRemove(ctx, srcPort)
@ -250,20 +237,20 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
return e.handleTCPServe(ctx, srcType, srcPort, args[1])
default:
fmt.Fprintf(os.Stderr, "error: invalid serve type %q\n", srcType)
fmt.Fprint(os.Stderr, "must be one of: http:<port>, https:<port>, tcp:<port> or tls-terminated-tcp:<port>\n\n", srcType)
fmt.Fprint(os.Stderr, "must be one of: https:<port>, tcp:<port> or tls-terminated-tcp:<port>\n\n", srcType)
return flag.ErrHelp
}
}
// handleWebServe handles the "tailscale serve (http/https):..." subcommand. It
// configures the serve config to forward HTTPS connections to the given source.
// handleWebServe handles the "tailscale serve https:..." subcommand.
// It configures the serve config to forward HTTPS connections to the
// given source.
//
// Examples:
// - tailscale serve http / http://localhost:3000
// - tailscale serve https / http://localhost:3000
// - tailscale serve https:8443 /files/ /home/alice/shared-files/
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, useTLS bool, mount, source string) error {
func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, mount, source string) error {
h := new(ipn.HTTPHandler)
ts, _, _ := strings.Cut(source, ":")
@ -322,7 +309,7 @@ func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, useTLS bo
return flag.ErrHelp
}
mak.Set(&sc.TCP, srvPort, &ipn.TCPPortHandler{HTTPS: useTLS, HTTP: !useTLS})
mak.Set(&sc.TCP, srvPort, &ipn.TCPPortHandler{HTTPS: true})
if _, ok := sc.Web[hp]; !ok {
mak.Set(&sc.Web, hp, new(ipn.WebServerConfig))
@ -425,7 +412,6 @@ 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
}
@ -436,26 +422,6 @@ 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
@ -630,10 +596,7 @@ func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
printf("\n")
}
for hp := range sc.Web {
err := e.printWebStatusTree(sc, hp)
if err != nil {
return err
}
printWebStatusTree(sc, hp)
printf("\n")
}
printFunnelWarning(sc)
@ -672,37 +635,20 @@ func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.S
return nil
}
func (e *serveEnv) printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) error {
// No-op if no serve config
func printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) {
if sc == nil {
return nil
return
}
fStatus := "tailnet only"
if sc.AllowFunnel[hp] {
fStatus = "Funnel on"
}
host, portStr, _ := net.SplitHostPort(string(hp))
port, err := parseServePort(portStr)
if err != nil {
return fmt.Errorf("invalid port %q: %w", portStr, err)
if portStr == "443" {
printf("https://%s (%s)\n", host, fStatus)
} else {
printf("https://%s:%s (%s)\n", host, portStr, fStatus)
}
scheme := "https"
if sc.IsServingHTTP(port) {
scheme = "http"
}
portPart := ":" + portStr
if scheme == "http" && portStr == "80" ||
scheme == "https" && portStr == "443" {
portPart = ""
}
if scheme == "http" {
hostname, _, _ := strings.Cut("host", ".")
printf("%s://%s%s (%s)\n", scheme, hostname, portPart, fStatus)
}
printf("%s://%s%s (%s)\n", scheme, host, portPart, fStatus)
srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
switch {
case h.Path != "":
@ -729,8 +675,6 @@ func (e *serveEnv) printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) erro
t, d := srvTypeAndDesc(h)
printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
}
return nil
}
func elipticallyTruncate(s string, max int) string {
@ -739,28 +683,3 @@ 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)
}
// parseServePort parses a port number from a string and returns it as a
// uint16. It returns an error if the port number is invalid or zero.
func parseServePort(s string) (uint16, error) {
p, err := strconv.ParseUint(s, 10, 16)
if err != nil {
return 0, err
}
if p == 0 {
return 0, errors.New("port number must be non-zero")
}
return uint16(p), nil
}

View File

@ -89,59 +89,6 @@ func TestServeConfigMutations(t *testing.T) {
wantErr: exactErr(flag.ErrHelp, "flag.ErrHelp"),
})
// https
add(step{reset: true})
add(step{ // allow omitting port (default to 80)
command: cmd("http / http://localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{ // support non Funnel port
command: cmd("http:9999 /abc http://localhost:3001"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 9999: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:9999": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
add(step{
command: cmd("http:9999 /abc off"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{
command: cmd("http:8080 /abc http://127.0.0.1:3001"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 8080: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8080": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
// https
add(step{reset: true})
add(step{
@ -277,10 +224,7 @@ func TestServeConfigMutations(t *testing.T) {
command: cmd("https:443 bar https://127.0.0.1:8443"),
want: nil, // nothing to save
})
add(step{ // try resetting using reset command
command: cmd("reset"),
want: &ipn.ServeConfig{},
})
add(step{reset: true})
add(step{
command: cmd("https:443 / https+insecure://127.0.0.1:3001"),
want: &ipn.ServeConfig{

View File

@ -13,13 +13,11 @@ import (
"fmt"
"log"
"net/netip"
"net/url"
"os"
"os/signal"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"syscall"
@ -28,9 +26,6 @@ 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"
@ -668,10 +663,6 @@ 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,
@ -1111,96 +1102,3 @@ 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
}

View File

@ -29,7 +29,6 @@ import (
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
"tailscale.com/util/cmpx"
"tailscale.com/util/groupmember"
"tailscale.com/version/distro"
)
@ -62,8 +61,6 @@ type tmplData struct {
TUNMode bool
IsSynology bool
DSMVersion int // 6 or 7, if IsSynology=true
IsUnraid bool
UnraidToken string
IPNVersion string
}
@ -156,7 +153,10 @@ func runWeb(ctx context.Context, args []string) error {
// urlOfListenAddr parses a given listen address into a formatted URL
func urlOfListenAddr(addr string) string {
host, port, _ := net.SplitHostPort(addr)
return fmt.Sprintf("http://%s", net.JoinHostPort(cmpx.Or(host, "127.0.0.1"), port))
if host == "" {
host = "127.0.0.1"
}
return fmt.Sprintf("http://%s", net.JoinHostPort(host, port))
}
// authorize returns the name of the user accessing the web UI after verifying
@ -441,8 +441,6 @@ 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")

View File

@ -116,12 +116,10 @@
<a class="text-xs text-gray-500 hover:text-gray-600" href="{{ .LicensesURL }}">Open Source Licenses</a>
</footer>
<script>(function () {
const advertiseExitNode = {{ .AdvertiseExitNode }};
const isUnraid = {{ .IsUnraid }};
const unraidCsrfToken = "{{ .UnraidToken }}";
const advertiseExitNode = {{.AdvertiseExitNode}};
let fetchingUrl = false;
var data = {
AdvertiseRoutes: "{{ .AdvertiseRoutes }}",
AdvertiseRoutes: "{{.AdvertiseRoutes}}",
AdvertiseExitNode: advertiseExitNode,
Reauthenticate: false,
ForceLogout: false
@ -143,27 +141,15 @@ function postData(e) {
}
const nextUrl = new URL(window.location);
nextUrl.search = nextParams.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": contentType,
"Content-Type": "application/json",
},
body: body
body: JSON.stringify(data)
}).then(res => res.json()).then(res => {
fetchingUrl = false;
const err = res["error"];
@ -172,11 +158,7 @@ function postData(e) {
}
const url = res["url"];
if (url) {
if(isUnraid) {
window.open(url, "_blank");
} else {
document.location.href = url;
}
document.location.href = url;
} else {
location.reload();
}

View File

@ -3,26 +3,17 @@ 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
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
github.com/fxamacker/cbor/v2 from tailscale.com/tka
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
L github.com/google/nftables from tailscale.com/util/linuxfw
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
L github.com/google/nftables/expr from github.com/google/nftables+
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
L github.com/google/nftables/xt from github.com/google/nftables/expr+
github.com/google/uuid from tailscale.com/util/quarantine+
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
github.com/klauspost/compress/flate from nhooyr.io/websocket
@ -30,7 +21,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
💣 github.com/mattn/go-isatty from github.com/mattn/go-colorable+
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/netlink/nltest from github.com/google/nftables
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
@ -44,30 +34,13 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
L github.com/vishvananda/netns from github.com/tailscale/netlink+
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/derp+
go4.org/netipx from tailscale.com/wgengine/filter
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
gopkg.in/yaml.v2 from sigs.k8s.io/yaml
L gvisor.dev/gvisor/pkg/abi from gvisor.dev/gvisor/pkg/abi/linux
L 💣 gvisor.dev/gvisor/pkg/abi/linux from tailscale.com/util/linuxfw
L gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/abi/linux
L gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/abi/linux
L 💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/abi/linux+
L 💣 gvisor.dev/gvisor/pkg/hostarch from gvisor.dev/gvisor/pkg/abi/linux+
L gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
L gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context
L gvisor.dev/gvisor/pkg/marshal from gvisor.dev/gvisor/pkg/abi/linux+
L 💣 gvisor.dev/gvisor/pkg/marshal/primitive from gvisor.dev/gvisor/pkg/abi/linux
L 💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/abi/linux+
L gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
L 💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/linewriter+
L gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context
k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
nhooyr.io/websocket from tailscale.com/derp/derphttp+
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
@ -101,7 +74,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
tailscale.com/net/neterror from tailscale.com/net/netcheck+
tailscale.com/net/netknob from tailscale.com/net/netns
tailscale.com/net/netmon from tailscale.com/net/sockstats+
tailscale.com/net/netns from tailscale.com/derp/derphttp+
tailscale.com/net/netutil from tailscale.com/client/tailscale+
tailscale.com/net/packet from tailscale.com/wgengine/filter+
@ -109,7 +81,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
tailscale.com/net/stun from tailscale.com/net/netcheck
L tailscale.com/net/tcpinfo from tailscale.com/derp
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp+
tailscale.com/net/tsaddr from tailscale.com/net/interfaces+
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
@ -140,13 +111,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/util/clientmetric from tailscale.com/net/netcheck+
tailscale.com/util/cloudenv from tailscale.com/net/dnscache+
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
tailscale.com/util/cmpx from tailscale.com/cmd/tailscale/cli+
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
tailscale.com/util/groupmember from tailscale.com/cmd/tailscale/cli
tailscale.com/util/httpm from tailscale.com/client/tailscale
tailscale.com/util/lineread from tailscale.com/net/interfaces+
L 💣 tailscale.com/util/linuxfw from tailscale.com/net/netns
tailscale.com/util/mak from tailscale.com/net/netcheck+
tailscale.com/util/multierr from tailscale.com/control/controlhttp+
tailscale.com/util/must from tailscale.com/cmd/tailscale/cli
@ -173,7 +142,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/exp/constraints from golang.org/x/exp/slices
golang.org/x/exp/maps from tailscale.com/types/views
golang.org/x/exp/slices from tailscale.com/net/tsaddr+
golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+
@ -183,12 +151,9 @@ 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+
@ -205,7 +170,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
bytes from bufio+
compress/flate from compress/gzip+
compress/gzip from net/http
compress/zlib from image/png+
compress/zlib from image/png
container/list from crypto/tls+
context from crypto/tls+
crypto from crypto/ecdsa+
@ -230,8 +195,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+
database/sql/driver from github.com/google/uuid
L debug/dwarf from debug/elf
L debug/elf from golang.org/x/sys/unix
embed from tailscale.com/cmd/tailscale/cli+
encoding from encoding/json+
encoding/asn1 from crypto/x509+
@ -259,7 +222,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
io/fs from crypto/x509+
io/ioutil from golang.org/x/sys/cpu+
log from expvar+
log/internal from log
math from compress/flate+
math/big from crypto/dsa+
math/bits from compress/flate+

View File

@ -23,10 +23,10 @@ import (
"tailscale.com/derp/derphttp"
"tailscale.com/ipn"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/wgengine/monitor"
)
var debugArgs struct {
@ -42,7 +42,7 @@ var debugModeFunc = debugMode // so it can be addressable
func debugMode(args []string) error {
fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.BoolVar(&debugArgs.ifconfig, "ifconfig", false, "If true, print network interface state")
fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run network monitor forever. Precludes all other options.")
fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run link monitor forever. Precludes all other options.")
fs.BoolVar(&debugArgs.portmap, "portmap", false, "If true, run portmap debugging. Precludes all other options.")
fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.")
fs.StringVar(&debugArgs.derpCheck, "derp", "", "if non-empty, test a DERP ping via named region code")
@ -76,7 +76,7 @@ func runMonitor(ctx context.Context, loop bool) error {
j, _ := json.MarshalIndent(st, "", " ")
os.Stderr.Write(j)
}
mon, err := netmon.New(log.Printf)
mon, err := monitor.New(log.Printf)
if err != nil {
return err
}
@ -84,10 +84,10 @@ func runMonitor(ctx context.Context, loop bool) error {
mon.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
if !changed {
log.Printf("Network monitor fired; no change")
log.Printf("Link monitor fired; no change")
return
}
log.Printf("Network monitor fired. New state:")
log.Printf("Link monitor fired. New state:")
dump(st)
})
if loop {
@ -193,8 +193,8 @@ func checkDerp(ctx context.Context, derpRegion string) (err error) {
priv1 := key.NewNode()
priv2 := key.NewNode()
c1 := derphttp.NewRegionClient(priv1, log.Printf, nil, getRegion)
c2 := derphttp.NewRegionClient(priv2, log.Printf, nil, getRegion)
c1 := derphttp.NewRegionClient(priv1, log.Printf, getRegion)
c2 := derphttp.NewRegionClient(priv2, log.Printf, getRegion)
defer func() {
if err != nil {
c1.Close()

View File

@ -3,9 +3,7 @@ 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
@ -14,7 +12,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+
@ -40,7 +38,6 @@ 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
@ -51,19 +48,16 @@ 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+
@ -75,23 +69,16 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/smithy-go/transport/http from github.com/aws/aws-sdk-go-v2/aws/middleware+
L github.com/aws/smithy-go/transport/http/internal/io from github.com/aws/smithy-go/transport/http
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
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
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
L github.com/google/nftables from tailscale.com/util/linuxfw
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
L github.com/google/nftables/expr from github.com/google/nftables+
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
L github.com/google/nftables/xt from github.com/google/nftables/expr+
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L 💣 github.com/illarion/gonotify from tailscale.com/net/dns
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun
@ -106,7 +93,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
@ -115,21 +102,15 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/mdlayher/genetlink from tailscale.com/net/tstun
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/netlink/nltest from github.com/google/nftables
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/alias from github.com/tailscale/golang-x-crypto/chacha20
LD 💣 github.com/tailscale/golang-x-crypto/internal/subtle 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+
@ -160,18 +141,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
go4.org/netipx from tailscale.com/ipn/ipnlocal+
W 💣 golang.zx2c4.com/wintun from github.com/tailscale/wireguard-go/tun+
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/dns+
L gvisor.dev/gvisor/pkg/abi from gvisor.dev/gvisor/pkg/abi/linux
L 💣 gvisor.dev/gvisor/pkg/abi/linux from tailscale.com/util/linuxfw
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/tcpip+
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/bufferv2+
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/bufferv2
💣 gvisor.dev/gvisor/pkg/bufferv2 from gvisor.dev/gvisor/pkg/tcpip+
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs+
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
L 💣 gvisor.dev/gvisor/pkg/hostarch from gvisor.dev/gvisor/pkg/abi/linux+
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
L gvisor.dev/gvisor/pkg/marshal from gvisor.dev/gvisor/pkg/abi/linux+
L 💣 gvisor.dev/gvisor/pkg/marshal/primitive from gvisor.dev/gvisor/pkg/abi/linux
gvisor.dev/gvisor/pkg/rand from gvisor.dev/gvisor/pkg/tcpip/network/hash+
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/bufferv2+
💣 gvisor.dev/gvisor/pkg/sleep from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
@ -264,19 +240,17 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
tailscale.com/net/netknob from tailscale.com/net/netns+
tailscale.com/net/netmon from tailscale.com/cmd/tailscaled+
tailscale.com/net/netns from tailscale.com/derp/derphttp+
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnauth+
tailscale.com/net/netutil from tailscale.com/ipn/ipnlocal+
tailscale.com/net/packet from tailscale.com/net/tstun+
tailscale.com/net/ping from tailscale.com/net/netcheck+
tailscale.com/net/ping from tailscale.com/net/netcheck
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
tailscale.com/net/routetable from tailscale.com/doctor/routetable
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
tailscale.com/net/stun from tailscale.com/net/netcheck+
L tailscale.com/net/tcpinfo from tailscale.com/derp
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
tailscale.com/net/tsaddr from tailscale.com/ipn+
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
@ -295,8 +269,7 @@ 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/tsd from tailscale.com/cmd/tailscaled+
tailscale.com/tstime from tailscale.com/wgengine/magicsock+
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
@ -321,7 +294,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/clientmetric from tailscale.com/control/controlclient+
tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+
LW tailscale.com/util/cmpver from tailscale.com/net/dns+
tailscale.com/util/cmpx from tailscale.com/derp/derphttp+
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+
tailscale.com/util/dnsname from tailscale.com/hostinfo+
@ -330,7 +302,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
tailscale.com/util/httpm from tailscale.com/client/tailscale+
tailscale.com/util/lineread from tailscale.com/hostinfo+
L 💣 tailscale.com/util/linuxfw from tailscale.com/net/netns+
tailscale.com/util/mak from tailscale.com/control/controlclient+
tailscale.com/util/multierr from tailscale.com/control/controlclient+
tailscale.com/util/must from tailscale.com/logpolicy
@ -353,6 +324,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/capture from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/monitor from tailscale.com/control/controlclient+
tailscale.com/wgengine/netlog from tailscale.com/wgengine
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+
@ -379,7 +351,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+
golang.org/x/exp/constraints from golang.org/x/exp/slices+
golang.org/x/exp/maps from tailscale.com/wgengine+
golang.org/x/exp/maps from tailscale.com/wgengine
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from net+
@ -412,7 +384,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
bytes from bufio+
compress/flate from compress/gzip+
compress/gzip from golang.org/x/net/http2+
L compress/zlib from debug/elf
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
container/list from crypto/tls+
context from crypto/tls+
@ -437,8 +408,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
crypto/tls from github.com/tcnksm/go-httpstat+
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+
L debug/dwarf from debug/elf
L debug/elf from golang.org/x/sys/unix
embed from tailscale.com+
encoding from encoding/json+
encoding/asn1 from crypto/x509+
@ -454,7 +423,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
flag from net/http/httptest+
fmt from compress/flate+
hash from crypto+
hash/adler32 from tailscale.com/ipn/ipnlocal+
hash/adler32 from tailscale.com/ipn/ipnlocal
hash/crc32 from compress/gzip+
hash/fnv from tailscale.com/wgengine/magicsock+
hash/maphash from go4.org/mem
@ -463,7 +432,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
io/fs from crypto/x509+
io/ioutil from github.com/godbus/dbus/v5+
log from expvar+
log/internal from log
LD log/syslog from tailscale.com/ssh/tailssh
math from compress/flate+
math/big from crypto/dsa+

View File

@ -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, distro.Unraid:
case distro.Synology, distro.TrueNAS, distro.QNAP:
// See if they have a "Taildrop" share.
// See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319
path, err := findTaildropDir(dg)
@ -42,8 +42,6 @@ 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)
}
@ -105,25 +103,3 @@ 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)
}

View File

@ -39,7 +39,6 @@ import (
"tailscale.com/logtail"
"tailscale.com/net/dns"
"tailscale.com/net/dnsfallback"
"tailscale.com/net/netmon"
"tailscale.com/net/netns"
"tailscale.com/net/proxymux"
"tailscale.com/net/socks5"
@ -50,7 +49,6 @@ 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"
@ -61,6 +59,7 @@ import (
"tailscale.com/version"
"tailscale.com/version/distro"
"tailscale.com/wgengine"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/netstack"
"tailscale.com/wgengine/router"
)
@ -330,19 +329,7 @@ var logPol *logpolicy.Policy
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 := logpolicy.New(logtail.CollectionNode)
pol.SetVerbosityLevel(args.verbose)
logPol = pol
defer func() {
@ -366,6 +353,7 @@ func run() error {
return nil
}
var logf logger.Logf = log.Printf
if envknob.Bool("TS_DEBUG_MEMORY") {
logf = logger.RusagePrefixLog(logf)
}
@ -391,10 +379,10 @@ func run() error {
debugMux = newDebugMux()
}
return startIPNServer(context.Background(), logf, pol.PublicID, sys)
return startIPNServer(context.Background(), logf, pol.PublicID)
}
func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) error {
func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID) error {
ln, err := safesocket.Listen(args.socketpath)
if err != nil {
return fmt.Errorf("safesocket.Listen: %v", err)
@ -420,7 +408,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
}
}()
srv := ipnserver.New(logf, logID, sys.NetMon.Get())
srv := ipnserver.New(logf, logID)
if debugMux != nil {
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)
}
@ -438,7 +426,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
return
}
}
lb, err := getLocalBackend(ctx, logf, logID, sys)
lb, err := getLocalBackend(ctx, logf, logID)
if err == nil {
logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond))
srv.SetLocalBackend(lb)
@ -462,28 +450,35 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
return nil
}
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) (_ *ipnlocal.LocalBackend, retErr error) {
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID) (_ *ipnlocal.LocalBackend, retErr error) {
linkMon, err := monitor.New(logf)
if err != nil {
return nil, fmt.Errorf("monitor.New: %w", err)
}
if logPol != nil {
logPol.Logtail.SetNetMon(sys.NetMon.Get())
logPol.Logtail.SetLinkMonitor(linkMon)
}
socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr)
dialer := &tsdial.Dialer{Logf: logf} // mutated below (before used)
sys.Set(dialer)
onlyNetstack, err := createEngine(logf, sys)
e, onlyNetstack, err := createEngine(logf, linkMon, dialer)
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 ms, ok := sys.MagicSock.GetOK(); ok {
debugMux.HandleFunc("/debug/magicsock", ms.ServeHTTPDebug)
if ig, ok := e.(wgengine.InternalsGetter); ok {
if _, mc, _, ok := ig.GetInternals(); ok {
debugMux.HandleFunc("/debug/magicsock", mc.ServeHTTPDebug)
}
}
go runDebugServer(debugMux, args.debug)
}
ns, err := newNetstack(logf, sys)
ns, err := newNetstack(logf, dialer, e)
if err != nil {
return nil, fmt.Errorf("newNetstack: %w", err)
}
@ -491,7 +486,6 @@ 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
@ -522,15 +516,16 @@ 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, sys, opts.LoginFlags)
lb, err := ipnlocal.NewLocalBackend(logf, logID, store, dialer, e, opts.LoginFlags)
if err != nil {
return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err)
}
@ -539,7 +534,7 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
lb.SetLogFlusher(logPol.Logtail.StartFlush)
}
if root := lb.TailscaleVarRoot(); root != "" {
dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json"), logf)
dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json"))
}
lb.SetDecompressor(func() (controlclient.Decompressor, error) {
return smallzstd.NewDecoder(nil)
@ -556,21 +551,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, sys *tsd.System) (onlyNetstack bool, err error) {
func createEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer) (e wgengine.Engine, onlyNetstack bool, err error) {
if args.tunname == "" {
return false, errors.New("no --tun value specified")
return nil, false, errors.New("no --tun value specified")
}
var errs []error
for _, name := range strings.Split(args.tunname, ",") {
logf("wgengine.NewUserspaceEngine(tun %q) ...", name)
onlyNetstack, err = tryEngine(logf, sys, name)
e, onlyNetstack, err = tryEngine(logf, linkMon, dialer, name)
if err == nil {
return onlyNetstack, nil
return e, onlyNetstack, nil
}
logf("wgengine.NewUserspaceEngine(tun %q) error: %v", name, err)
errs = append(errs, err)
}
return false, multierr.New(errs...)
return nil, false, multierr.New(errs...)
}
// handleSubnetsInNetstack reports whether netstack should handle subnet routers
@ -595,23 +590,21 @@ func handleSubnetsInNetstack() bool {
var tstunNew = tstun.New
func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack bool, err error) {
func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, name string) (e wgengine.Engine, onlyNetstack bool, err error) {
conf := wgengine.Config{
ListenPort: args.port,
NetMon: sys.NetMon.Get(),
Dialer: sys.Dialer.Get(),
SetSubsystem: sys.Set,
ListenPort: args.port,
LinkMonitor: linkMon,
Dialer: dialer,
}
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 false, fmt.Errorf("createBIRDClient: %w", err)
return nil, false, fmt.Errorf("createBIRDClient: %w", err)
}
}
if onlyNetstack {
@ -624,55 +617,44 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo
// TODO(bradfitz): add a Synology-specific DNS manager.
conf.DNS, err = dns.NewOSConfigurator(logf, "") // empty interface name
if err != nil {
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
}
}
} else {
dev, devName, err := tstunNew(logf, name)
if err != nil {
tstun.Diagnose(logf, name, err)
return false, fmt.Errorf("tstun.New(%q): %w", name, err)
return nil, 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)
if err != nil {
return false, err
}
sys.Set(e)
return false, err
return e, false, err
}
r, err := router.New(logf, dev, sys.NetMon.Get())
r, err := router.New(logf, dev, linkMon)
if err != nil {
dev.Close()
return false, fmt.Errorf("creating router: %w", err)
return nil, false, fmt.Errorf("creating router: %w", err)
}
d, err := dns.NewOSConfigurator(logf, devName)
if err != nil {
dev.Close()
r.Close()
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
return nil, 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 onlyNetstack, err
return nil, onlyNetstack, err
}
e = wgengine.NewWatchdog(e)
sys.Set(e)
sys.NetstackRouter.Set(netstackSubnetRouter)
return onlyNetstack, nil
return e, onlyNetstack, nil
}
func newDebugMux() *http.ServeMux {
@ -702,8 +684,12 @@ func runDebugServer(mux *http.ServeMux, addr string) {
}
}
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())
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)
}
// mustStartProxyListeners creates listeners for local SOCKS and HTTP

View File

@ -45,9 +45,7 @@ import (
"tailscale.com/logpolicy"
"tailscale.com/logtail/backoff"
"tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/net/tstun"
"tailscale.com/tsd"
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/util/winutil"
@ -126,10 +124,6 @@ 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 {
@ -297,15 +291,8 @@ func beWindowsSubprocess() bool {
}
}()
sys := new(tsd.System)
netMon, err := netmon.New(log.Printf)
if err != nil {
log.Fatalf("Could not create netMon: %v", err)
}
sys.Set(netMon)
publicLogID, _ := logid.ParsePublicID(logID)
err = startIPNServer(ctx, log.Printf, publicLogID, sys)
err := startIPNServer(ctx, log.Printf, publicLogID)
if err != nil {
log.Fatalf("ipnserver: %v", err)
}

View File

@ -7,20 +7,16 @@
package flakytest
import (
"fmt"
"os"
"regexp"
"testing"
)
// FlakyTestLogMessage is a sentinel value that is printed to stderr when a
// flaky test is marked. This is used by cmd/testwrapper to detect flaky tests
// and retry them.
const FlakyTestLogMessage = "flakytest: this is a known flaky test"
// FlakeAttemptEnv is an environment variable that is set by cmd/testwrapper
// when a flaky test is retried. It contains the attempt number, starting at 1.
const FlakeAttemptEnv = "TS_TESTWRAPPER_ATTEMPT"
// InTestWrapper returns whether or not this binary is running under our test
// wrapper.
func InTestWrapper() bool {
return os.Getenv("TS_IN_TESTWRAPPER") != ""
}
var issueRegexp = regexp.MustCompile(`\Ahttps://github\.com/tailscale/[a-zA-Z0-9_.-]+/issues/\d+\z`)
@ -34,6 +30,16 @@ func Mark(t testing.TB, issue string) {
t.Fatalf("bad issue format: %q", issue)
}
fmt.Fprintln(os.Stderr, FlakyTestLogMessage) // sentinel value for testwrapper
t.Logf("flakytest: issue tracking this flaky test: %s", issue)
if !InTestWrapper() {
return
}
t.Cleanup(func() {
if t.Failed() {
t.Logf("flakytest: signaling test wrapper to retry test")
// Signal to test wrapper that we should restart.
os.Exit(123)
}
})
}

View File

@ -3,10 +3,7 @@
package flakytest
import (
"os"
"testing"
)
import "testing"
func TestIssueFormat(t *testing.T) {
testCases := []struct {
@ -27,17 +24,3 @@ func TestIssueFormat(t *testing.T) {
}
}
}
// TestFlakeRun is a test that fails when run in the testwrapper
// for the first time, but succeeds on the second run.
// It's used to test whether the testwrapper retries flaky tests.
func TestFlakeRun(t *testing.T) {
Mark(t, "https://github.com/tailscale/tailscale/issues/0") // random issue
e := os.Getenv(FlakeAttemptEnv)
if e == "" {
t.Skip("not running in testwrapper")
}
if e == "1" {
t.Fatal("First run in testwrapper, failing so that test is retried. This is expected.")
}
}

View File

@ -1,278 +1,62 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// testwrapper is a wrapper for retrying flaky tests. It is an alternative to
// `go test` and re-runs failed marked flaky tests (using the flakytest pkg). It
// takes different arguments than go test and requires the first positional
// argument to be the pattern to test.
// testwrapper is a wrapper for retrying flaky tests, using the -exec flag of
// 'go test'. Tests that are flaky can use the 'flakytest' subpackage to mark
// themselves as flaky and be retried on failure.
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"sort"
"strings"
"time"
"golang.org/x/exp/maps"
"tailscale.com/cmd/testwrapper/flakytest"
)
const maxAttempts = 3
type testAttempt struct {
name testName
outcome string // "pass", "fail", "skip"
logs bytes.Buffer
isMarkedFlaky bool // set if the test is marked as flaky
pkgFinished bool
}
type testName struct {
pkg string // "tailscale.com/types/key"
name string // "TestFoo"
}
type packageTests struct {
// pattern is the package pattern to run.
// Must be a single pattern, not a list of patterns.
pattern string // "./...", "./types/key"
// tests is a list of tests to run. If empty, all tests in the package are
// run.
tests []string // ["TestFoo", "TestBar"]
}
type goTestOutput struct {
Time time.Time
Action string
Package string
Test string
Output string
}
var debug = os.Getenv("TS_TESTWRAPPER_DEBUG") != ""
// runTests runs the tests in pt and sends the results on ch. It sends a
// testAttempt for each test and a final testAttempt per pkg with pkgFinished
// set to true.
// It calls close(ch) when it's done.
func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []string, ch chan<- *testAttempt) {
defer close(ch)
args := []string{"test", "-json", pt.pattern}
args = append(args, otherArgs...)
if len(pt.tests) > 0 {
runArg := strings.Join(pt.tests, "|")
args = append(args, "-run", runArg)
}
if debug {
fmt.Println("running", strings.Join(args, " "))
}
cmd := exec.CommandContext(ctx, "go", args...)
r, err := cmd.StdoutPipe()
if err != nil {
log.Printf("error creating stdout pipe: %v", err)
}
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", flakytest.FlakeAttemptEnv, attempt))
if err := cmd.Start(); err != nil {
log.Printf("error starting test: %v", err)
os.Exit(1)
}
done := make(chan struct{})
go func() {
defer close(done)
cmd.Wait()
}()
jd := json.NewDecoder(r)
resultMap := make(map[testName]*testAttempt)
for {
var goOutput goTestOutput
if err := jd.Decode(&goOutput); err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) {
break
}
panic(err)
}
if goOutput.Test == "" {
switch goOutput.Action {
case "fail", "pass", "skip":
ch <- &testAttempt{
name: testName{
pkg: goOutput.Package,
},
outcome: goOutput.Action,
pkgFinished: true,
}
}
continue
}
name := testName{
pkg: goOutput.Package,
name: goOutput.Test,
}
if test, _, isSubtest := strings.Cut(goOutput.Test, "/"); isSubtest {
name.name = test
if goOutput.Action == "output" {
resultMap[name].logs.WriteString(goOutput.Output)
}
continue
}
switch goOutput.Action {
case "start":
// ignore
case "run":
resultMap[name] = &testAttempt{
name: name,
}
case "skip", "pass", "fail":
resultMap[name].outcome = goOutput.Action
ch <- resultMap[name]
case "output":
if strings.TrimSpace(goOutput.Output) == flakytest.FlakyTestLogMessage {
resultMap[name].isMarkedFlaky = true
} else {
resultMap[name].logs.WriteString(goOutput.Output)
}
}
}
<-done
}
const (
retryStatus = 123
maxIterations = 3
)
func main() {
ctx := context.Background()
debug := os.Getenv("TS_TESTWRAPPER_DEBUG") != ""
// We only need to parse the -v flag to figure out whether to print the logs
// for a test. We don't need to parse any other flags, so we just use the
// flag package to parse the -v flag and then pass the rest of the args
// through to 'go test'.
// We run `go test -json` which returns the same information as `go test -v`,
// but in a machine-readable format. So this flag is only for testwrapper's
// output.
v := flag.Bool("v", false, "verbose")
flag.Usage = func() {
fmt.Println("usage: testwrapper [testwrapper-flags] [pattern] [build/test flags & test binary flags]")
fmt.Println()
fmt.Println("testwrapper-flags:")
flag.CommandLine.PrintDefaults()
fmt.Println()
fmt.Println("examples:")
fmt.Println("\ttestwrapper -v ./... -count=1")
fmt.Println("\ttestwrapper ./pkg/foo -run TestBar -count=1")
fmt.Println()
fmt.Println("Unlike 'go test', testwrapper requires a package pattern as the first positional argument and only supports a single pattern.")
}
flag.Parse()
args := flag.Args()
if len(args) < 1 || strings.HasPrefix(args[0], "-") {
fmt.Println("no pattern specified")
flag.Usage()
os.Exit(1)
} else if len(args) > 1 && !strings.HasPrefix(args[1], "-") {
fmt.Println("expected single pattern")
flag.Usage()
os.Exit(1)
}
pattern, otherArgs := args[0], args[1:]
type nextRun struct {
tests []*packageTests
attempt int
log.SetPrefix("testwrapper: ")
if !debug {
log.SetFlags(0)
}
toRun := []*nextRun{
{
tests: []*packageTests{{pattern: pattern}},
attempt: 1,
},
}
printPkgOutcome := func(pkg, outcome string, attempt int) {
if outcome == "skip" {
fmt.Printf("?\t%s [skipped/no tests] \n", pkg)
for i := 1; i <= maxIterations; i++ {
if i > 1 {
log.Printf("retrying flaky tests (%d of %d)", i, maxIterations)
}
cmd := exec.CommandContext(ctx, os.Args[1], os.Args[2:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), "TS_IN_TESTWRAPPER=1")
err := cmd.Run()
if err == nil {
return
}
if outcome == "pass" {
outcome = "ok"
}
if outcome == "fail" {
outcome = "FAIL"
}
if attempt > 1 {
fmt.Printf("%s\t%s [attempt=%d]\n", outcome, pkg, attempt)
return
}
fmt.Printf("%s\t%s\n", outcome, pkg)
}
for len(toRun) > 0 {
var thisRun *nextRun
thisRun, toRun = toRun[0], toRun[1:]
if thisRun.attempt >= maxAttempts {
fmt.Println("max attempts reached")
os.Exit(1)
}
if thisRun.attempt > 1 {
fmt.Printf("\n\nAttempt #%d: Retrying flaky tests:\n\n", thisRun.attempt)
}
failed := false
toRetry := make(map[string][]string) // pkg -> tests to retry
for _, pt := range thisRun.tests {
ch := make(chan *testAttempt)
go runTests(ctx, thisRun.attempt, pt, otherArgs, ch)
for tr := range ch {
if tr.pkgFinished {
printPkgOutcome(tr.name.pkg, tr.outcome, thisRun.attempt)
continue
}
if *v || tr.outcome == "fail" {
io.Copy(os.Stdout, &tr.logs)
}
if tr.outcome != "fail" {
continue
}
if tr.isMarkedFlaky {
toRetry[tr.name.pkg] = append(toRetry[tr.name.pkg], tr.name.name)
} else {
failed = true
}
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) {
if debug {
log.Printf("error isn't an ExitError")
}
}
if failed {
fmt.Println("\n\nNot retrying flaky tests because non-flaky tests failed.")
os.Exit(1)
}
if len(toRetry) == 0 {
continue
if code := exitErr.ExitCode(); code != retryStatus {
if debug {
log.Printf("code (%d) != retryStatus (%d)", code, retryStatus)
}
os.Exit(code)
}
pkgs := maps.Keys(toRetry)
sort.Strings(pkgs)
nextRun := &nextRun{
attempt: thisRun.attempt + 1,
}
for _, pkg := range pkgs {
tests := toRetry[pkg]
sort.Strings(tests)
nextRun.tests = append(nextRun.tests, &packageTests{
pattern: pkg,
tests: tests,
})
}
toRun = append(toRun, nextRun)
}
log.Printf("test did not pass in %d iterations", maxIterations)
os.Exit(1)
}

View File

@ -37,7 +37,6 @@ import (
"tailscale.com/safesocket"
"tailscale.com/smallzstd"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/wgengine"
"tailscale.com/wgengine/netstack"
"tailscale.com/words"
@ -47,7 +46,7 @@ import (
var ControlURL = ipn.DefaultControlURL
func main() {
js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) any {
js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
log.Fatal("Usage: newIPN(config)")
return nil
@ -97,19 +96,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,
SetSubsystem: sys.Set,
Dialer: dialer,
})
if err != nil {
log.Fatal(err)
}
sys.Set(eng)
ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
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)
if err != nil {
log.Fatalf("netstack.Create: %v", err)
}
@ -122,11 +121,10 @@ 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, sys, controlclient.LoginEphemeral)
srv := ipnserver.New(logf, logid)
lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, controlclient.LoginEphemeral)
if err != nil {
log.Fatalf("ipnlocal.NewLocalBackend: %v", err)
}
@ -148,7 +146,7 @@ func newIPN(jsConfig js.Value) map[string]any {
}
return map[string]any{
"run": js.FuncOf(func(this js.Value, args []js.Value) any {
"run": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
log.Fatal(`Usage: run({
notifyState(state: int): void,
@ -161,7 +159,7 @@ func newIPN(jsConfig js.Value) map[string]any {
jsIPN.run(args[0])
return nil
}),
"login": js.FuncOf(func(this js.Value, args []js.Value) any {
"login": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 0 {
log.Printf("Usage: login()")
return nil
@ -169,7 +167,7 @@ func newIPN(jsConfig js.Value) map[string]any {
jsIPN.login()
return nil
}),
"logout": js.FuncOf(func(this js.Value, args []js.Value) any {
"logout": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 0 {
log.Printf("Usage: logout()")
return nil
@ -177,7 +175,7 @@ func newIPN(jsConfig js.Value) map[string]any {
jsIPN.logout()
return nil
}),
"ssh": js.FuncOf(func(this js.Value, args []js.Value) any {
"ssh": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 3 {
log.Printf("Usage: ssh(hostname, userName, termConfig)")
return nil
@ -187,7 +185,7 @@ func newIPN(jsConfig js.Value) map[string]any {
args[1].String(),
args[2])
}),
"fetch": js.FuncOf(func(this js.Value, args []js.Value) any {
"fetch": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
log.Printf("Usage: fetch(url)")
return nil
@ -336,10 +334,10 @@ func (i *jsIPN) ssh(host, username string, termConfig js.Value) map[string]any {
go jsSSHSession.Run()
return map[string]any{
"close": js.FuncOf(func(this js.Value, args []js.Value) any {
"close": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
return jsSSHSession.Close() != nil
}),
"resize": js.FuncOf(func(this js.Value, args []js.Value) any {
"resize": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
rows := args[0].Int()
cols := args[1].Int()
return jsSSHSession.Resize(rows, cols) != nil
@ -428,7 +426,7 @@ func (s *jsSSHSession) Run() {
session.Stdout = termWriter{writeFn}
session.Stderr = termWriter{writeFn}
setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) any {
setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) interface{} {
input := args[0].String()
_, err := stdin.Write([]byte(input))
if err != nil {
@ -498,7 +496,7 @@ func (i *jsIPN) fetch(url string) js.Value {
return map[string]any{
"status": res.StatusCode,
"statusText": res.Status,
"text": js.FuncOf(func(this js.Value, args []js.Value) any {
"text": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
return makePromise(func() (any, error) {
defer res.Body.Close()
buf := new(bytes.Buffer)
@ -604,7 +602,7 @@ func generateHostname() string {
// f is run on a goroutine and its return value is used to resolve the promise
// (or reject it if an error is returned).
func makePromise(f func() (any, error)) js.Value {
handler := js.FuncOf(func(this js.Value, args []js.Value) any {
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
resolve := args[0]
reject := args[1]
go func() {

View File

@ -9,7 +9,7 @@ import (
"net/netip"
)
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded --clone-only-type=OnlyGetClone
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone --clone-only-type=OnlyGetClone
type StructWithoutPtrs struct {
Int int
@ -61,8 +61,3 @@ type StructWithSlices struct {
type OnlyGetClone struct {
SinViewerPorFavor bool
}
type StructWithEmbedded struct {
A *StructWithPtrs
StructWithSlices
}

View File

@ -211,22 +211,3 @@ 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
}{})

View File

@ -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,StructWithEmbedded
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone
// View returns a readonly view of StructWithPtrs.
func (p *StructWithPtrs) View() StructWithPtrsView {
@ -325,59 +325,3 @@ 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
}{})

View File

@ -398,7 +398,7 @@ type maxMsgBuffer [maxMessageSize]byte
// bufPool holds the temporary buffers for Conn.Read & Write.
var bufPool = &sync.Pool{
New: func() any {
New: func() interface{} {
return new(maxMsgBuffer)
},
}

View File

@ -20,7 +20,7 @@ func dumpGoroutinesToURL(c *http.Client, targetURL string) {
zbuf := new(bytes.Buffer)
zw := gzip.NewWriter(zbuf)
zw.Write(goroutines.ScrubbedGoroutineDump(true))
zw.Write(goroutines.ScrubbedGoroutineDump())
zw.Close()
req, err := http.NewRequestWithContext(ctx, "PUT", targetURL, zbuf)

View File

@ -37,7 +37,6 @@ import (
"tailscale.com/net/dnscache"
"tailscale.com/net/dnsfallback"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/net/netutil"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tsdial"
@ -55,20 +54,20 @@ import (
"tailscale.com/util/multierr"
"tailscale.com/util/singleflight"
"tailscale.com/util/systemd"
"tailscale.com/wgengine/monitor"
)
// Direct is the client that connects to a tailcontrol server for a node.
type Direct struct {
httpc *http.Client // HTTP client used to talk to tailcontrol
dialer *tsdial.Dialer
dnsCache *dnscache.Resolver
serverURL string // URL of the tailcontrol server
timeNow func() time.Time
lastPrintMap time.Time
newDecompressor func() (Decompressor, error)
keepAlive bool
logf logger.Logf
netMon *netmon.Monitor // or nil
linkMon *monitor.Mon // or nil
discoPubKey key.DiscoPublic
getMachinePrivKey func() (key.MachinePrivate, error)
debugFlags []string
@ -114,7 +113,7 @@ type Options struct {
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
NoiseTestClient *http.Client // optional HTTP client to use for noise RPCs (tests only)
DebugFlags []string // debug settings to send to control
NetMon *netmon.Monitor // optional network monitor
LinkMonitor *monitor.Mon // optional link monitor
PopBrowserURL func(url string) // optional func to open browser
OnClientVersion func(*tailcfg.ClientVersion) // optional func to inform GUI of client version status
OnControlTime func(time.Time) // optional func to notify callers of new time from control
@ -200,14 +199,6 @@ 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
@ -217,6 +208,12 @@ func NewDirect(opts Options) (*Direct, error) {
httpc = http.DefaultClient
}
if httpc == nil {
dnsCache := &dnscache.Resolver{
Forward: dnscache.Get().Forward, // use default cache's forwarder
UseLastGood: true,
LookupIPFallback: dnsfallback.Lookup,
Logf: opts.Logf,
}
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
@ -244,7 +241,7 @@ func NewDirect(opts Options) (*Direct, error) {
discoPubKey: opts.DiscoPublicKey,
debugFlags: opts.DebugFlags,
keepSharerAndUserSplit: opts.KeepSharerAndUserSplit,
netMon: opts.NetMon,
linkMon: opts.LinkMonitor,
skipIPForwardingCheck: opts.SkipIPForwardingCheck,
pinger: opts.Pinger,
popBrowser: opts.PopBrowserURL,
@ -252,7 +249,6 @@ func NewDirect(opts Options) (*Direct, error) {
onControlTime: opts.OnControlTime,
c2nHandler: opts.C2NHandler,
dialer: opts.Dialer,
dnsCache: dnsCache,
dialPlan: opts.DialPlan,
}
if opts.Hostinfo == nil {
@ -875,8 +871,8 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
ReadOnly: readOnly && !allowStream,
}
var extraDebugFlags []string
if hi != nil && c.netMon != nil && !c.skipIPForwardingCheck &&
ipForwardingBroken(hi.RoutableIPs, c.netMon.InterfaceState()) {
if hi != nil && c.linkMon != nil && !c.skipIPForwardingCheck &&
ipForwardingBroken(hi.RoutableIPs, c.linkMon.InterfaceState()) {
extraDebugFlags = append(extraDebugFlags, "warn-ip-forwarding-off")
}
if health.RouterHealth() != nil {
@ -1512,16 +1508,7 @@ func (c *Direct) getNoiseClient() (*NoiseClient, error) {
return nil, err
}
c.logf("creating new noise client")
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,
})
nc, err := NewNoiseClient(k, serverNoiseKey, c.serverURL, c.dialer, dp)
if err != nil {
return nil, err
}

View File

@ -19,12 +19,9 @@ import (
"golang.org/x/net/http2"
"tailscale.com/control/controlbase"
"tailscale.com/control/controlhttp"
"tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/util/mak"
"tailscale.com/util/multierr"
"tailscale.com/util/singleflight"
@ -159,7 +156,6 @@ 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
@ -171,9 +167,6 @@ type NoiseClient struct {
// be nil.
dialPlan func() *tailcfg.ControlDialPlan
logf logger.Logf
netMon *netmon.Monitor
// mu only protects the following variables.
mu sync.Mutex
last *noiseConn // or nil
@ -181,39 +174,12 @@ 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(opts NoiseOpts) (*NoiseClient, error) {
u, err := url.Parse(opts.ServerURL)
func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string, dialer *tsdial.Dialer, dialPlan func() *tailcfg.ControlDialPlan) (*NoiseClient, error) {
u, err := url.Parse(serverURL)
if err != nil {
return nil, err
}
@ -233,18 +199,14 @@ func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
httpPort = "80"
httpsPort = "443"
}
np := &NoiseClient{
serverPubKey: opts.ServerPubKey,
privKey: opts.PrivKey,
serverPubKey: serverPubKey,
privKey: privKey,
host: u.Hostname(),
httpPort: httpPort,
httpsPort: httpsPort,
dialer: opts.Dialer,
dnsCache: opts.DNSCache,
dialPlan: opts.DialPlan,
logf: opts.Logf,
netMon: opts.NetMon,
dialer: dialer,
dialPlan: dialPlan,
}
// Create the HTTP/2 Transport using a net/http.Transport
@ -287,25 +249,6 @@ func (nc *NoiseClient) GetSingleUseRoundTripper(ctx context.Context) (http.Round
return nil, nil, errors.New("[unexpected] failed to reserve a request on a connection")
}
// contextErr is an error that wraps another error and is used to indicate that
// the error was because a context expired.
type contextErr struct {
err error
}
func (e contextErr) Error() string {
return e.err.Error()
}
func (e contextErr) Unwrap() error {
return e.err
}
// getConn returns a noiseConn that can be used to make requests to the
// coordination server. It may return a cached connection or create a new one.
// Dials are singleflighted, so concurrent calls to getConn may only dial once.
// As such, context values may not be respected as there are no guarantees that
// the context passed to getConn is the same as the context passed to dial.
func (nc *NoiseClient) getConn(ctx context.Context) (*noiseConn, error) {
nc.mu.Lock()
if last := nc.last; last != nil && last.canTakeNewRequest() {
@ -314,35 +257,11 @@ func (nc *NoiseClient) getConn(ctx context.Context) (*noiseConn, error) {
}
nc.mu.Unlock()
for {
// We singeflight the dial to avoid making multiple connections, however
// that means that we can't simply cancel the dial if the context is
// canceled. Instead, we have to additionally check that the context
// which was canceled is our context and retry if our context is still
// valid.
conn, err, _ := nc.sfDial.Do(struct{}{}, func() (*noiseConn, error) {
c, err := nc.dial(ctx)
if err != nil {
if ctx.Err() != nil {
return nil, contextErr{ctx.Err()}
}
return nil, err
}
return c, nil
})
var ce contextErr
if err == nil || !errors.As(err, &ce) {
return conn, err
}
if ctx.Err() == nil {
// The dial failed because of a context error, but our context
// is still valid. Retry.
continue
}
// The dial failed because our context was canceled. Return the
// underlying error.
return nil, ce.Unwrap()
conn, err, _ := nc.sfDial.Do(struct{}{}, nc.dial)
if err != nil {
return nil, err
}
return conn, nil
}
func (nc *NoiseClient) RoundTrip(req *http.Request) (*http.Response, error) {
@ -387,7 +306,7 @@ func (nc *NoiseClient) Close() error {
// dial opens a new connection to tailcontrol, fetching the server noise key
// if not cached.
func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
func (nc *NoiseClient) dial() (*noiseConn, error) {
nc.mu.Lock()
connID := nc.nextID
nc.nextID++
@ -435,7 +354,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
}
timeout := time.Duration(timeoutSec * float64(time.Second))
ctx, cancel := context.WithTimeout(ctx, timeout)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
clientConn, err := (&controlhttp.Dialer{
@ -446,10 +365,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
ControlKey: nc.serverPubKey,
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
Dialer: nc.dialer.SystemDial,
DNSCache: nc.dnsCache,
DialPlan: dialPlan,
Logf: nc.logf,
NetMon: nc.netMon,
}).Dial(ctx)
if err != nil {
return nil, err

View File

@ -74,12 +74,7 @@ func (tt noiseClientTest) run(t *testing.T) {
defer hs.Close()
dialer := new(tsdial.Dialer)
nc, err := NewNoiseClient(NoiseOpts{
PrivKey: clientPrivate,
ServerPubKey: serverPrivate.Public(),
ServerURL: hs.URL,
Dialer: dialer,
})
nc, err := NewNoiseClient(clientPrivate, serverPrivate.Public(), hs.URL, dialer, nil)
if err != nil {
t.Fatal(err)
}

View File

@ -374,22 +374,6 @@ 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.
@ -405,10 +389,14 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr,
SingleHostStaticResult: []netip.Addr{addr},
SingleHost: u.Hostname(),
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
NetMon: a.NetMon,
}
} else {
dns = a.resolver()
dns = &dnscache.Resolver{
Forward: dnscache.Get().Forward,
LookupIPFallback: dnsfallback.Lookup,
UseLastGood: true,
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
}
}
var dialer dnscache.DialContextFunc

View File

@ -9,7 +9,6 @@ import (
"time"
"tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@ -67,17 +66,10 @@ type Dialer struct {
// If not specified, this defaults to net.Dialer.DialContext.
Dialer dnscache.DialContextFunc
// DNSCache is the caching Resolver used by this Dialer.
//
// If not specified, a new Resolver is created per attempt.
DNSCache *dnscache.Resolver
// Logf, if set, is a logging function to use; if unset, logs are
// dropped.
Logf logger.Logf
NetMon *netmon.Monitor
// DialPlan, if set, contains instructions from the control server on
// how to connect to it. If present, we will try the methods in this
// plan before falling back to DNS.

View File

@ -583,20 +583,19 @@ func TestDialPlan(t *testing.T) {
}},
want: goodAddr,
},
// TODO(#8442): fix this test
// {
// name: "multiple-priority-fast-path",
// plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{
// // Dials some good IPs and our bad one (which
// // hangs forever), which then hits the fast
// // path where we bail without waiting.
// {IP: brokenAddr, Priority: 1, DialTimeoutSec: 10},
// {IP: goodAddr, Priority: 1, DialTimeoutSec: 10},
// {IP: other2Addr, Priority: 1, DialTimeoutSec: 10},
// {IP: otherAddr, Priority: 2, DialTimeoutSec: 10},
// }},
// want: otherAddr,
// },
{
name: "multiple-priority-fast-path",
plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{
// Dials some good IPs and our bad one (which
// hangs forever), which then hits the fast
// path where we bail without waiting.
{IP: brokenAddr, Priority: 1, DialTimeoutSec: 10},
{IP: goodAddr, Priority: 1, DialTimeoutSec: 10},
{IP: other2Addr, Priority: 1, DialTimeoutSec: 10},
{IP: otherAddr, Priority: 2, DialTimeoutSec: 10},
}},
want: otherAddr,
},
{
name: "multiple-priority-slow-path",
plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{

View File

@ -1,61 +0,0 @@
# 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.

View File

@ -498,7 +498,7 @@ func (s *Server) registerClient(c *sclient) {
switch set := set.(type) {
case nil:
s.clients[c.key] = singleClient{c}
c.debugLogf("register single client")
c.debug("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.debugLogf("register duplicate client")
c.debug("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.debugLogf("register another duplicate client")
c.debug("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.debugLogf("removed connection")
c.logf("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.debugLogf("removed duplicate client")
c.debug("removed duplicate client")
if set.removeClient(c) {
s.dupClientConns.Add(-1)
} else {
@ -712,12 +712,9 @@ 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.debug = true
c.debugLogging = true
}
}
if s.debug {
c.debug = true
}
s.registerClient(c)
defer s.unregisterClient(c)
@ -730,12 +727,6 @@ 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
@ -753,20 +744,16 @@ func (c *sclient) run(ctx context.Context) error {
defer func() {
cancelSender()
if err := grp.Wait(); err != nil && !c.s.isClosed() {
if errors.Is(err, context.Canceled) {
c.debugLogf("sender canceled by reader exiting")
} else {
c.logf("sender failed: %v", err)
}
c.logf("sender failed: %v", err)
}
}()
for {
ft, fl, err := readFrameHeader(c.br)
c.debugLogf("read frame type %d len %d err %v", ft, fl, err)
c.debug("read frame type %d len %d err %v", ft, fl, err)
if err != nil {
if errors.Is(err, io.EOF) {
c.debugLogf("read EOF")
c.logf("read EOF")
return nil
}
if c.s.isClosed() {
@ -923,7 +910,7 @@ func (c *sclient) handleFrameForwardPacket(ft frameType, fl uint32) error {
return nil
}
dst.debugLogf("received forwarded packet from %s via %s", srcKey.ShortString(), c.key.ShortString())
dst.debug("received forwarded packet from %s via %s", srcKey.ShortString(), c.key.ShortString())
return c.sendPkt(dst, pkt{
bs: contents,
@ -973,7 +960,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.debugLogf("SendPacket for %s, forwarding via %s: %v", dstKey.ShortString(), fwd, err)
c.debug("SendPacket for %s, forwarding via %s: %v", dstKey.ShortString(), fwd, err)
if err != nil {
// TODO:
return nil
@ -987,10 +974,10 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
c.requestPeerGoneWriteLimited(dstKey, contents, PeerGoneReasonNotHere)
}
s.recordDrop(contents, c.key, dstKey, reason)
c.debugLogf("SendPacket for %s, dropping with reason=%s", dstKey.ShortString(), reason)
c.debug("SendPacket for %s, dropping with reason=%s", dstKey.ShortString(), reason)
return nil
}
c.debugLogf("SendPacket for %s, sending directly", dstKey.ShortString())
c.debug("SendPacket for %s, sending directly", dstKey.ShortString())
p := pkt{
bs: contents,
@ -1000,8 +987,8 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
return c.sendPkt(dst, p)
}
func (c *sclient) debugLogf(format string, v ...any) {
if c.debug {
func (c *sclient) debug(format string, v ...any) {
if c.debugLogging {
c.logf(format, v...)
}
}
@ -1024,8 +1011,7 @@ const (
func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, reason dropReason) {
s.packetsDropped.Add(1)
s.packetsDroppedReasonCounters[reason].Add(1)
looksDisco := disco.LooksLikeDiscoWrapper(packetBytes)
if looksDisco {
if disco.LooksLikeDiscoWrapper(packetBytes) {
s.packetsDroppedTypeDisco.Add(1)
} else {
s.packetsDroppedTypeOther.Add(1)
@ -1038,7 +1024,9 @@ 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)
}
s.debugLogf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, looksDisco)
if s.debug {
s.logf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, disco.LooksLikeDiscoWrapper(packetBytes))
}
}
func (c *sclient) sendPkt(dst *sclient, p pkt) error {
@ -1056,13 +1044,13 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
select {
case <-dst.done:
s.recordDrop(p.bs, c.key, dstKey, dropReasonGoneDisconnected)
dst.debugLogf("sendPkt attempt %d dropped, dst gone", attempt)
dst.debug("sendPkt attempt %d dropped, dst gone", attempt)
return nil
default:
}
select {
case sendQueue <- p:
dst.debugLogf("sendPkt attempt %d enqueued", attempt)
dst.debug("sendPkt attempt %d enqueued", attempt)
return nil
default:
}
@ -1078,7 +1066,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.debugLogf("sendPkt attempt %d dropped, queue full")
dst.debug("sendPkt attempt %d dropped, queue full")
return nil
}
@ -1316,7 +1304,8 @@ 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
debug bool // turn on for verbose logging
debugLogging bool
// Owned by run, not thread-safe.
br *bufio.Reader
@ -1604,7 +1593,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.debugLogf("sendPacket from %s: %v", srcKey.ShortString(), err)
c.debug("sendPacket from %s: %v", srcKey.ShortString(), err)
}()
c.setWriteDeadline()

View File

@ -9,18 +9,19 @@ import (
"net"
"time"
"tailscale.com/net/tcpinfo"
"golang.org/x/sys/unix"
)
func (c *sclient) statsLoop(ctx context.Context) error {
// Get the RTT initially to verify it's supported.
conn := c.tcpConn()
if conn == nil {
// If we can't get a TCP socket, then we can't send stats.
tcpConn := c.tcpConn()
if tcpConn == nil {
c.s.tcpRtt.Add("non-tcp", 1)
return nil
}
if _, err := tcpinfo.RTT(conn); err != nil {
c.logf("error fetching initial RTT: %v", err)
rawConn, err := tcpConn.SyscallConn()
if err != nil {
c.logf("error getting SyscallConn: %v", err)
c.s.tcpRtt.Add("error", 1)
return nil
}
@ -30,16 +31,23 @@ func (c *sclient) statsLoop(ctx context.Context) error {
ticker := time.NewTicker(statsInterval)
defer ticker.Stop()
var (
tcpInfo *unix.TCPInfo
sysErr error
)
statsLoop:
for {
select {
case <-ticker.C:
rtt, err := tcpinfo.RTT(conn)
if err != nil {
err = rawConn.Control(func(fd uintptr) {
tcpInfo, sysErr = unix.GetsockoptTCPInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_INFO)
})
if err != nil || sysErr != nil {
continue statsLoop
}
// TODO(andrew): more metrics?
rtt := time.Duration(tcpInfo.Rtt) * time.Microsecond
c.s.tcpRtt.Add(durationToLabel(rtt), 1)
case <-ctx.Done():

View File

@ -31,7 +31,6 @@ import (
"tailscale.com/derp"
"tailscale.com/envknob"
"tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/net/netns"
"tailscale.com/net/sockstats"
"tailscale.com/net/tlsdial"
@ -40,7 +39,6 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/util/cmpx"
)
// Client is a DERP-over-HTTP client.
@ -57,7 +55,6 @@ type Client struct {
privateKey key.NodePrivate
logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
dialer func(ctx context.Context, network, addr string) (net.Conn, error)
// Either url or getRegion is non-nil:
@ -91,13 +88,11 @@ func (c *Client) String() string {
// NewRegionClient returns a new DERP-over-HTTP client. It connects lazily.
// To trigger a connection, use Connect.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmon.Monitor, getRegion func() *tailcfg.DERPRegion) *Client {
func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, getRegion func() *tailcfg.DERPRegion) *Client {
ctx, cancel := context.WithCancel(context.Background())
c := &Client{
privateKey: privateKey,
logf: logf,
netMon: netMon,
getRegion: getRegion,
ctx: ctx,
cancelCtx: cancel,
@ -497,7 +492,7 @@ func (c *Client) dialURL(ctx context.Context) (net.Conn, error) {
return c.dialer(ctx, "tcp", net.JoinHostPort(host, urlPort(c.url)))
}
hostOrIP := host
dialer := netns.NewDialer(c.logf, c.netMon)
dialer := netns.NewDialer(c.logf)
if c.DNSCache != nil {
ip, _, _, err := c.DNSCache.LookupIP(ctx, host)
@ -592,7 +587,7 @@ func (c *Client) DialRegionTLS(ctx context.Context, reg *tailcfg.DERPRegion) (tl
}
func (c *Client) dialContext(ctx context.Context, proto, addr string) (net.Conn, error) {
return netns.NewDialer(c.logf, c.netMon).DialContext(ctx, proto, addr)
return netns.NewDialer(c.logf).DialContext(ctx, proto, addr)
}
// shouldDialProto reports whether an explicitly provided IPv4 or IPv6
@ -655,7 +650,10 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
// Start v4 dial
}
}
dst := cmpx.Or(dstPrimary, n.HostName)
dst := dstPrimary
if dst == "" {
dst = n.HostName
}
port := "443"
if n.DERPPort != 0 {
port = fmt.Sprint(n.DERPPort)

View File

@ -6,20 +6,22 @@ SA_NAME ?= tailscale
TS_KUBE_SECRET ?= tailscale
rbac:
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" role.yaml
@echo "---"
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" rolebinding.yaml
@echo "---"
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" sa.yaml
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" role.yaml | kubectl apply -f -
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" rolebinding.yaml | kubectl apply -f -
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" sa.yaml | kubectl apply -f -
sidecar:
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" sidecar.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g"
@kubectl delete -f sidecar.yaml --ignore-not-found --grace-period=0
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" sidecar.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | kubectl create -f-
userspace-sidecar:
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" userspace-sidecar.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g"
@kubectl delete -f userspace-sidecar.yaml --ignore-not-found --grace-period=0
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" userspace-sidecar.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | kubectl create -f-
proxy:
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" proxy.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{TS_DEST_IP}};$(TS_DEST_IP);g"
kubectl delete -f proxy.yaml --ignore-not-found --grace-period=0
sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" proxy.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{TS_DEST_IP}};$(TS_DEST_IP);g" | kubectl create -f-
subnet-router:
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" subnet.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{TS_ROUTES}};$(TS_ROUTES);g"
@kubectl delete -f subnet.yaml --ignore-not-found --grace-period=0
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" subnet.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{TS_ROUTES}};$(TS_ROUTES);g" | kubectl create -f-

View File

@ -26,7 +26,7 @@ There are quite a few ways of running Tailscale inside a Kubernetes Cluster, som
```bash
export SA_NAME=tailscale
export TS_KUBE_SECRET=tailscale-auth
make rbac | kubectl apply -f-
make rbac
```
### Sample Sidecar
@ -36,7 +36,7 @@ Running as a sidecar allows you to directly expose a Kubernetes pod over Tailsca
1. Create and login to the sample nginx pod with a Tailscale sidecar
```bash
make sidecar | kubectl apply -f-
make sidecar
# If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs nginx ts-sidecar
```
@ -60,7 +60,7 @@ You can also run the sidecar in userspace mode. The obvious benefit is reducing
1. Create and login to the sample nginx pod with a Tailscale sidecar
```bash
make userspace-sidecar | kubectl apply -f-
make userspace-sidecar
# If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs nginx ts-sidecar
```
@ -100,7 +100,7 @@ Running a Tailscale proxy allows you to provide inbound connectivity to a Kubern
1. Deploy the proxy pod
```bash
make proxy | kubectl apply -f-
make proxy
# If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs proxy
```
@ -133,7 +133,7 @@ the entire Kubernetes cluster network (assuming NetworkPolicies allow) over Tail
1. Deploy the subnet-router pod.
```bash
make subnet-router | kubectl apply -f-
make subnet-router
# If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs subnet-router
```

View File

@ -457,24 +457,13 @@ 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.
//
// 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.
// 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 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() {
@ -523,15 +512,9 @@ func getPlatformEnvFile() string {
return "/etc/tailscale/tailscaled-env.txt"
}
case "darwin":
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"
}
// 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.
}
return ""
}

View File

@ -115,4 +115,4 @@
in
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
}
# nix-direnv cache busting line: sha256-fgCrmtJs1svFz0Xn7iwLNrbBNlcO6V0yqGPMY0+V1VQ=
# nix-direnv cache busting line: sha256-LfHkNXQbg7/u6y882gtINuQtwinYakg3abKJTDrrADo=

429
go.mod
View File

@ -3,207 +3,200 @@ module tailscale.com
go 1.20
require (
filippo.io/mkcert v1.4.4
github.com/Microsoft/go-winio v0.6.1
filippo.io/mkcert v1.4.3
github.com/Microsoft/go-winio v0.6.0
github.com/akutz/memconn v0.1.0
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
github.com/andybalholm/brotli v1.0.5
github.com/andybalholm/brotli v1.0.3
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
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/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/coreos/go-iptables v0.6.0
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
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/creack/pty v1.1.17
github.com/dave/jennifer v1.4.1
github.com/dblohm7/wingoes v0.0.0-20221124203957-6ac47ab19aa5
github.com/dsnet/try v0.0.3
github.com/evanw/esbuild v0.14.53
github.com/frankban/quicktest v1.14.5
github.com/frankban/quicktest v1.14.0
github.com/fxamacker/cbor/v2 v2.4.0
github.com/go-json-experiment/json v0.0.0-20230321051131-ccbac49a6929
github.com/go-logr/zapr v1.2.4
github.com/go-json-experiment/json v0.0.0-20221017203807-c5ed296b8c92
github.com/go-logr/zapr v1.2.3
github.com/go-ole/go-ole v1.2.6
github.com/godbus/dbus/v5 v5.1.0
github.com/godbus/dbus/v5 v5.0.6
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.14.0
github.com/google/go-containerregistry v0.9.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.1.0
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3
github.com/iancoleman/strcase v0.2.0
github.com/illarion/gonotify v1.0.1
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/jsimonetti/rtnetlink v1.3.2
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.16.5
github.com/klauspost/compress v1.15.4
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.18
github.com/mdlayher/genetlink v1.3.2
github.com/mdlayher/netlink v1.7.2
github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-isatty v0.0.14
github.com/mdlayher/genetlink v1.2.0
github.com/mdlayher/netlink v1.7.1
github.com/mdlayher/sdnotify v1.0.0
github.com/miekg/dns v1.1.55
github.com/miekg/dns v1.1.43
github.com/mitchellh/go-ps v1.0.0
github.com/peterbourgon/ff/v3 v3.3.0
github.com/peterbourgon/ff/v3 v3.1.2
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.5
github.com/prometheus/client_golang v1.15.1
github.com/prometheus/common v0.42.0
github.com/pkg/sftp v1.13.4
github.com/prometheus/client_golang v1.14.0
github.com/prometheus/common v0.41.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-20221115211329-17a3db2c30d2
github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f
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.2.0
github.com/tc-hib/winres v0.1.6
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
github.com/u-root/u-root v0.11.0
github.com/vishvananda/netlink v1.2.1-beta.2
go.uber.org/zap v1.24.0
go4.org/mem v0.0.0-20220726221520-4f986261bf13
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
golang.org/x/mod v0.10.0
golang.org/x/net v0.10.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sync v0.2.0
golang.org/x/sys v0.8.1-0.20230609144347-5059a07aa46a
golang.org/x/term v0.8.0
golang.org/x/time v0.3.0
golang.org/x/tools v0.9.1
github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
go.uber.org/zap v1.21.0
go4.org/mem v0.0.0-20210711025021-927187094b94
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
golang.org/x/crypto v0.6.0
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
golang.org/x/mod v0.7.0
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.5.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.5.1-0.20230222185716-a3b23cc77e89
golang.org/x/term v0.5.0
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
golang.org/x/tools v0.5.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard/windows v0.5.3
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f
honnef.co/go/tools v0.4.3
gvisor.dev/gvisor v0.0.0-20230328175328-162ed5ef888d
honnef.co/go/tools v0.4.2
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
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
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
nhooyr.io/websocket v1.8.7
sigs.k8s.io/controller-runtime v0.15.0
sigs.k8s.io/controller-runtime v0.13.1
sigs.k8s.io/yaml v1.3.0
software.sslmate.com/src/go-pkcs12 v0.2.0
)
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/Abirdcfly/dupword v0.0.11 // indirect
github.com/Antonboom/errname v0.1.9 // indirect
github.com/Antonboom/nilnil v0.1.4 // indirect
4d63.com/gochecknoglobals v0.1.0 // indirect
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
github.com/Antonboom/errname v0.1.5 // indirect
github.com/Antonboom/nilnil v0.1.0 // indirect
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.2.1 // indirect
github.com/Masterminds/semver/v3 v3.1.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-20230426101702-58e86b294756 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/OpenPeeDeeP/depguard v1.0.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/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.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/ashanbrown/forbidigo v1.2.0 // indirect
github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.6.4 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.0 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v3 v3.4.0 // indirect
github.com/breml/bidichk v0.2.4 // indirect
github.com/breml/errchkjson v0.3.1 // indirect
github.com/butuzov/ireturn v0.2.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/blizzy78/varnamelen v0.5.0 // indirect
github.com/bombsimon/wsl/v3 v3.3.0 // indirect
github.com/breml/bidichk v0.2.1 // indirect
github.com/butuzov/ireturn v0.1.1 // indirect
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
github.com/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.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/charithe/durationcheck v0.0.9 // indirect
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
github.com/daixiang0/gci v0.2.9 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingaikin/go-header v0.4.3 // indirect
github.com/docker/cli v23.0.5+incompatible // indirect
github.com/denis-tingajkin/go-header v0.4.2 // indirect
github.com/docker/cli v20.10.16+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // 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/docker/docker v20.10.16+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/esimonov/ifshort v1.0.3 // indirect
github.com/ettle/strcase v0.1.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/go-critic/go-critic v0.8.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fzipp/gocyclo v0.3.1 // indirect
github.com/gliderlabs/ssh v0.3.3 // indirect
github.com/go-critic/go-critic v0.6.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect
github.com/go-git/go-git/v5 v5.6.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.1.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-git/go-git/v5 v5.4.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-toolsmith/astcast v1.0.0 // indirect
github.com/go-toolsmith/astcopy v1.0.0 // indirect
github.com/go-toolsmith/astequal v1.0.1 // indirect
github.com/go-toolsmith/astfmt v1.0.0 // indirect
github.com/go-toolsmith/astp v1.0.0 // indirect
github.com/go-toolsmith/strparse v1.0.0 // indirect
github.com/go-toolsmith/typep v1.0.2 // indirect
github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 // indirect
github.com/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.3 // indirect
github.com/golang/protobuf v1.5.2 // 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
github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 // indirect
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a // indirect
github.com/golangci/golangci-lint v1.43.0 // indirect
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
github.com/golangci/misspell v0.4.0 // indirect
github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect
github.com/golangci/misspell v0.3.5 // indirect
github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2 // indirect
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // 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/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/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect
github.com/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
github.com/goreleaser/chglog v0.4.2 // indirect
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 // indirect
github.com/goreleaser/chglog v0.1.2 // indirect
github.com/goreleaser/fileglob v0.3.1 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
@ -211,12 +204,10 @@ require (
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.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
@ -224,134 +215,116 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/julz/importas v0.1.0 // indirect
github.com/junk1tm/musttag v0.5.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kisielk/errcheck v1.6.3 // indirect
github.com/julz/importas v0.0.0-20210922140945-27e0a5d4dee2 // indirect
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/kisielk/errcheck v1.6.0 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.6 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/ldez/gomoddirectives v0.2.3 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.1 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/kulti/thelper v0.4.0 // indirect
github.com/kunwardeep/paralleltest v1.0.3 // indirect
github.com/kyoh86/exportloopref v0.1.8 // indirect
github.com/ldez/gomoddirectives v0.2.2 // indirect
github.com/ldez/tagliatelle v0.2.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/maratori/testpackage v1.0.1 // indirect
github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mgechev/revive v1.3.1 // indirect
github.com/mdlayher/socket v0.4.0 // indirect
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
github.com/mgechev/revive v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/moricho/tparallel v0.3.1 // indirect
github.com/moricho/tparallel v0.2.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nishanths/exhaustive v0.10.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.11.2 // indirect
github.com/nishanths/exhaustive v0.7.11 // indirect
github.com/nishanths/predeclared v0.2.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/gomega v1.20.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.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/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polyfloyd/go-errorlint v1.4.1 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/polyfloyd/go-errorlint v0.0.0-20211125173453-6d6d39c5bb8b // indirect
github.com/prometheus/client_model v0.3.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/go-ruleguard v0.3.13 // 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.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/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/ryancurrah/gomodguard v1.2.3 // indirect
github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect
github.com/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.2.0 // indirect
github.com/securego/gosec/v2 v2.15.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/sassoftware/go-rpmutils v0.1.0 // indirect
github.com/securego/gosec/v2 v2.9.3 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/nosnakecase v1.7.0 // indirect
github.com/sivchari/tenv v1.7.1 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
github.com/sonatard/noctx v0.0.2 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/sivchari/tenv v1.4.7 // indirect
github.com/sonatard/noctx v0.0.1 // indirect
github.com/sourcegraph/go-diff v0.6.1 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.15.0 // indirect
github.com/spf13/viper v1.9.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.2 // indirect
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/sylvia7788/contextcheck v1.0.4 // indirect
github.com/tdakkota/asciicheck v0.1.1 // indirect
github.com/tetafro/godot v1.4.11 // indirect
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
github.com/timonwong/loggercheck v0.9.4 // indirect
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect
github.com/tomarrell/wrapcheck/v2 v2.4.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.4.0 // indirect
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/ultraware/funlen v0.0.3 // indirect
github.com/ultraware/whitespace v0.0.5 // indirect
github.com/uudashr/gocognit v1.0.6 // indirect
github.com/ultraware/whitespace v0.0.4 // indirect
github.com/uudashr/gocognit v1.0.5 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.2.0 // indirect
gitlab.com/bosi/decorder v0.2.3 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect
golang.org/x/image v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/yeya24/promlinter v0.1.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/image v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
k8s.io/apiextensions-apiserver v0.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
k8s.io/apiextensions-apiserver v0.25.0 // indirect
k8s.io/component-base v0.25.0 // indirect
k8s.io/klog/v2 v2.70.1 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
mvdan.cc/gofumpt v0.2.0 // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
mvdan.cc/unparam v0.0.0-20211002134041-24922b6997ca // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)

View File

@ -1 +1 @@
sha256-fgCrmtJs1svFz0Xn7iwLNrbBNlcO6V0yqGPMY0+V1VQ=
sha256-LfHkNXQbg7/u6y882gtINuQtwinYakg3abKJTDrrADo=

1345
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
tailscale.go1.21
tailscale.go1.20

View File

@ -1 +1 @@
492f6d9d792fa6e4caa388e4d7bab46b48d07ad5
ddff070c02790cb571006e820e58cce9627569cf

View File

@ -7,10 +7,8 @@ package hostinfo
import (
"bufio"
"bytes"
"io"
"os"
"os/exec"
"runtime"
"runtime/debug"
"strings"
@ -283,7 +281,7 @@ func inContainer() opt.Bool {
return nil
})
lineread.File("/proc/mounts", func(line []byte) error {
if mem.Contains(mem.B(line), mem.S("lxcfs /proc/cpuinfo fuse.lxcfs")) {
if mem.Contains(mem.B(line), mem.S("fuse.lxcfs")) {
ret.Set(true)
return io.EOF
}
@ -407,7 +405,7 @@ func DisabledEtcAptSource() bool {
return false
}
mod := fi.ModTime()
if c, ok := etcAptSrcCache.Load().(etcAptSrcResult); ok && c.mod.Equal(mod) {
if c, ok := etcAptSrcCache.Load().(etcAptSrcResult); ok && c.mod == mod {
return c.disabled
}
f, err := os.Open(path)
@ -436,12 +434,3 @@ func etcAptSourceFileIsDisabled(r io.Reader) bool {
}
return disabled
}
// IsSELinuxEnforcing reports whether SELinux is in "Enforcing" mode.
func IsSELinuxEnforcing() bool {
if runtime.GOOS != "linux" {
return false
}
out, _ := exec.Command("getenforce").Output()
return string(bytes.TrimSpace(out)) == "Enforcing"
}

View File

@ -95,8 +95,6 @@ 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))
@ -155,8 +153,6 @@ func linuxVersionMeta() (meta versionMeta) {
meta.DistroVersion = m["productversion"]
case distro.OpenWrt:
meta.DistroVersion = m["DISTRIB_RELEASE"]
case distro.Unraid:
meta.DistroVersion = m["version"]
}
return
}

View File

@ -6,7 +6,6 @@
package tooldeps
import (
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "github.com/tailscale/depaware/depaware"
_ "golang.org/x/tools/cmd/goimports"
)

View File

@ -103,7 +103,6 @@ func (src *TCPPortHandler) Clone() *TCPPortHandler {
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct {
HTTPS bool
HTTP bool
TCPForward string
TerminateTLS string
}{})

View File

@ -228,14 +228,12 @@ func (v *TCPPortHandlerView) UnmarshalJSON(b []byte) error {
}
func (v TCPPortHandlerView) HTTPS() bool { return v.ж.HTTPS }
func (v TCPPortHandlerView) HTTP() bool { return v.ж.HTTP }
func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward }
func (v TCPPortHandlerView) TerminateTLS() string { return v.ж.TerminateTLS }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct {
HTTPS bool
HTTP bool
TCPForward string
TerminateTLS string
}{})

View File

@ -17,7 +17,6 @@ import (
"time"
"tailscale.com/envknob"
"tailscale.com/net/sockstats"
"tailscale.com/tailcfg"
"tailscale.com/util/clientmetric"
"tailscale.com/util/goroutines"
@ -49,7 +48,7 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
}
case "/debug/goroutines":
w.Header().Set("Content-Type", "text/plain")
w.Write(goroutines.ScrubbedGoroutineDump(true))
w.Write(goroutines.ScrubbedGoroutineDump())
case "/debug/prefs":
writeJSON(b.Prefs())
case "/debug/metrics":
@ -95,8 +94,7 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
return
}
b.sockstatLogger.Flush()
fmt.Fprintf(w, "logid: %s\n", b.sockstatLogger.LogID())
fmt.Fprintf(w, "debug info: %v\n", sockstats.DebugInfo())
fmt.Fprintln(w, b.sockstatLogger.LogID())
default:
http.Error(w, "unknown c2n path", http.StatusBadRequest)
}

View File

@ -31,7 +31,6 @@ import (
"time"
"golang.org/x/crypto/acme"
"golang.org/x/exp/slices"
"tailscale.com/atomicfile"
"tailscale.com/envknob"
"tailscale.com/hostinfo"
@ -101,13 +100,11 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
}
if pair, err := getCertPEMCached(cs, domain, now); err == nil {
shouldRenew, err := shouldStartDomainRenewal(domain, now, pair)
if err != nil {
logf("error checking for certificate renewal: %v", err)
} else if shouldRenew {
future := now.AddDate(0, 0, 14)
if b.shouldStartDomainRenewal(cs, domain, future) {
logf("starting async renewal")
// Start renewal in the background.
go b.getCertPEM(context.Background(), cs, logf, traceACME, domain, now)
go b.getCertPEM(context.Background(), cs, logf, traceACME, domain, future)
}
return pair, nil
}
@ -120,41 +117,18 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
return pair, nil
}
func shouldStartDomainRenewal(domain string, now time.Time, pair *TLSCertKeyPair) (bool, error) {
func (b *LocalBackend) shouldStartDomainRenewal(cs certStore, domain string, future time.Time) bool {
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, nil
return false
}
lastRenewCheck[domain] = now
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
_, err := getCertPEMCached(cs, domain, future)
return errors.Is(err, errCertExpired)
}
// certStore provides a way to perist and retrieve TLS certificates.
@ -387,16 +361,17 @@ 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
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 {
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 {
logf("starting SetDNS call...")
err = b.SetDNS(ctx, key, rec)
if err != nil {

View File

@ -6,19 +6,12 @@
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"
)
@ -107,94 +100,3 @@ 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)
}
}
})
}
}

View File

@ -16,7 +16,6 @@ import (
"tailscale.com/types/dnstype"
"tailscale.com/types/netmap"
"tailscale.com/util/cloudenv"
"tailscale.com/util/cmpx"
"tailscale.com/util/dnsname"
)
@ -309,7 +308,10 @@ func TestDNSConfigForNetmap(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
verOS := cmpx.Or(tt.os, "linux")
verOS := tt.os
if verOS == "" {
verOS = "linux"
}
var log tstest.MemLogger
got := dnsConfigForNetmap(tt.nm, tt.prefs.View(), log.Logf, verOS)
if !reflect.DeepEqual(got, tt.want) {

View File

@ -158,7 +158,7 @@ func (em *expiryManager) nextPeerExpiry(nm *netmap.NetworkMap, localNow time.Tim
// nextExpiry being zero is a sentinel that we haven't yet set
// an expiry; otherwise, only update if this node's expiry is
// sooner than the currently-stored one (since we want the
// soonest-occurring expiry time).
// soonest-occuring expiry time).
if nextExpiry.IsZero() || peer.KeyExpiry.Before(nextExpiry) {
nextExpiry = peer.KeyExpiry
}

View File

@ -243,7 +243,7 @@ func TestNextPeerExpiry(t *testing.T) {
em := newExpiryManager(t.Logf)
em.timeNow = func() time.Time { return now }
got := em.nextPeerExpiry(tt.netmap, now)
if !got.Equal(tt.want) {
if got != tt.want {
t.Errorf("got %q, want %q", got.Format(time.RFC3339), tt.want.Format(time.RFC3339))
} else if !got.IsZero() && got.Before(now) {
t.Errorf("unexpectedly got expiry %q before now %q", got.Format(time.RFC3339), now.Format(time.RFC3339))
@ -269,7 +269,7 @@ func TestNextPeerExpiry(t *testing.T) {
}
got := em.nextPeerExpiry(nm, now)
want := now.Add(30 * time.Second)
if !got.Equal(want) {
if got != want {
t.Errorf("got %q, want %q", got.Format(time.RFC3339), want.Format(time.RFC3339))
}
})

View File

@ -4,6 +4,7 @@
package ipnlocal
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
@ -17,6 +18,7 @@ import (
"net/netip"
"net/url"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
@ -30,7 +32,6 @@ import (
"go4.org/mem"
"go4.org/netipx"
"golang.org/x/exp/slices"
"gvisor.dev/gvisor/pkg/tcpip"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/control/controlclient"
"tailscale.com/doctor"
@ -59,7 +60,6 @@ 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"
@ -70,7 +70,6 @@ import (
"tailscale.com/types/preftype"
"tailscale.com/types/ptr"
"tailscale.com/types/views"
"tailscale.com/util/cmpx"
"tailscale.com/util/deephash"
"tailscale.com/util/dnsname"
"tailscale.com/util/mak"
@ -138,13 +137,12 @@ 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
sys *tsd.System
e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys
e wgengine.Engine
pm *profileManager
store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys
dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys
store ipn.StateStore
dialer *tsdial.Dialer // non-nil
backendLogID logid.PublicID
unregisterNetMon func()
unregisterLinkMon func()
unregisterHealthWatch func()
portpoll *portlist.Poller // may be nil
portpollOnce sync.Once // guards starting readPoller
@ -269,10 +267,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, sys *tsd.System, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
e := sys.Engine.Get()
store := sys.StateStore.Get()
dialer := sys.Dialer.Get()
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")
}
pm, err := newProfileManager(store, logf)
if err != nil {
@ -292,7 +290,10 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
osshare.SetFileSharingEnabled(false, logf)
ctx, cancel := context.WithCancel(context.Background())
portpoll := new(portlist.Poller)
portpoll, err := portlist.NewPoller()
if err != nil {
logf("skipping portlist: %s", err)
}
b := &LocalBackend{
ctx: ctx,
@ -300,11 +301,10 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
logf: logf,
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
sys: sys,
e: e,
dialer: dialer,
store: store,
pm: pm,
store: store,
dialer: dialer,
backendLogID: logID,
state: ipn.NoState,
portpoll: portpoll,
@ -313,8 +313,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
loginFlags: loginFlags,
}
netMon := sys.NetMon.Get()
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon)
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID)
if err != nil {
log.Printf("error setting up sockstat logger: %v", err)
}
@ -331,17 +330,23 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
b.statusChanged = sync.NewCond(&b.statusLock)
b.e.SetStatusCallback(b.setWgengineStatus)
b.prevIfState = netMon.InterfaceState()
linkMon := e.GetLinkMonitor()
b.prevIfState = linkMon.InterfaceState()
// Call our linkChange code once with the current state, and
// then also whenever it changes:
b.linkChange(false, netMon.InterfaceState())
b.unregisterNetMon = netMon.RegisterChangeCallback(b.linkChange)
b.linkChange(false, linkMon.InterfaceState())
b.unregisterLinkMon = linkMon.RegisterChangeCallback(b.linkChange)
b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange)
if tunWrap, ok := b.sys.Tun.GetOK(); ok {
tunWrap.PeerAPIPort = b.GetPeerAPIPort
} else {
wiredPeerAPIPort := false
if ig, ok := e.(wgengine.InternalsGetter); ok {
if tunWrap, _, _, ok := ig.GetInternals(); ok {
tunWrap.PeerAPIPort = b.GetPeerAPIPort
wiredPeerAPIPort = true
}
}
if !wiredPeerAPIPort {
b.logf("[unexpected] failed to wire up PeerAPI port for engine %T", e)
}
@ -433,7 +438,7 @@ func (b *LocalBackend) SetComponentDebugLogging(component string, until time.Tim
// unchanged when the timer actually fires.
b.mu.Lock()
defer b.mu.Unlock()
if ls := b.componentLogUntil[component]; ls.until.Equal(until) {
if ls := b.componentLogUntil[component]; ls.until == until {
setEnabled(false)
b.logf("debugging logging for component %q disabled (by timer)", component)
}
@ -459,7 +464,6 @@ 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
}
@ -495,7 +499,7 @@ func (b *LocalBackend) maybePauseControlClientLocked() {
b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || !networkUp)
}
// linkChange is our network monitor callback, called whenever the network changes.
// linkChange is our link monitor callback, called whenever the network changes.
// major is whether ifst is different than earlier.
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
b.mu.Lock()
@ -572,7 +576,7 @@ func (b *LocalBackend) Shutdown() {
b.sockstatLogger.Shutdown()
}
b.unregisterNetMon()
b.unregisterLinkMon()
b.unregisterHealthWatch()
if cc != nil {
cc.Shutdown()
@ -640,7 +644,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
defer b.mu.Unlock()
sb.MutateStatus(func(s *ipnstate.Status) {
s.Version = version.Long()
s.TUN = !b.sys.IsNetstack()
s.TUN = !wgengine.IsNetstack(b.e)
s.BackendState = b.state.String()
s.AuthURL = b.authURLSticky
if err := health.OverallError(); err != nil {
@ -742,6 +746,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
HostName: p.Hostinfo.Hostname(),
DNSName: p.Name,
OS: p.Hostinfo.OS(),
KeepAlive: p.KeepAlive,
LastSeen: lastSeen,
Online: p.Online != nil && *p.Online,
ShareeNode: p.Hostinfo.ShareeNode(),
@ -1072,7 +1077,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
b.e.SetDERPMap(st.NetMap.DERPMap)
// Update our cached DERP map
dnsfallback.UpdateCache(st.NetMap.DERPMap, b.logf)
dnsfallback.UpdateCache(st.NetMap.DERPMap)
b.send(ipn.Notify{NetMap: st.NetMap})
}
@ -1310,8 +1315,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
hostinfo := hostinfo.New()
hostinfo.BackendLogID = b.backendLogID.String()
hostinfo.FrontendLogID = opts.FrontendLogID
hostinfo.Userspace.Set(b.sys.IsNetstack())
hostinfo.UserspaceRouter.Set(b.sys.IsNetstackRouter())
hostinfo.Userspace.Set(wgengine.IsNetstack(b.e))
hostinfo.UserspaceRouter.Set(wgengine.IsNetstackRouter(b.e))
if b.cc != nil {
// TODO(apenwarr): avoid the need to reinit controlclient.
@ -1373,6 +1378,7 @@ 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
@ -1395,7 +1401,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
var err error
isNetstack := b.sys.IsNetstackRouter()
isNetstack := wgengine.IsNetstackRouter(b.e)
debugFlags := controlDebugFlags
if isNetstack {
debugFlags = append([]string{"netstack"}, debugFlags...)
@ -1417,7 +1423,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
HTTPTestClient: httpTestClient,
DiscoPublicKey: discoPublic,
DebugFlags: debugFlags,
NetMon: b.sys.NetMon.Get(),
LinkMonitor: b.e.GetLinkMonitor(),
Pinger: b,
PopBrowserURL: b.tellClientToBrowseToURL,
OnClientVersion: b.onClientVersion,
@ -1807,30 +1813,11 @@ 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() {
isFirst := true
ticker := time.NewTicker(portlist.PollInterval())
defer ticker.Stop()
initChan := make(chan struct{})
close(initChan)
n := 0
for {
select {
case <-ticker.C:
case <-b.ctx.Done():
ports, ok := <-b.portpoll.Updates()
if !ok {
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 {
@ -1854,8 +1841,8 @@ func (b *LocalBackend) readPoller() {
b.doSetHostinfoFilterServices(hi)
if isFirst {
isFirst = false
n++
if n == 1 {
close(b.gotPortPollRes)
}
}
@ -2492,7 +2479,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
}
@ -2580,7 +2567,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
if distro.Get() == distro.QNAP && !envknob.UseWIPCode() {
return errors.New("The Tailscale SSH server does not run on QNAP.")
}
b.updateSELinuxHealthWarning()
checkSELinux()
// otherwise okay
case "darwin":
// okay only in tailscaled mode for now.
@ -2826,14 +2813,14 @@ func (b *LocalBackend) GetPeerAPIPort(ip netip.Addr) (port uint16, ok bool) {
return 0, false
}
// handlePeerAPIConn serves an already-accepted connection c.
// ServePeerAPIConnection serves an already-accepted connection c.
//
// The remote parameter is the remote address.
// The local parameter is the local address (either a Tailscale IPv4
// or IPv6 IP and the peerapi port for that address).
//
// The connection will be closed by handlePeerAPIConn.
func (b *LocalBackend) handlePeerAPIConn(remote, local netip.AddrPort, c net.Conn) {
// The connection will be closed by ServePeerAPIConnection.
func (b *LocalBackend) ServePeerAPIConnection(remote, local netip.AddrPort, c net.Conn) {
b.mu.Lock()
defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners {
@ -2847,48 +2834,6 @@ func (b *LocalBackend) handlePeerAPIConn(remote, local netip.AddrPort, c net.Con
return
}
func (b *LocalBackend) isLocalIP(ip netip.Addr) bool {
nm := b.NetMap()
return nm != nil && slices.Contains(nm.Addresses, netip.PrefixFrom(ip, ip.BitLen()))
}
var (
magicDNSIP = tsaddr.TailscaleServiceIP()
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
)
// TCPHandlerForDst returns a TCP handler for connections to dst, or nil if
// no handler is needed. It also returns a list of TCP socket options to
// apply to the socket before calling the handler.
func (b *LocalBackend) TCPHandlerForDst(src, dst netip.AddrPort) (handler func(c net.Conn) error, opts []tcpip.SettableSocketOption) {
if dst.Port() == 80 && (dst.Addr() == magicDNSIP || dst.Addr() == magicDNSIPv6) {
return b.HandleQuad100Port80Conn, opts
}
if !b.isLocalIP(dst.Addr()) {
return nil, nil
}
if dst.Port() == 22 && b.ShouldRunSSH() {
// Use a higher keepalive idle time for SSH connections, as they are
// typically long lived and idle connections are more likely to be
// intentional. Ideally we would turn this off entirely, but we can't
// tell the difference between a long lived connection that is idle
// vs a connection that is dead because the peer has gone away.
// We pick 72h as that is typically sufficient for a long weekend.
opts = append(opts, ptr.To(tcpip.KeepaliveIdleOption(72*time.Hour)))
return b.handleSSHConn, opts
}
if port, ok := b.GetPeerAPIPort(dst.Addr()); ok && dst.Port() == port {
return func(c net.Conn) error {
b.handlePeerAPIConn(src, dst, c)
return nil
}, opts
}
if handler := b.tcpHandlerForServe(dst.Port(), src); handler != nil {
return handler, opts
}
return nil, nil
}
func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) {
for _, pln := range b.peerAPIListeners {
proto := tailcfg.PeerAPI4
@ -3372,12 +3317,14 @@ func (b *LocalBackend) initPeerAPIListener() {
directFileMode: b.directFileRoot != "",
directFileDoFinalRename: b.directFileDoFinalRename,
}
if dm, ok := b.sys.DNSManager.GetOK(); ok {
ps.resolver = dm.Resolver()
if re, ok := b.e.(wgengine.ResolvingEngine); ok {
if r, ok := re.GetResolver(); ok {
ps.resolver = r
}
}
b.peerAPIServer = ps
isNetstack := b.sys.IsNetstack()
isNetstack := wgengine.IsNetstack(b.e)
for i, a := range b.netMap.Addresses {
var ln net.Listener
var err error
@ -3683,19 +3630,6 @@ 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 {
@ -3973,7 +3907,10 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
b.dialer.SetNetMap(nm)
var login string
if nm != nil {
login = cmpx.Or(nm.UserProfiles[nm.User].LoginName, "<missing-profile>")
login = nm.UserProfiles[nm.User].LoginName
if login == "" {
login = "<missing-profile>"
}
}
b.netMap = nm
if login != b.activeLogin {
@ -4103,7 +4040,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
b.setServeProxyHandlersLocked()
// don't listen on netmap addresses if we're in userspace mode
if !b.sys.IsNetstack() {
if !wgengine.IsNetstack(b.e) {
b.updateServeTCPPortNetMapAddrListenersLocked(servePorts)
}
}
@ -4128,10 +4065,6 @@ func (b *LocalBackend) setServeProxyHandlersLocked() {
b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
backend := h.Proxy()
if backend == "" {
// Only create proxy handlers for servers with a proxy backend.
return true
}
mak.Set(&backends, backend, true)
if _, ok := b.serveProxyHandlers.Load(backend); ok {
return true
@ -4458,7 +4391,7 @@ func nodeIP(n *tailcfg.Node, pred func(netip.Addr) bool) netip.Addr {
}
func (b *LocalBackend) CheckIPForwarding() error {
if b.sys.IsNetstackRouter() {
if wgengine.IsNetstackRouter(b.e) {
return nil
}
@ -4604,9 +4537,13 @@ func (b *LocalBackend) DebugReSTUN() error {
}
func (b *LocalBackend) magicConn() (*magicsock.Conn, error) {
mc, ok := b.sys.MagicSock.GetOK()
ig, ok := b.e.(wgengine.InternalsGetter)
if !ok {
return nil, errors.New("failed to get magicsock from sys")
return nil, errors.New("engine isn't InternalsGetter")
}
_, mc, _, ok := ig.GetInternals()
if !ok {
return nil, errors.New("failed to get internals")
}
return mc, nil
}
@ -4706,29 +4643,33 @@ func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) {
var warnSSHSELinux = health.NewWarnable()
func (b *LocalBackend) updateSELinuxHealthWarning() {
if hostinfo.IsSELinuxEnforcing() {
func checkSELinux() {
if runtime.GOOS != "linux" {
return
}
out, _ := exec.Command("getenforce").Output()
if string(bytes.TrimSpace(out)) == "Enforcing" {
warnSSHSELinux.Set(errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux"))
} else {
warnSSHSELinux.Set(nil)
}
}
func (b *LocalBackend) handleSSHConn(c net.Conn) (err error) {
func (b *LocalBackend) HandleSSHConn(c net.Conn) (err error) {
s, err := b.sshServerOrInit()
if err != nil {
return err
}
b.updateSELinuxHealthWarning()
checkSELinux()
return s.HandleSSHConn(c)
}
// HandleQuad100Port80Conn serves http://100.100.100.100/ on port 80 (and
// the equivalent tsaddr.TailscaleServiceIPv6 address).
func (b *LocalBackend) HandleQuad100Port80Conn(c net.Conn) error {
func (b *LocalBackend) HandleQuad100Port80Conn(c net.Conn) {
var s http.Server
s.Handler = http.HandlerFunc(b.handleQuad100Port80Conn)
return s.Serve(netutil.NewOneConnListener(c, nil))
s.Serve(netutil.NewOneConnListener(c, nil))
}
func validQuad100Host(h string) bool {

View File

@ -20,7 +20,6 @@ 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"
@ -502,16 +501,13 @@ 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)
sys.Set(store)
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
t.Cleanup(eng.Close)
sys.Set(eng)
lb, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
lb, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, eng, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}
@ -769,16 +765,13 @@ func TestPacketFilterPermitsUnlockedNodes(t *testing.T) {
func TestStatusWithoutPeers(t *testing.T) {
logf := tstest.WhileTestRunningLogger(t)
store := new(testStateStorage)
sys := new(tsd.System)
sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
sys.Set(e)
t.Cleanup(e.Close)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
b, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, e, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}

View File

@ -12,7 +12,6 @@ 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"
@ -48,17 +47,14 @@ 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)
sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
if err != nil {
t.Fatal(err)
}
t.Cleanup(e.Close)
sys.Set(e)
lb, err := NewLocalBackend(logf, idA, sys, 0)
lb, err := NewLocalBackend(logf, idA, store, nil, e, 0)
if err != nil {
t.Fatal(err)
}

View File

@ -158,9 +158,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
return nil
}
if b.tka != nil || nm.TKAEnabled {
b.logf("tkaSyncIfNeeded: enabled=%v, head=%v", nm.TKAEnabled, nm.TKAHead)
}
b.logf("tkaSyncIfNeeded: enabled=%v, head=%v", nm.TKAEnabled, nm.TKAHead)
ourNodeKey := prefs.Persist().PublicNodeKey()
@ -199,7 +197,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
health.SetTKAHealth(nil)
}
} else {
return fmt.Errorf("[bug] unreachable invariant of wantEnabled w/ isEnabled")
return fmt.Errorf("[bug] unreachable invariant of wantEnabled /w isEnabled")
}
}
@ -451,8 +449,6 @@ 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,
@ -461,7 +457,6 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
NodeKeySigned: selfAuthorized,
TrustedKeys: outKeys,
FilteredPeers: filtered,
StateID: stateID1,
}
}
@ -889,18 +884,6 @@ func (b *LocalBackend) NetworkLockWrapPreauthKey(preauthKey string, tkaKey key.N
return fmt.Sprintf("%s--TL%s-%s", preauthKey, tkaSuffixEncoder.EncodeToString(sig.Serialize()), tkaSuffixEncoder.EncodeToString(priv)), nil
}
// NetworkLockVerifySigningDeeplink asks the authority to verify the given deeplink
// URL. See the comment for ValidateDeeplink for details.
func (b *LocalBackend) NetworkLockVerifySigningDeeplink(url string) tka.DeeplinkValidationResult {
b.mu.Lock()
defer b.mu.Unlock()
if b.tka == nil {
return tka.DeeplinkValidationResult{IsValid: false, Error: errNetworkLockNotActive.Error()}
}
return b.tka.authority.ValidateDeeplink(url)
}
func signNodeKey(nodeInfo tailcfg.TKASignInfo, signer key.NLPrivate) (*tka.NodeKeySignature, error) {
p, err := nodeInfo.NodePublic.MarshalBinary()
if err != nil {

View File

@ -875,7 +875,7 @@ func TestTKAForceDisable(t *testing.T) {
}
if b.tka != nil {
t.Fatal("tka was re-initialized")
t.Fatal("tka was re-initalized")
}
}

View File

@ -49,7 +49,7 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/util/clientmetric"
"tailscale.com/util/multierr"
"tailscale.com/version/distro"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
)
@ -468,7 +468,7 @@ func (s *peerAPIServer) listen(ip netip.Addr, ifState *interfaces.State) (ln net
}
}
if s.b.sys.IsNetstack() {
if wgengine.IsNetstack(s.b.e) {
ipStr = ""
}
@ -605,16 +605,6 @@ func (h *peerAPIHandler) logf(format string, a ...any) {
h.ps.b.logf("peerapi: "+format, a...)
}
// isAddressValid reports whether addr is a valid destination address for this
// node originating from the peer.
func (h *peerAPIHandler) isAddressValid(addr netip.Addr) bool {
if h.peerNode.SelfNodeV4MasqAddrForThisPeer != nil {
return *h.peerNode.SelfNodeV4MasqAddrForThisPeer == addr
}
pfx := netip.PrefixFrom(addr, addr.BitLen())
return slices.Contains(h.selfNode.Addresses, pfx)
}
func (h *peerAPIHandler) validateHost(r *http.Request) error {
if r.Host == "peer" {
return nil
@ -623,8 +613,9 @@ func (h *peerAPIHandler) validateHost(r *http.Request) error {
if err != nil {
return err
}
if !h.isAddressValid(ap.Addr()) {
return fmt.Errorf("%v not found in self addresses", ap.Addr())
hostIPPfx := netip.PrefixFrom(ap.Addr(), ap.Addr().BitLen())
if !slices.Contains(h.selfNode.Addresses, hostIPPfx) {
return fmt.Errorf("%v not found in self addresses", hostIPPfx)
}
return nil
}
@ -780,7 +771,7 @@ func (h *peerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Reque
return
}
getConnOrReset := func() (net.Conn, bool) {
getConn := func() (net.Conn, bool) {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
h.logf("ingress: failed hijacking conn")
@ -798,7 +789,7 @@ func (h *peerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Reque
http.Error(w, "denied", http.StatusForbidden)
}
h.ps.b.HandleIngressTCPConn(h.peerNode, target, srcAddr, getConnOrReset, sendRST)
h.ps.b.HandleIngressTCPConn(h.peerNode, target, srcAddr, getConn, sendRST)
}
func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Request) {
@ -956,12 +947,6 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req
fmt.Fprintln(w, "</tfoot>")
fmt.Fprintln(w, "</table>")
fmt.Fprintln(w, "<h2>Debug Info</h2>")
fmt.Fprintln(w, "<pre>")
fmt.Fprintln(w, html.EscapeString(sockstats.DebugInfo()))
fmt.Fprintln(w, "</pre>")
}
type incomingFile struct {
@ -1090,10 +1075,6 @@ 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 {
@ -1238,9 +1219,12 @@ func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Req
http.Error(w, "denied; no debug access", http.StatusForbidden)
return
}
if mc, ok := h.ps.b.sys.MagicSock.GetOK(); ok {
mc.ServeHTTPDebug(w, r)
return
eng := h.ps.b.e
if ig, ok := eng.(wgengine.InternalsGetter); ok {
if _, mc, _, ok := ig.GetInternals(); ok {
mc.ServeHTTPDebug(w, r)
return
}
}
http.Error(w, "miswired", 500)
}

View File

@ -22,10 +22,6 @@ 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 {
@ -44,13 +40,6 @@ 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 {
@ -75,11 +64,9 @@ 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" {
pm.dlogf("SetCurrentUserID: windows: migrating from legacy preferences")
if err := pm.migrateFromLegacyPrefs(); err != nil && !errors.Is(err, errAlreadyMigrated) {
if err := pm.migrateFromLegacyPrefs(); err != nil {
return err
}
} else {
@ -92,7 +79,6 @@ 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
}
@ -558,16 +544,8 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, goos stri
if err := pm.setPrefsLocked(prefs); err != nil {
return nil, err
}
// 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" {
} else if len(knownProfiles) == 0 && 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
}
@ -584,15 +562,13 @@ func (pm *profileManager) migrateFromLegacyPrefs() error {
sentinel, prefs, err := pm.loadLegacyPrefs()
if err != nil {
metricMigrationError.Add(1)
return fmt.Errorf("load legacy prefs: %w", err)
return 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
}

View File

@ -5,7 +5,6 @@ package ipnlocal
import (
"fmt"
"os/user"
"strconv"
"testing"
@ -303,12 +302,6 @@ func TestProfileManagement(t *testing.T) {
// TestProfileManagementWindows tests going into and out of Unattended mode on
// Windows.
func TestProfileManagementWindows(t *testing.T) {
u, err := user.Current()
if err != nil {
t.Fatal(err)
}
uid := ipn.WindowsUserID(u.Uid)
store := new(mem.Store)
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "windows")
@ -357,8 +350,8 @@ func TestProfileManagementWindows(t *testing.T) {
{
t.Logf("Set user1 as logged in user")
if err := pm.SetCurrentUserID(uid); err != nil {
t.Fatalf("can't set user id: %s", err)
if err := pm.SetCurrentUserID("user1"); err != nil {
t.Fatal(err)
}
checkProfiles(t)
t.Logf("Save prefs for user1")
@ -393,7 +386,7 @@ func TestProfileManagementWindows(t *testing.T) {
{
t.Logf("Set user1 as current user")
if err := pm.SetCurrentUserID(uid); err != nil {
if err := pm.SetCurrentUserID("user1"); err != nil {
t.Fatal(err)
}
wantCurProfile = "test"
@ -403,8 +396,8 @@ func TestProfileManagementWindows(t *testing.T) {
t.Logf("set unattended mode")
wantProfiles["test"] = setPrefs(t, "test", true)
}
if pm.CurrentUserID() != uid {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
if pm.CurrentUserID() != "user1" {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), "user1")
}
// Recreate the profile manager to ensure that it starts with test profile.
@ -413,7 +406,7 @@ func TestProfileManagementWindows(t *testing.T) {
t.Fatal(err)
}
checkProfiles(t)
if pm.CurrentUserID() != uid {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
if pm.CurrentUserID() != "user1" {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), "user1")
}
}

View File

@ -6,7 +6,6 @@ package ipnlocal
import (
"errors"
"fmt"
"io/fs"
"os"
"os/user"
"path/filepath"
@ -22,6 +21,8 @@ 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
@ -40,7 +41,6 @@ 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,20 +48,14 @@ 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
}

View File

@ -17,6 +17,7 @@ import (
"net/url"
"os"
"path"
pathpkg "path"
"strconv"
"strings"
"sync"
@ -143,7 +144,7 @@ func (s *serveListener) Run() {
}
func (s *serveListener) shouldWarnAboutListenError(err error) bool {
if !s.b.sys.NetMon.Get().InterfaceState().HasIP(s.ap.Addr()) {
if !s.b.e.GetLinkMonitor().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
@ -162,13 +163,12 @@ func (s *serveListener) handleServeListenersAccept(ln net.Listener) error {
return err
}
srcAddr := conn.RemoteAddr().(*net.TCPAddr).AddrPort()
handler := s.b.tcpHandlerForServe(s.ap.Port(), srcAddr)
if handler == nil {
getConn := func() (net.Conn, bool) { return conn, true }
sendRST := func() {
s.b.logf("serve RST for %v", srcAddr)
conn.Close()
continue
}
go handler(conn)
go s.b.HandleInterceptedTCPConn(s.ap.Port(), srcAddr, getConn, sendRST)
}
}
@ -257,7 +257,7 @@ func (b *LocalBackend) ServeConfig() ipn.ServeConfigView {
return b.serveConfig
}
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ipn.HostPort, srcAddr netip.AddrPort, getConnOrReset func() (net.Conn, bool), sendRST func()) {
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ipn.HostPort, srcAddr netip.AddrPort, getConn func() (net.Conn, bool), sendRST func()) {
b.mu.Lock()
sc := b.serveConfig
b.mu.Unlock()
@ -290,7 +290,7 @@ func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ip
if b.getTCPHandlerForFunnelFlow != nil {
handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport)
if handler != nil {
c, ok := getConnOrReset()
c, ok := getConn()
if !ok {
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
return
@ -299,41 +299,39 @@ func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ip
return
}
}
// TODO(bradfitz): pass ingressPeer etc in context to tcpHandlerForServe,
// TODO(bradfitz): pass ingressPeer etc in context to HandleInterceptedTCPConn,
// extend serveHTTPContext or similar.
handler := b.tcpHandlerForServe(dport, srcAddr)
if handler == nil {
sendRST()
return
}
c, ok := getConnOrReset()
if !ok {
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
return
}
handler(c)
b.HandleInterceptedTCPConn(dport, srcAddr, getConn, sendRST)
}
// tcpHandlerForServe returns a handler for a TCP connection to be served via
// the ipn.ServeConfig.
func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort) (handler func(net.Conn) error) {
func (b *LocalBackend) HandleInterceptedTCPConn(dport uint16, srcAddr netip.AddrPort, getConn func() (net.Conn, bool), sendRST func()) {
b.mu.Lock()
sc := b.serveConfig
b.mu.Unlock()
if !sc.Valid() {
b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport)
return nil
sendRST()
return
}
tcph, ok := sc.TCP().GetOk(dport)
if !ok {
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
return nil
sendRST()
return
}
if tcph.HTTPS() || tcph.HTTP() {
if tcph.HTTPS() {
conn, ok := getConn()
if !ok {
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
return
}
hs := &http.Server{
TLSConfig: &tls.Config{
GetCertificate: b.getTLSServeCertForPort(dport),
},
Handler: http.HandlerFunc(b.serveWebHandler),
BaseContext: func(_ net.Listener) context.Context {
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
@ -342,92 +340,79 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
})
},
}
if tcph.HTTPS() {
hs.TLSConfig = &tls.Config{
GetCertificate: b.getTLSServeCertForPort(dport),
}
return func(c net.Conn) error {
return hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
}
}
return func(c net.Conn) error {
return hs.Serve(netutil.NewOneConnListener(c, nil))
}
hs.ServeTLS(netutil.NewOneConnListener(conn, nil), "", "")
return
}
if backDst := tcph.TCPForward(); backDst != "" {
return func(conn net.Conn) error {
defer conn.Close()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
cancel()
if err != nil {
b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
return nil
}
defer backConn.Close()
if sni := tcph.TerminateTLS(); sni != "" {
conn = tls.Server(conn, &tls.Config{
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
pair, err := b.GetCertPEM(ctx, sni)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
if err != nil {
return nil, err
}
return &cert, nil
},
})
}
// TODO(bradfitz): do the RegisterIPPortIdentity and
// UnregisterIPPortIdentity stuff that netstack does
errc := make(chan error, 1)
go func() {
_, err := io.Copy(backConn, conn)
errc <- err
}()
go func() {
_, err := io.Copy(conn, backConn)
errc <- err
}()
return <-errc
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
cancel()
if err != nil {
b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
sendRST()
return
}
conn, ok := getConn()
if !ok {
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
backConn.Close()
return
}
defer conn.Close()
defer backConn.Close()
if sni := tcph.TerminateTLS(); sni != "" {
conn = tls.Server(conn, &tls.Config{
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
pair, err := b.GetCertPEM(ctx, sni)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
if err != nil {
return nil, err
}
return &cert, nil
},
})
}
// TODO(bradfitz): do the RegisterIPPortIdentity and
// UnregisterIPPortIdentity stuff that netstack does
errc := make(chan error, 1)
go func() {
_, err := io.Copy(backConn, conn)
errc <- err
}()
go func() {
_, err := io.Copy(conn, backConn)
errc <- err
}()
<-errc
return
}
b.logf("closing TCP conn to port %v (from %v) with actionless TCPPortHandler", dport, srcAddr)
return nil
}
func getServeHTTPContext(r *http.Request) (c *serveHTTPContext, ok bool) {
c, ok = r.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext)
return c, ok
sendRST()
}
func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, at string, ok bool) {
var z ipn.HTTPHandlerView // zero value
hostname := r.Host
if r.TLS == nil {
tcd := "." + b.Status().CurrentTailnet.MagicDNSSuffix
if !strings.HasSuffix(hostname, tcd) {
hostname += tcd
}
} else {
hostname = r.TLS.ServerName
return z, "", false
}
sctx, ok := getServeHTTPContext(r)
sctx, ok := r.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext)
if !ok {
b.logf("[unexpected] localbackend: no serveHTTPContext in request")
return z, "", false
}
wsc, ok := b.webServerConfig(hostname, sctx.DestPort)
wsc, ok := b.webServerConfig(r.TLS.ServerName, sctx.DestPort)
if !ok {
return z, "", false
}
@ -435,19 +420,19 @@ func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView,
if h, ok := wsc.Handlers().GetOk(r.URL.Path); ok {
return h, r.URL.Path, true
}
pth := path.Clean(r.URL.Path)
path := path.Clean(r.URL.Path)
for {
withSlash := pth + "/"
withSlash := path + "/"
if h, ok := wsc.Handlers().GetOk(withSlash); ok {
return h, withSlash, true
}
if h, ok := wsc.Handlers().GetOk(pth); ok {
return h, pth, true
if h, ok := wsc.Handlers().GetOk(path); ok {
return h, path, true
}
if pth == "/" {
if path == "/" {
return z, "", false
}
pth = path.Dir(pth)
path = pathpkg.Dir(path)
}
}
@ -463,8 +448,9 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.Reverse
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(u)
r.Out.Host = r.In.Host
addProxyForwardedHeaders(r)
b.addTailscaleIdentityHeaders(r)
if c, ok := r.Out.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext); ok {
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
}
},
Transport: &http.Transport{
DialContext: b.dialer.SystemDial,
@ -482,40 +468,6 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.Reverse
return rp, nil
}
func addProxyForwardedHeaders(r *httputil.ProxyRequest) {
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
if r.In.TLS != nil {
r.Out.Header.Set("X-Forwarded-Proto", "https")
}
if c, ok := getServeHTTPContext(r.Out); ok {
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
}
}
func (b *LocalBackend) addTailscaleIdentityHeaders(r *httputil.ProxyRequest) {
// Clear any incoming values squatting in the headers.
r.Out.Header.Del("Tailscale-User-Login")
r.Out.Header.Del("Tailscale-User-Name")
r.Out.Header.Del("Tailscale-Headers-Info")
c, ok := getServeHTTPContext(r.Out)
if !ok {
return
}
node, user, ok := b.WhoIs(c.SrcAddr)
if !ok {
return // traffic from outside of Tailnet (funneled)
}
if node.IsTagged() {
// 2023-06-14: Not setting identity headers for tagged nodes.
// Only currently set for nodes with user identities.
return
}
r.Out.Header.Set("Tailscale-User-Login", user.LoginName)
r.Out.Header.Set("Tailscale-User-Name", user.DisplayName)
r.Out.Header.Set("Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers")
}
func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
h, mountPoint, ok := b.getServeHandler(r)
if !ok {
@ -648,8 +600,8 @@ func allNumeric(s string) bool {
return s != ""
}
func (b *LocalBackend) webServerConfig(hostname string, port uint16) (c ipn.WebServerConfigView, ok bool) {
key := ipn.HostPort(fmt.Sprintf("%s:%v", hostname, port))
func (b *LocalBackend) webServerConfig(sniName string, port uint16) (c ipn.WebServerConfigView, ok bool) {
key := ipn.HostPort(fmt.Sprintf("%s:%v", sniName, port))
b.mu.Lock()
defer b.mu.Unlock()

View File

@ -10,22 +10,12 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/netip"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"tailscale.com/ipn"
"tailscale.com/ipn/store/mem"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/types/logid"
"tailscale.com/types/netmap"
"tailscale.com/util/cmpx"
"tailscale.com/util/must"
"tailscale.com/wgengine"
)
func TestExpandProxyArg(t *testing.T) {
@ -150,7 +140,10 @@ func TestGetServeHandler(t *testing.T) {
},
TLS: &tls.ConnectionState{ServerName: serverName},
}
port := cmpx.Or(tt.port, 443)
port := tt.port
if port == 0 {
port = 443
}
req = req.WithContext(context.WithValue(req.Context(), serveHTTPContextKey{}, &serveHTTPContext{
DestPort: port,
}))
@ -169,142 +162,6 @@ func TestGetServeHandler(t *testing.T) {
}
}
func TestServeHTTPProxy(t *testing.T) {
sys := &tsd.System{}
e, err := wgengine.NewUserspaceEngine(t.Logf, wgengine.Config{SetSubsystem: sys.Set})
if err != nil {
t.Fatal(err)
}
sys.Set(e)
sys.Set(new(mem.Store))
b, err := NewLocalBackend(t.Logf, logid.PublicID{}, sys, 0)
if err != nil {
t.Fatal(err)
}
defer b.Shutdown()
dir := t.TempDir()
b.SetVarRoot(dir)
pm := must.Get(newProfileManager(new(mem.Store), t.Logf))
pm.currentProfile = &ipn.LoginProfile{ID: "id0"}
b.pm = pm
b.netMap = &netmap.NetworkMap{
SelfNode: &tailcfg.Node{
Name: "example.ts.net",
},
UserProfiles: map[tailcfg.UserID]tailcfg.UserProfile{
tailcfg.UserID(1): {
LoginName: "someone@example.com",
DisplayName: "Some One",
},
},
}
b.nodeByAddr = map[netip.Addr]*tailcfg.Node{
netip.MustParseAddr("100.150.151.152"): {
ComputedName: "some-peer",
User: tailcfg.UserID(1),
},
netip.MustParseAddr("100.150.151.153"): {
ComputedName: "some-tagged-peer",
Tags: []string{"tag:server", "tag:test"},
User: tailcfg.UserID(1),
},
}
// Start test serve endpoint.
testServ := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
// Piping all the headers through the response writer
// so we can check their values in tests below.
for key, val := range r.Header {
w.Header().Add(key, strings.Join(val, ","))
}
},
))
defer testServ.Close()
conf := &ipn.ServeConfig{
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: testServ.URL},
}},
},
}
if err := b.SetServeConfig(conf); err != nil {
t.Fatal(err)
}
type headerCheck struct {
header string
want string
}
tests := []struct {
name string
srcIP string
wantHeaders []headerCheck
}{
{
name: "request-from-user-within-tailnet",
srcIP: "100.150.151.152",
wantHeaders: []headerCheck{
{"X-Forwarded-Proto", "https"},
{"X-Forwarded-For", "100.150.151.152"},
{"Tailscale-User-Login", "someone@example.com"},
{"Tailscale-User-Name", "Some One"},
{"Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers"},
},
},
{
name: "request-from-tagged-node-within-tailnet",
srcIP: "100.150.151.153",
wantHeaders: []headerCheck{
{"X-Forwarded-Proto", "https"},
{"X-Forwarded-For", "100.150.151.153"},
{"Tailscale-User-Login", ""},
{"Tailscale-User-Name", ""},
{"Tailscale-Headers-Info", ""},
},
},
{
name: "request-from-outside-tailnet",
srcIP: "100.160.161.162",
wantHeaders: []headerCheck{
{"X-Forwarded-Proto", "https"},
{"X-Forwarded-For", "100.160.161.162"},
{"Tailscale-User-Login", ""},
{"Tailscale-User-Name", ""},
{"Tailscale-Headers-Info", ""},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := &http.Request{
URL: &url.URL{Path: "/"},
TLS: &tls.ConnectionState{ServerName: "example.ts.net"},
}
req = req.WithContext(context.WithValue(req.Context(), serveHTTPContextKey{}, &serveHTTPContext{
DestPort: 443,
SrcAddr: netip.MustParseAddrPort(tt.srcIP + ":1234"), // random src port for tests
}))
w := httptest.NewRecorder()
b.serveWebHandler(w, req)
// Verify the headers.
h := w.Result().Header
for _, c := range tt.wantHeaders {
if got := h.Get(c.header); got != c.want {
t.Errorf("invalid %q header; want=%q, got=%q", c.header, c.want, got)
}
}
})
}
}
func TestServeFileOrDirectory(t *testing.T) {
td := t.TempDir()
writeFile := func(suffix, contents string) {

View File

@ -17,7 +17,6 @@ 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"
@ -298,17 +297,14 @@ func TestStateMachine(t *testing.T) {
c := qt.New(t)
logf := tstest.WhileTestRunningLogger(t)
sys := new(tsd.System)
store := new(testStateStorage)
sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
t.Cleanup(e.Close)
sys.Set(e)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
b, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, e, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}
@ -945,16 +941,13 @@ func TestStateMachine(t *testing.T) {
func TestEditPrefsHasNoKeys(t *testing.T) {
logf := tstest.WhileTestRunningLogger(t)
sys := new(tsd.System)
sys.Set(new(mem.Store))
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err)
}
t.Cleanup(e.Close)
sys.Set(e)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
b, err := NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), nil, e, 0)
if err != nil {
t.Fatalf("NewLocalBackend: %v", err)
}
@ -1030,14 +1023,10 @@ func TestWGEngineStatusRace(t *testing.T) {
t.Skip("test fails")
c := qt.New(t)
logf := tstest.WhileTestRunningLogger(t)
sys := new(tsd.System)
sys.Set(new(mem.Store))
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
c.Assert(err, qt.IsNil)
t.Cleanup(eng.Close)
sys.Set(eng)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
b, err := NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), nil, eng, 0)
c.Assert(err, qt.IsNil)
var cc *mockControl

View File

@ -37,8 +37,7 @@ func (s *Server) handleProxyConnectConn(w http.ResponseWriter, r *http.Request)
return
}
dialContext := logpolicy.MakeDialFunc(s.netMon)
back, err := dialContext(ctx, "tcp", hostPort)
back, err := logpolicy.DialContext(ctx, "tcp", hostPort)
if err != nil {
s.logf("error CONNECT dialing %v: %v", hostPort, err)
http.Error(w, "Connect failure", http.StatusBadGateway)

View File

@ -24,7 +24,6 @@ import (
"tailscale.com/ipn/ipnauth"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/localapi"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/util/mak"
@ -37,7 +36,6 @@ import (
type Server struct {
lb atomic.Pointer[ipnlocal.LocalBackend]
logf logger.Logf
netMon *netmon.Monitor // must be non-nil
backendLogID logid.PublicID
// resetOnZero is whether to call bs.Reset on transition from
// 1->0 active HTTP requests. That is, this is whether the backend is
@ -199,7 +197,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) {
defer onDone()
if strings.HasPrefix(r.URL.Path, "/localapi/") {
lah := localapi.NewHandler(lb, s.logf, s.netMon, s.backendLogID)
lah := localapi.NewHandler(lb, s.logf, s.backendLogID)
lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci)
lah.PermitCert = s.connCanFetchCerts(ci)
lah.ServeHTTP(w, r)
@ -415,14 +413,10 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit
//
// At some point, either before or after Run, the Server's SetLocalBackend
// method must also be called before Server can do anything useful.
func New(logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) *Server {
if netMon == nil {
panic("nil netMon")
}
func New(logf logger.Logf, logID logid.PublicID) *Server {
return &Server{
backendLogID: logID,
logf: logf,
netMon: netMon,
resetOnZero: envknob.GOOS() == "windows",
}
}

View File

@ -121,11 +121,6 @@ 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.
@ -223,8 +218,9 @@ type PeerStatus struct {
LastSeen time.Time // last seen to tailcontrol; only present if offline
LastHandshake time.Time // with local wireguard
Online bool // whether node is connected to the control plane
ExitNode bool // true if this is the currently selected exit node.
ExitNodeOption bool // true if this node can be an exit node (offered && approved)
KeepAlive bool
ExitNode bool // true if this is the currently selected exit node.
ExitNodeOption bool // true if this node can be an exit node (offered && approved)
// Active is whether the node was recently active. The
// definition is somewhat undefined but has historically and
@ -436,6 +432,9 @@ func (sb *StatusBuilder) AddPeer(peer key.NodePublic, st *PeerStatus) {
if st.InEngine {
e.InEngine = true
}
if st.KeepAlive {
e.KeepAlive = true
}
if st.ExitNode {
e.ExitNode = true
}
@ -584,8 +583,6 @@ func osEmoji(os string) string {
return "🖥️"
case "iOS":
return "📱"
case "tvOS":
return "🍎📺"
case "android":
return "🤖"
case "freebsd":

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