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 }} private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
- name: Send pull request - 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: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
author: License Updater <noreply+license-updater@tailscale.com> author: License Updater <noreply@tailscale.com>
committer: License Updater <noreply+license-updater@tailscale.com> committer: License Updater <noreply@tailscale.com>
branch: licenses/cli branch: licenses/cli
commit-message: "licenses: update tailscale{,d} licenses" commit-message: "licenses: update tailscale{,d} licenses"
title: "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: include:
- goarch: amd64 - goarch: amd64
- goarch: amd64 - goarch: amd64
buildflags: "-race" variant: race
- goarch: "386" # thanks yaml - goarch: "386" # thanks yaml
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v3 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 - name: build all
run: ./tool/go build ${{matrix.buildflags}} ./... run: ./tool/go build ./...
env: env:
GOARCH: ${{ matrix.goarch }} GOARCH: ${{ matrix.goarch }}
- name: build variant CLIs - name: build variant CLIs
@ -90,11 +73,13 @@ jobs:
- name: build test wrapper - name: build test wrapper
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
- name: test all - 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: env:
GOARCH: ${{ matrix.goarch }} GOARCH: ${{ matrix.goarch }}
- name: bench all - name: test all (race)
run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}} -bench=. -benchtime=1x -run=^$ if: matrix.variant == 'race'
run: ./tool/go test -race -exec=/tmp/testwrapper -bench=. -benchtime=1x ./...
env: env:
GOARCH: ${{ matrix.goarch }} GOARCH: ${{ matrix.goarch }}
- name: check that no tracked files changed - name: check that no tracked files changed
@ -116,13 +101,6 @@ jobs:
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
go-version-file: go.mod
cache: false
- name: Restore Cache - name: Restore Cache
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
@ -131,20 +109,17 @@ jobs:
# contains zips that can be unpacked in parallel faster than they can be # contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar # fetched and extracted by tar
path: | path: |
~/.cache/go-build
~/go/pkg/mod/cache ~/go/pkg/mod/cache
~\AppData\Local\go-build ~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be # The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes). # cached changes (e.g. path above changes).
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }} # TODO(raggi): add a go version here.
restore-keys: | key: ${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
${{ github.job }}-${{ runner.os }}-go-2-
- name: test - name: test
# Don't use -bench=. -benchtime=1x. # Don't use -bench=. -benchtime=1x.
# Somewhere in the layers (powershell?) # Somewhere in the layers (powershell?)
# the equals signs cause great confusion. # the equals signs cause great confusion.
run: go test -bench . -benchtime 1x ./... run: ./tool/go test -bench . -benchtime 1x ./...
vm: vm:
runs-on: ["self-hosted", "linux", "vm"] runs-on: ["self-hosted", "linux", "vm"]
@ -199,23 +174,6 @@ jobs:
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v3 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 - name: build all
run: ./tool/go build ./cmd/... run: ./tool/go build ./cmd/...
env: env:
@ -265,23 +223,6 @@ jobs:
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v3 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 - name: build tsconnect client
run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli
env: env:
@ -458,7 +399,7 @@ jobs:
# By having the job always run, but skipping its only step as needed, we # By having the job always run, but skipping its only step as needed, we
# let the CI output collapse nicely in PRs. # let the CI output collapse nicely in PRs.
if: failure() && github.event_name == 'push' if: failure() && github.event_name == 'push'
uses: ruby/action-slack@v3.2.1 uses: ruby/action-slack@v3.0.0
with: with:
payload: | payload: |
{ {

View File

@ -35,11 +35,11 @@ jobs:
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }} private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
- name: Send pull request - 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: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
author: Flakes Updater <noreply+flakes-updater@tailscale.com> author: Flakes Updater <noreply@tailscale.com>
committer: Flakes Updater <noreply+flakes-updater@tailscale.com> committer: Flakes Updater <noreply@tailscale.com>
branch: flakes branch: flakes
commit-message: "go.mod.sri: update SRI hash for go.mod changes" commit-message: "go.mod.sri: update SRI hash for go.mod changes"
title: "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/ssh \
golang.org/x/crypto/acme \ golang.org/x/crypto/acme \
nhooyr.io/websocket \ nhooyr.io/websocket \
github.com/mdlayher/netlink github.com/mdlayher/netlink \
golang.zx2c4.com/wireguard/device
COPY . . COPY . .
@ -72,4 +73,4 @@ RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables
COPY --from=build-env /go/bin/* /usr/local/bin/ COPY --from=build-env /go/bin/* /usr/local/bin/
# For compat with the previous run.sh, although ideally you should be # For compat with the previous run.sh, although ideally you should be
# using build_docker.sh which sets an entrypoint for the image. # 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 # SPDX-License-Identifier: BSD-3-Clause
FROM alpine:3.16 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) ./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 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 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 pushspk: spk ## Push and install synology package on ${SYNO_HOST} host
echo "Pushing SPK to root@${SYNO_HOST} (env var SYNO_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 ``` jsonc
{ {
// addresses (array of strings) is a list of Tailscale IP // addresses (array of strings) is a list of Tailscale IP
// addresses for the device, including both IPv4 (formatted as 100.x.y.z) // 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. // and ipv6 (formatted as fd7a:115c:a1e0:a:b:c:d:e) addresses.
"addresses": [ "addresses": [
"100.87.74.78", "100.87.74.78",
"fd7a:115c:a1e0:ac82:4843:ca90:697d:c36e" "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 POST /api/v2/device/{deviceID}/authorized
``` ```
Authorize a device. Authorize a device. This call marks a device as authorized for tailnets where device authorization is required.
This call marks a device as authorized or revokes its authorization for tailnets where device authorization is required, according to the `authorized` field in the payload.
This returns a successful 2xx response with an empty JSON object in the response body. 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) #### `authorized` (required in `POST` body)
Specify whether the device is authorized. Specify whether the device is authorized. Only 'true' is currently supported.
``` jsonc ``` 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 (int) is the duration in seconds a new key is valid.
"expirySeconds": 86400 "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. Specifies the duration in seconds until the key should expire.
Defaults to 90 days if not supplied. 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 ### Request example
``` jsonc ``` jsonc
@ -1333,8 +1324,7 @@ curl "https://api.tailscale.com/api/v2/tailnet/example.com/keys" \
} }
} }
}, },
"expirySeconds": 86400, "expirySeconds": 86400
"description": "dev access"
}' }'
``` ```
@ -1346,8 +1336,8 @@ It holds the capabilities specified in the request and can no longer be retrieve
``` jsonc ``` jsonc
{ {
"id": "k123456CNTRL", "id": "XXXX456CNTRL",
"key": "tskey-auth-k123456CNTRL-abcdefghijklmnopqrstuvwxyz", "key": "tskey-k123456CNTRL-abcdefghijklmnopqrstuvwxyz",
"created": "2021-12-09T23:22:39Z", "created": "2021-12-09T23:22:39Z",
"expires": "2022-03-09T23:22:39Z", "expires": "2022-03-09T23:22:39Z",
"revoked": "2022-03-12T23: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, "ephemeral": false,
"preauthorized": false, "preauthorized": false,
"tags": [ "tag:example" ] "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. // 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"}) l, err := net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -16,7 +16,7 @@ if [ -n "${TS_USE_TOOLCHAIN:-}" ]; then
go="./tool/go" go="./tool/go"
fi 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 if [ "$1" = "shellvars" ]; then
cat <<EOF cat <<EOF
@ -49,4 +49,4 @@ while [ "$#" -gt 1 ]; do
esac esac
done 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) postData, err := json.Marshal(tests)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"tailscale.com/types/opt" "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. // AuthorizeDevice marks a device as authorized.
func (c *Client) AuthorizeDevice(ctx context.Context, deviceID string) error { 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)) 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 { if err != nil {
return err return err
} }

View File

@ -63,7 +63,7 @@ func (c *Client) dnsGETRequest(ctx context.Context, endpoint string) ([]byte, er
return b, nil 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) path := fmt.Sprintf("%s/api/v2/tailnet/%s/dns/%s", c.baseURL(), c.tailnet, endpoint)
data, err := json.Marshal(&postData) data, err := json.Marshal(&postData)
if err != nil { 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 // 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. // later, and the key metadata.
// func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (string, *Key, error) {
// To create a key with a specific expiry, use CreateKeyWithExpiry.
func (c *Client) CreateKey(ctx context.Context, caps KeyCapabilities) (keySecret string, keyMeta *Key, _ error) {
return c.CreateKeyWithExpiry(ctx, caps, 0)
}
// CreateKeyWithExpiry is like CreateKey, but allows specifying a expiration time.
//
// The time is truncated to a whole number of seconds. If zero, that means no expiration.
func (c *Client) CreateKeyWithExpiry(ctx context.Context, caps KeyCapabilities, expiry time.Duration) (keySecret string, keyMeta *Key, _ error) {
// convert expirySeconds to an int64 (seconds)
expirySeconds := int64(expiry.Seconds())
if expirySeconds < 0 {
return "", nil, fmt.Errorf("expiry must be positive")
}
if expirySeconds == 0 && expiry != 0 {
return "", nil, fmt.Errorf("non-zero expiry must be at least one second")
}
keyRequest := struct { keyRequest := struct {
Capabilities KeyCapabilities `json:"capabilities"` Capabilities KeyCapabilities `json:"capabilities"`
ExpirySeconds int64 `json:"expirySeconds,omitempty"` }{caps}
}{caps, int64(expirySeconds)}
bs, err := json.Marshal(keyRequest) bs, err := json.Marshal(keyRequest)
if err != nil { if err != nil {
return "", nil, err return "", nil, err

View File

@ -946,21 +946,6 @@ func (lc *LocalClient) NetworkLockForceLocalDisable(ctx context.Context) error {
return nil 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. // SetServeConfig sets or replaces the serving settings.
// If config is nil, settings are cleared and serving is disabled. // If config is nil, settings are cleared and serving is disabled.
func (lc *LocalClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error { 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) return nil, fmt.Errorf("can not load cert: %w", err)
} }
if err := x509Cert.VerifyHostname(hostname); err != nil { 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 return &manualCertManager{cert: &cert, hostname: hostname}, nil
} }
@ -81,7 +81,7 @@ func (m *manualCertManager) TLSConfig() *tls.Config {
return &tls.Config{ return &tls.Config{
Certificates: nil, Certificates: nil,
NextProtos: []string{ NextProtos: []string{
"http/1.1", "h2", "http/1.1", // enable HTTP/2
}, },
GetCertificate: m.getCertificate, GetCertificate: m.getCertificate,
} }
@ -89,7 +89,7 @@ func (m *manualCertManager) TLSConfig() *tls.Config {
func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) { func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
if hi.ServerName != m.hostname { 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 // 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 from github.com/hdevalence/ed25519consensus
filippo.io/edwards25519/field from filippo.io/edwards25519 filippo.io/edwards25519/field from filippo.io/edwards25519
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket 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/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/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 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/internal/common from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy 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/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
💣 github.com/cespare/xxhash/v2 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/fxamacker/cbor/v2 from tailscale.com/tka
github.com/golang/groupcache/lru from tailscale.com/net/dnscache github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil+ github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil+
L github.com/google/nftables from tailscale.com/util/linuxfw github.com/golang/protobuf/ptypes/timestamp from github.com/prometheus/client_model/go
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 github.com/hdevalence/ed25519consensus from tailscale.com/tka
L github.com/josharian/native from github.com/mdlayher/netlink+ 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 L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress/flate from nhooyr.io/websocket github.com/klauspost/compress/flate from nhooyr.io/websocket
github.com/matttproud/golang_protobuf_extensions/pbutil from github.com/prometheus/common/expfmt 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 from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc 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 L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket 💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz 💣 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 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/fs from github.com/prometheus/procfs
LD github.com/prometheus/procfs/internal/util 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 github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/client/tailscale+ 💣 go4.org/mem from tailscale.com/client/tailscale+
go4.org/netipx from tailscale.com/wgengine/filter 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/protoiface from github.com/golang/protobuf/proto+
google.golang.org/protobuf/runtime/protoimpl 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/descriptorpb from google.golang.org/protobuf/reflect/protodesc
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+ google.golang.org/protobuf/types/known/timestamppb from github.com/golang/protobuf/ptypes/timestamp+
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
nhooyr.io/websocket from tailscale.com/cmd/derper+ nhooyr.io/websocket from tailscale.com/cmd/derper+
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
nhooyr.io/websocket/internal/xsync 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/interfaces from tailscale.com/net/netns+
tailscale.com/net/netaddr from tailscale.com/ipn+ tailscale.com/net/netaddr from tailscale.com/ipn+
tailscale.com/net/netknob from tailscale.com/net/netns 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/netns from tailscale.com/derp/derphttp
tailscale.com/net/netutil from tailscale.com/client/tailscale tailscale.com/net/netutil from tailscale.com/client/tailscale
tailscale.com/net/packet from tailscale.com/wgengine/filter tailscale.com/net/packet from tailscale.com/wgengine/filter
tailscale.com/net/sockstats from tailscale.com/derp/derphttp tailscale.com/net/sockstats from tailscale.com/derp/derphttp
tailscale.com/net/stun from tailscale.com/cmd/derper 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/tlsdial from tailscale.com/derp/derphttp
tailscale.com/net/tsaddr from tailscale.com/ipn+ tailscale.com/net/tsaddr from tailscale.com/ipn+
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+ 💣 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 W tailscale.com/util/clientmetric from tailscale.com/net/tshttpproxy
tailscale.com/util/cloudenv from tailscale.com/hostinfo+ tailscale.com/util/cloudenv from tailscale.com/hostinfo+
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy 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 L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
tailscale.com/util/dnsname from tailscale.com/hostinfo+ tailscale.com/util/dnsname from tailscale.com/hostinfo+
tailscale.com/util/httpm from tailscale.com/client/tailscale tailscale.com/util/httpm from tailscale.com/client/tailscale
tailscale.com/util/lineread from tailscale.com/hostinfo+ 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/mak from tailscale.com/syncs+
tailscale.com/util/multierr from tailscale.com/health+ tailscale.com/util/multierr from tailscale.com/health
tailscale.com/util/set from tailscale.com/health+ tailscale.com/util/set from tailscale.com/health
tailscale.com/util/singleflight from tailscale.com/net/dnscache tailscale.com/util/singleflight from tailscale.com/net/dnscache
tailscale.com/util/slicesx from tailscale.com/cmd/derper+ tailscale.com/util/slicesx from tailscale.com/cmd/derper+
tailscale.com/util/vizerror from tailscale.com/tsweb 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/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/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/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/exp/slices from tailscale.com/net/tsaddr+
L golang.org/x/net/bpf from github.com/mdlayher/netlink+ L golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+ 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+ bytes from bufio+
compress/flate from compress/gzip+ compress/flate from compress/gzip+
compress/gzip from internal/profile+ compress/gzip from internal/profile+
L compress/zlib from debug/elf
container/list from crypto/tls+ container/list from crypto/tls+
context from crypto/tls+ context from crypto/tls+
crypto from crypto/ecdsa+ 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/tls from golang.org/x/crypto/acme+
crypto/x509 from crypto/tls+ crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+ 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+ embed from crypto/internal/nistec+
encoding from encoding/json+ encoding from encoding/json+
encoding/asn1 from crypto/x509+ encoding/asn1 from crypto/x509+
@ -249,7 +215,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
fmt from compress/flate+ fmt from compress/flate+
go/token from google.golang.org/protobuf/internal/strs go/token from google.golang.org/protobuf/internal/strs
hash from crypto+ hash from crypto+
L hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+ hash/crc32 from compress/gzip+
hash/fnv from google.golang.org/protobuf/internal/detrand hash/fnv from google.golang.org/protobuf/internal/detrand
hash/maphash from go4.org/mem 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/fs from crypto/x509+
io/ioutil from github.com/mitchellh/go-ps+ io/ioutil from github.com/mitchellh/go-ps+
log from expvar+ log from expvar+
log/internal from log
math from compress/flate+ math from compress/flate+
math/big from crypto/dsa+ math/big from crypto/dsa+
math/bits from compress/flate+ math/bits from compress/flate+

View File

@ -33,7 +33,6 @@ import (
"tailscale.com/net/stun" "tailscale.com/net/stun"
"tailscale.com/tsweb" "tailscale.com/tsweb"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/util/cmpx"
) )
var ( var (
@ -437,7 +436,11 @@ func defaultMeshPSKFile() string {
} }
func rateLimitedListenAndServeTLS(srv *http.Server) error { 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 { if err != nil {
return err return err
} }

25
cmd/dist/dist.go vendored
View File

@ -13,38 +13,15 @@ import (
"tailscale.com/release/dist" "tailscale.com/release/dist"
"tailscale.com/release/dist/cli" "tailscale.com/release/dist/cli"
"tailscale.com/release/dist/synology"
"tailscale.com/release/dist/unixpkgs" "tailscale.com/release/dist/unixpkgs"
) )
var synologyPackageCenter bool
func getTargets() ([]dist.Target, error) { func getTargets() ([]dist.Target, error) {
var ret []dist.Target return unixpkgs.Targets(), nil
ret = append(ret, unixpkgs.Targets()...)
// Synology packages can be built either for sideloading, or for
// distribution by Synology in their package center. When
// distributed through the package center, apps can request
// additional permissions to use a tuntap interface and control
// the NAS's network stack, rather than be forced to run in
// userspace mode.
//
// Since only we can provide packages to Synology for
// distribution, we default to building the "sideload" variant of
// packages that we distribute on pkgs.tailscale.com.
ret = append(ret, synology.Targets(synologyPackageCenter)...)
return ret, nil
} }
func main() { func main() {
cmd := cli.CLI(getTargets) 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) { if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil && !errors.Is(err, flag.ErrHelp) {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -16,7 +16,6 @@ import (
"golang.org/x/oauth2/clientcredentials" "golang.org/x/oauth2/clientcredentials"
"tailscale.com/client/tailscale" "tailscale.com/client/tailscale"
"tailscale.com/util/cmpx"
) )
func main() { func main() {
@ -30,9 +29,9 @@ func main() {
tags := flag.String("tags", "", "comma-separated list of tags to apply to the authkey") tags := flag.String("tags", "", "comma-separated list of tags to apply to the authkey")
flag.Parse() flag.Parse()
clientID := os.Getenv("TS_API_CLIENT_ID") clientId := os.Getenv("TS_API_CLIENT_ID")
clientSecret := os.Getenv("TS_API_CLIENT_SECRET") 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") 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") 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{ credentials := clientcredentials.Config{
ClientID: clientID, ClientID: clientId,
ClientSecret: clientSecret, ClientSecret: clientSecret,
TokenURL: baseURL + "/api/v2/oauth/token", TokenURL: baseUrl + "/api/v2/oauth/token",
Scopes: []string{"device"}, Scopes: []string{"device"},
} }
ctx := context.Background() ctx := context.Background()
tsClient := tailscale.NewClient("-", nil) tsClient := tailscale.NewClient("-", nil)
tsClient.HTTPClient = credentials.Client(ctx) tsClient.HTTPClient = credentials.Client(ctx)
tsClient.BaseURL = baseURL tsClient.BaseURL = baseUrl
caps := tailscale.KeyCapabilities{ caps := tailscale.KeyCapabilities{
Devices: tailscale.KeyDeviceCapabilities{ Devices: tailscale.KeyDeviceCapabilities{

View File

@ -25,6 +25,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/transport" "k8s.io/client-go/transport"
"sigs.k8s.io/controller-runtime/pkg/builder" "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"
"sigs.k8s.io/controller-runtime/pkg/manager/signals" "sigs.k8s.io/controller-runtime/pkg/manager/signals"
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"tailscale.com/client/tailscale" "tailscale.com/client/tailscale"
"tailscale.com/hostinfo" "tailscale.com/hostinfo"
@ -46,7 +48,6 @@ import (
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/opt" "tailscale.com/types/opt"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/version"
) )
func main() { func main() {
@ -63,7 +64,6 @@ func main() {
clientIDPath = defaultEnv("CLIENT_ID_FILE", "") clientIDPath = defaultEnv("CLIENT_ID_FILE", "")
clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "") clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "")
image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest") image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest")
priorityClassName = defaultEnv("PROXY_PRIORITY_CLASS_NAME", "")
tags = defaultEnv("PROXY_TAGS", "tag:k8s") tags = defaultEnv("PROXY_TAGS", "tag:k8s")
shouldRunAuthProxy = defaultBool("AUTH_PROXY", false) shouldRunAuthProxy = defaultBool("AUTH_PROXY", false)
) )
@ -183,33 +183,32 @@ waitOnline:
// the cache that sits a few layers below the builder stuff, which will // 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 // implicitly filter what parts of the world the builder code gets to see at
// all. // all.
nsFilter := cache.ByObject{ nsFilter := cache.ObjectSelector{
Field: client.InNamespace(tsNamespace).AsSelector(), Field: fields.SelectorFromSet(fields.Set{"metadata.namespace": tsNamespace}),
} }
restConfig := config.GetConfigOrDie() restConfig := config.GetConfigOrDie()
mgr, err := manager.New(restConfig, manager.Options{ mgr, err := manager.New(restConfig, manager.Options{
Cache: cache.Options{ NewCache: cache.BuilderWithOptions(cache.Options{
ByObject: map[client.Object]cache.ByObject{ SelectorsByObject: map[client.Object]cache.ObjectSelector{
&corev1.Secret{}: nsFilter, &corev1.Secret{}: nsFilter,
&appsv1.StatefulSet{}: nsFilter, &appsv1.StatefulSet{}: nsFilter,
}, },
}, }),
}) })
if err != nil { if err != nil {
startlog.Fatalf("could not create manager: %v", err) startlog.Fatalf("could not create manager: %v", err)
} }
sr := &ServiceReconciler{ sr := &ServiceReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
tsClient: tsClient, tsClient: tsClient,
defaultTags: strings.Split(tags, ","), defaultTags: strings.Split(tags, ","),
operatorNamespace: tsNamespace, operatorNamespace: tsNamespace,
proxyImage: image, proxyImage: image,
proxyPriorityClassName: priorityClassName, logger: zlog.Named("service-reconciler"),
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() ls := o.GetLabels()
if ls[LabelManaged] != "true" { if ls[LabelManaged] != "true" {
return nil return nil
@ -229,14 +228,14 @@ waitOnline:
err = builder. err = builder.
ControllerManagedBy(mgr). ControllerManagedBy(mgr).
For(&corev1.Service{}). For(&corev1.Service{}).
Watches(&appsv1.StatefulSet{}, reconcileFilter). Watches(&source.Kind{Type: &appsv1.StatefulSet{}}, reconcileFilter).
Watches(&corev1.Secret{}, reconcileFilter). Watches(&source.Kind{Type: &corev1.Secret{}}, reconcileFilter).
Complete(sr) Complete(sr)
if err != nil { if err != nil {
startlog.Fatalf("could not create controller: %v", err) 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 { if shouldRunAuthProxy {
cfg, err := restConfig.TransportConfig() cfg, err := restConfig.TransportConfig()
if err != nil { if err != nil {
@ -279,12 +278,11 @@ const (
// ServiceReconciler is a simple ControllerManagedBy example implementation. // ServiceReconciler is a simple ControllerManagedBy example implementation.
type ServiceReconciler struct { type ServiceReconciler struct {
client.Client client.Client
tsClient tsClient tsClient tsClient
defaultTags []string defaultTags []string
operatorNamespace string operatorNamespace string
proxyImage string proxyImage string
proxyPriorityClassName string logger *zap.SugaredLogger
logger *zap.SugaredLogger
} }
type tsClient interface { type tsClient interface {
@ -568,9 +566,6 @@ func (a *ServiceReconciler) getDeviceInfo(ctx context.Context, svc *corev1.Servi
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
if sec == nil {
return "", "", nil
}
id = string(sec.Data["device_id"]) id = string(sec.Data["device_id"])
if id == "" { if id == "" {
return "", "", nil return "", "", nil
@ -594,7 +589,6 @@ func (a *ServiceReconciler) newAuthKey(ctx context.Context, tags []string) (stri
}, },
}, },
} }
key, _, err := a.tsClient.CreateKey(ctx, caps) key, _, err := a.tsClient.CreateKey(ctx, caps)
if err != nil { if err != nil {
return "", err return "", err
@ -639,7 +633,6 @@ func (a *ServiceReconciler) reconcileSTS(ctx context.Context, logger *zap.Sugare
ss.Spec.Template.ObjectMeta.Labels = map[string]string{ ss.Spec.Template.ObjectMeta.Labels = map[string]string{
"app": string(parentSvc.UID), "app": string(parentSvc.UID),
} }
ss.Spec.Template.Spec.PriorityClassName = a.proxyPriorityClassName
logger.Debugf("reconciling statefulset %s/%s", ss.GetNamespace(), ss.GetName()) 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 }) 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" "go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
@ -64,7 +65,7 @@ func TestLoadBalancerClass(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName)) expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName)) 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 // Normally the Tailscale proxy pod would come up here and write its info
// into the secret. Simulate that, then verify reconcile again and verify // 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) { mustUpdate(t, fc, "default", "test", func(s *corev1.Service) {
s.Spec.Type = corev1.ServiceTypeClusterIP s.Spec.Type = corev1.ServiceTypeClusterIP
s.Spec.LoadBalancerClass = nil s.Spec.LoadBalancerClass = nil
})
mustUpdateStatus(t, fc, "default", "test", func(s *corev1.Service) {
// Fake client doesn't automatically delete the LoadBalancer status when // Fake client doesn't automatically delete the LoadBalancer status when
// changing away from the LoadBalancer type, we have to do // changing away from the LoadBalancer type, we have to do
// controller-manager's work by hand. // controller-manager's work by hand.
@ -187,7 +186,7 @@ func TestAnnotations(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName)) expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName)) expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", "")) expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
want := &corev1.Service{ want := &corev1.Service{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: "Service", Kind: "Service",
@ -284,7 +283,7 @@ func TestAnnotationIntoLB(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName)) expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName)) 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 // 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 // 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") expectReconciled(t, sr, "default", "test")
// None of the proxy machinery should have changed... // None of the proxy machinery should have changed...
expectEqual(t, fc, expectedHeadlessService(shortName)) 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. // ... but the service should have a LoadBalancer status.
want = &corev1.Service{ want = &corev1.Service{
@ -400,7 +399,7 @@ func TestLBIntoAnnotation(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName)) expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName)) 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 // Normally the Tailscale proxy pod would come up here and write its info
// into the secret. Simulate that, then verify reconcile again and verify // 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.Type = corev1.ServiceTypeClusterIP
s.Spec.LoadBalancerClass = nil s.Spec.LoadBalancerClass = nil
})
mustUpdateStatus(t, fc, "default", "test", func(s *corev1.Service) {
// Fake client doesn't automatically delete the LoadBalancer status when // Fake client doesn't automatically delete the LoadBalancer status when
// changing away from the LoadBalancer type, we have to do // changing away from the LoadBalancer type, we have to do
// controller-manager's work by hand. // controller-manager's work by hand.
@ -459,7 +456,7 @@ func TestLBIntoAnnotation(t *testing.T) {
expectReconciled(t, sr, "default", "test") expectReconciled(t, sr, "default", "test")
expectEqual(t, fc, expectedHeadlessService(shortName)) expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test", "")) expectEqual(t, fc, expectedSTS(shortName, fullName, "default-test"))
want = &corev1.Service{ want = &corev1.Service{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
@ -526,7 +523,7 @@ func TestCustomHostname(t *testing.T) {
expectEqual(t, fc, expectedSecret(fullName)) expectEqual(t, fc, expectedSecret(fullName))
expectEqual(t, fc, expectedHeadlessService(shortName)) expectEqual(t, fc, expectedHeadlessService(shortName))
expectEqual(t, fc, expectedSTS(shortName, fullName, "reindeer-flotilla", "")) expectEqual(t, fc, expectedSTS(shortName, fullName, "reindeer-flotilla"))
want := &corev1.Service{ want := &corev1.Service{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: "Service", Kind: "Service",
@ -585,51 +582,6 @@ func TestCustomHostname(t *testing.T) {
expectEqual(t, fc, want) 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 { func expectedSecret(name string) *corev1.Secret {
return &corev1.Secret{ return &corev1.Secret{
TypeMeta: metav1.TypeMeta{ 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{ return &appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: "StatefulSet", Kind: "StatefulSet",
@ -707,7 +659,6 @@ func expectedSTS(stsName, secretName, hostname, priorityClassName string) *appsv
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
ServiceAccountName: "proxies", ServiceAccountName: "proxies",
PriorityClassName: priorityClassName,
InitContainers: []corev1.Container{ InitContainers: []corev1.Container{
{ {
Name: "sysctler", Name: "sysctler",
@ -719,11 +670,11 @@ func expectedSTS(stsName, secretName, hostname, priorityClassName string) *appsv
}, },
}, },
}, },
Containers: []corev1.Container{ Containers: []v1.Container{
{ {
Name: "tailscale", Name: "tailscale",
Image: "tailscale/tailscale", Image: "tailscale/tailscale",
Env: []corev1.EnvVar{ Env: []v1.EnvVar{
{Name: "TS_USERSPACE", Value: "false"}, {Name: "TS_USERSPACE", Value: "false"},
{Name: "TS_AUTH_ONCE", Value: "true"}, {Name: "TS_AUTH_ONCE", Value: "true"},
{Name: "TS_DEST_IP", Value: "10.20.30.40"}, {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) { func expectEqual[T any, O ptrObject[T]](t *testing.T, client client.Client, want O) {
t.Helper() t.Helper()
got := O(new(T)) got := O(new(T))
@ -879,6 +815,7 @@ func (c *fakeTSClient) CreateKey(ctx context.Context, caps tailscale.KeyCapabili
k := &tailscale.Key{ k := &tailscale.Key{
ID: "key", ID: "key",
Created: time.Now(), Created: time.Now(),
Expires: time.Now().Add(24 * time.Hour),
Capabilities: caps, Capabilities: caps,
} }
return "secret-authkey", k, nil 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' { if buf[0] != 'S' {
p.errors.Add("upstream-bad-protocol", 1) 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{ tlsConf := &tls.Config{
ServerName: p.upstreamHost, ServerName: p.upstreamHost,

View File

@ -18,7 +18,6 @@ import (
"golang.org/x/net/dns/dnsmessage" "golang.org/x/net/dns/dnsmessage"
"inet.af/tcpproxy" "inet.af/tcpproxy"
"tailscale.com/client/tailscale" "tailscale.com/client/tailscale"
"tailscale.com/hostinfo"
"tailscale.com/net/netutil" "tailscale.com/net/netutil"
"tailscale.com/tsnet" "tailscale.com/tsnet"
"tailscale.com/types/nettype" "tailscale.com/types/nettype"
@ -26,7 +25,6 @@ import (
var ( var (
ports = flag.String("ports", "443", "comma-separated list of ports to proxy") 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") promoteHTTPS = flag.Bool("promote-https", true, "promote HTTP to HTTPS")
) )
@ -38,10 +36,7 @@ func main() {
log.Fatal("no ports") log.Fatal("no ports")
} }
hostinfo.SetApp("sniproxy")
var s server var s server
s.ts.Port = uint16(*wgPort)
defer s.ts.Close() defer s.ts.Close()
lc, err := s.ts.LocalClient() lc, err := s.ts.LocalClient()

View File

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

View File

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

View File

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

View File

@ -19,7 +19,6 @@ import (
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/net/netcheck" "tailscale.com/net/netcheck"
"tailscale.com/net/netmon"
"tailscale.com/net/portmapper" "tailscale.com/net/portmapper"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -46,15 +45,9 @@ var netcheckArgs struct {
} }
func runNetcheck(ctx context.Context, args []string) error { 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{ c := &netcheck.Client{
UDPBindAddr: envknob.String("TS_DEBUG_NETCHECK_UDP_BIND"), UDPBindAddr: envknob.String("TS_DEBUG_NETCHECK_UDP_BIND"),
PortMapper: portmapper.NewClient(logf, netMon, nil, nil), PortMapper: portmapper.NewClient(logger.WithPrefix(log.Printf, "portmap: "), nil, nil),
UseDNSCache: false, // always resolve, don't cache
} }
if netcheckArgs.verbose { if netcheckArgs.verbose {
c.Logf = logger.WithPrefix(log.Printf, "netcheck: ") c.Logf = logger.WithPrefix(log.Printf, "netcheck: ")
@ -103,6 +96,7 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
var err error var err error
switch netcheckArgs.format { switch netcheckArgs.format {
case "": case "":
break
case "json": case "json":
j, err = json.MarshalIndent(report, "", "\t") j, err = json.MarshalIndent(report, "", "\t")
case "json-line": 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())) return 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
} }
var nlDisableCmd = &ffcli.Command{ 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.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.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.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") fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
return fs return fs
})(), })(),

View File

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

View File

@ -13,13 +13,11 @@ import (
"fmt" "fmt"
"log" "log"
"net/netip" "net/netip"
"net/url"
"os" "os"
"os/signal" "os/signal"
"reflect" "reflect"
"runtime" "runtime"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
@ -28,9 +26,6 @@ import (
shellquote "github.com/kballard/go-shellquote" shellquote "github.com/kballard/go-shellquote"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
qrcode "github.com/skip2/go-qrcode" qrcode "github.com/skip2/go-qrcode"
"golang.org/x/oauth2/clientcredentials"
"tailscale.com/client/tailscale"
"tailscale.com/envknob"
"tailscale.com/health/healthmsg" "tailscale.com/health/healthmsg"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
@ -668,10 +663,6 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE
if err != nil { if err != nil {
return err return err
} }
authKey, err = resolveAuthKey(ctx, authKey, upArgs.advertiseTags)
if err != nil {
return err
}
if err := localClient.Start(ctx, ipn.Options{ if err := localClient.Start(ctx, ipn.Options{
AuthKey: authKey, AuthKey: authKey,
UpdatePrefs: prefs, UpdatePrefs: prefs,
@ -1111,96 +1102,3 @@ func anyPeerAdvertisingRoutes(st *ipnstate.Status) bool {
} }
return false 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"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/util/cmpx"
"tailscale.com/util/groupmember" "tailscale.com/util/groupmember"
"tailscale.com/version/distro" "tailscale.com/version/distro"
) )
@ -62,8 +61,6 @@ type tmplData struct {
TUNMode bool TUNMode bool
IsSynology bool IsSynology bool
DSMVersion int // 6 or 7, if IsSynology=true DSMVersion int // 6 or 7, if IsSynology=true
IsUnraid bool
UnraidToken string
IPNVersion 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 // urlOfListenAddr parses a given listen address into a formatted URL
func urlOfListenAddr(addr string) string { func urlOfListenAddr(addr string) string {
host, port, _ := net.SplitHostPort(addr) 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 // 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, TUNMode: st.TUN,
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"), IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
DSMVersion: distro.DSMVersion(), DSMVersion: distro.DSMVersion(),
IsUnraid: distro.Get() == distro.Unraid,
UnraidToken: os.Getenv("UNRAID_CSRF_TOKEN"),
IPNVersion: versionShort, IPNVersion: versionShort,
} }
exitNodeRouteV4 := netip.MustParsePrefix("0.0.0.0/0") 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> <a class="text-xs text-gray-500 hover:text-gray-600" href="{{ .LicensesURL }}">Open Source Licenses</a>
</footer> </footer>
<script>(function () { <script>(function () {
const advertiseExitNode = {{ .AdvertiseExitNode }}; const advertiseExitNode = {{.AdvertiseExitNode}};
const isUnraid = {{ .IsUnraid }};
const unraidCsrfToken = "{{ .UnraidToken }}";
let fetchingUrl = false; let fetchingUrl = false;
var data = { var data = {
AdvertiseRoutes: "{{ .AdvertiseRoutes }}", AdvertiseRoutes: "{{.AdvertiseRoutes}}",
AdvertiseExitNode: advertiseExitNode, AdvertiseExitNode: advertiseExitNode,
Reauthenticate: false, Reauthenticate: false,
ForceLogout: false ForceLogout: false
@ -143,27 +141,15 @@ function postData(e) {
} }
const nextUrl = new URL(window.location); const nextUrl = new URL(window.location);
nextUrl.search = nextParams.toString() 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(); const url = nextUrl.toString();
fetch(url, { fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
"Accept": "application/json", "Accept": "application/json",
"Content-Type": contentType, "Content-Type": "application/json",
}, },
body: body body: JSON.stringify(data)
}).then(res => res.json()).then(res => { }).then(res => res.json()).then(res => {
fetchingUrl = false; fetchingUrl = false;
const err = res["error"]; const err = res["error"];
@ -172,11 +158,7 @@ function postData(e) {
} }
const url = res["url"]; const url = res["url"];
if (url) { if (url) {
if(isUnraid) { document.location.href = url;
window.open(url, "_blank");
} else {
document.location.href = url;
}
} else { } else {
location.reload(); 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 from github.com/hdevalence/ed25519consensus
filippo.io/edwards25519/field from filippo.io/edwards25519 filippo.io/edwards25519/field from filippo.io/edwards25519
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket 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/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/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 from github.com/alexbrainman/sspi/negotiate+
W github.com/alexbrainman/sspi/internal/common 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 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/fxamacker/cbor/v2 from tailscale.com/tka
github.com/golang/groupcache/lru from tailscale.com/net/dnscache 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/google/uuid from tailscale.com/util/quarantine+
github.com/hdevalence/ed25519consensus from tailscale.com/tka github.com/hdevalence/ed25519consensus from tailscale.com/tka
L github.com/josharian/native from github.com/mdlayher/netlink+ 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 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/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
github.com/klauspost/compress/flate from nhooyr.io/websocket 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+ 💣 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 from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc 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 L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+ 💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli 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/scpd from github.com/tailscale/goupnp
github.com/tailscale/goupnp/soap 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 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/tcnksm/go-httpstat from tailscale.com/net/netcheck
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli 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 github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/derp+ 💣 go4.org/mem from tailscale.com/derp+
go4.org/netipx from tailscale.com/wgengine/filter go4.org/netipx from tailscale.com/wgengine/filter
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+ W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
gopkg.in/yaml.v2 from sigs.k8s.io/yaml 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 k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
nhooyr.io/websocket from tailscale.com/derp/derphttp+ nhooyr.io/websocket from tailscale.com/derp/derphttp+
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket 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/netcheck from tailscale.com/cmd/tailscale/cli
tailscale.com/net/neterror from tailscale.com/net/netcheck+ tailscale.com/net/neterror from tailscale.com/net/netcheck+
tailscale.com/net/netknob from tailscale.com/net/netns 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/netns from tailscale.com/derp/derphttp+
tailscale.com/net/netutil from tailscale.com/client/tailscale+ tailscale.com/net/netutil from tailscale.com/client/tailscale+
tailscale.com/net/packet from tailscale.com/wgengine/filter+ 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/portmapper from tailscale.com/net/netcheck+
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+ tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
tailscale.com/net/stun from tailscale.com/net/netcheck 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/tlsdial from tailscale.com/derp/derphttp+
tailscale.com/net/tsaddr from tailscale.com/net/interfaces+ tailscale.com/net/tsaddr from tailscale.com/net/interfaces+
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+ 💣 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/clientmetric from tailscale.com/net/netcheck+
tailscale.com/util/cloudenv from tailscale.com/net/dnscache+ tailscale.com/util/cloudenv from tailscale.com/net/dnscache+
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy 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 L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+ tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
tailscale.com/util/groupmember 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/httpm from tailscale.com/client/tailscale
tailscale.com/util/lineread from tailscale.com/net/interfaces+ 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/mak from tailscale.com/net/netcheck+
tailscale.com/util/multierr from tailscale.com/control/controlhttp+ tailscale.com/util/multierr from tailscale.com/control/controlhttp+
tailscale.com/util/must from tailscale.com/cmd/tailscale/cli 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/pbkdf2 from software.sslmate.com/src/go-pkcs12
golang.org/x/crypto/salsa20/salsa 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/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/exp/slices from tailscale.com/net/tsaddr+
golang.org/x/net/bpf from github.com/mdlayher/netlink+ golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+ 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/icmp from tailscale.com/net/ping
golang.org/x/net/idna from golang.org/x/net/http/httpguts+ 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/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 golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+ 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/sync/errgroup from tailscale.com/derp+
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from tailscale.com/net/netns+ 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+ bytes from bufio+
compress/flate from compress/gzip+ compress/flate from compress/gzip+
compress/gzip from net/http compress/gzip from net/http
compress/zlib from image/png+ compress/zlib from image/png
container/list from crypto/tls+ container/list from crypto/tls+
context from crypto/tls+ context from crypto/tls+
crypto from crypto/ecdsa+ 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 from crypto/tls+
crypto/x509/pkix from crypto/x509+ crypto/x509/pkix from crypto/x509+
database/sql/driver from github.com/google/uuid 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+ embed from tailscale.com/cmd/tailscale/cli+
encoding from encoding/json+ encoding from encoding/json+
encoding/asn1 from crypto/x509+ 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/fs from crypto/x509+
io/ioutil from golang.org/x/sys/cpu+ io/ioutil from golang.org/x/sys/cpu+
log from expvar+ log from expvar+
log/internal from log
math from compress/flate+ math from compress/flate+
math/big from crypto/dsa+ math/big from crypto/dsa+
math/bits from compress/flate+ math/bits from compress/flate+

View File

@ -23,10 +23,10 @@ import (
"tailscale.com/derp/derphttp" "tailscale.com/derp/derphttp"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/net/tshttpproxy" "tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/wgengine/monitor"
) )
var debugArgs struct { var debugArgs struct {
@ -42,7 +42,7 @@ var debugModeFunc = debugMode // so it can be addressable
func debugMode(args []string) error { func debugMode(args []string) error {
fs := flag.NewFlagSet("debug", flag.ExitOnError) fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.BoolVar(&debugArgs.ifconfig, "ifconfig", false, "If true, print network interface state") 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.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.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") 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, "", " ") j, _ := json.MarshalIndent(st, "", " ")
os.Stderr.Write(j) os.Stderr.Write(j)
} }
mon, err := netmon.New(log.Printf) mon, err := monitor.New(log.Printf)
if err != nil { if err != nil {
return err return err
} }
@ -84,10 +84,10 @@ func runMonitor(ctx context.Context, loop bool) error {
mon.RegisterChangeCallback(func(changed bool, st *interfaces.State) { mon.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
if !changed { if !changed {
log.Printf("Network monitor fired; no change") log.Printf("Link monitor fired; no change")
return return
} }
log.Printf("Network monitor fired. New state:") log.Printf("Link monitor fired. New state:")
dump(st) dump(st)
}) })
if loop { if loop {
@ -193,8 +193,8 @@ func checkDerp(ctx context.Context, derpRegion string) (err error) {
priv1 := key.NewNode() priv1 := key.NewNode()
priv2 := key.NewNode() priv2 := key.NewNode()
c1 := derphttp.NewRegionClient(priv1, log.Printf, nil, getRegion) c1 := derphttp.NewRegionClient(priv1, log.Printf, getRegion)
c2 := derphttp.NewRegionClient(priv2, log.Printf, nil, getRegion) c2 := derphttp.NewRegionClient(priv2, log.Printf, getRegion)
defer func() { defer func() {
if err != nil { if err != nil {
c1.Close() 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 from github.com/hdevalence/ed25519consensus
filippo.io/edwards25519/field from filippo.io/edwards25519 filippo.io/edwards25519/field from filippo.io/edwards25519
W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket 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/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/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 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/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 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 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/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/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/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+ 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/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/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/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/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/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 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 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/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/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 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/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/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 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/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/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 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/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/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/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+ 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 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/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/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 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 from github.com/dblohm7/wingoes/com
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled 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 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 from github.com/go-ole/go-ole/oleutil+
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+ L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
github.com/golang/groupcache/lru from tailscale.com/net/dnscache github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ 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 github.com/hdevalence/ed25519consensus from tailscale.com/tka
L 💣 github.com/illarion/gonotify from tailscale.com/net/dns L 💣 github.com/illarion/gonotify from tailscale.com/net/dns
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun 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/flate from nhooyr.io/websocket
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0 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/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/internal/snapref from github.com/klauspost/compress/zstd
github.com/klauspost/compress/zstd from tailscale.com/smallzstd github.com/klauspost/compress/zstd from tailscale.com/smallzstd
github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd 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/genetlink from tailscale.com/net/tstun
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+ L 💣 github.com/mdlayher/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/sdnotify from tailscale.com/util/systemd
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket 💣 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 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 from tailscale.com/ssh/tailssh
LD github.com/pkg/sftp/internal/encoding/ssh/filexfer from github.com/pkg/sftp LD github.com/pkg/sftp/internal/encoding/ssh/filexfer from github.com/pkg/sftp
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient 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/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 from tailscale.com/ipn/ipnlocal+
LD github.com/tailscale/golang-x-crypto/ssh/internal/bcrypt_pbkdf from github.com/tailscale/golang-x-crypto/ssh 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+ 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+ go4.org/netipx from tailscale.com/ipn/ipnlocal+
W 💣 golang.zx2c4.com/wintun from github.com/tailscale/wireguard-go/tun+ 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+ 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/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/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+ 💣 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/linewriter from gvisor.dev/gvisor/pkg/log
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+ 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/rand from gvisor.dev/gvisor/pkg/tcpip/network/hash+
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/bufferv2+ gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/bufferv2+
💣 gvisor.dev/gvisor/pkg/sleep from gvisor.dev/gvisor/pkg/tcpip/transport/tcp 💣 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/netcheck from tailscale.com/wgengine/magicsock
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+ tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
tailscale.com/net/netknob from tailscale.com/net/netns+ 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/netns from tailscale.com/derp/derphttp+
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnauth+ 💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnauth+
tailscale.com/net/netutil from tailscale.com/ipn/ipnlocal+ tailscale.com/net/netutil from tailscale.com/ipn/ipnlocal+
tailscale.com/net/packet from tailscale.com/net/tstun+ 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/portmapper from tailscale.com/net/netcheck+
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
tailscale.com/net/routetable from tailscale.com/doctor/routetable tailscale.com/net/routetable from tailscale.com/doctor/routetable
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
tailscale.com/net/sockstats from tailscale.com/control/controlclient+ tailscale.com/net/sockstats from tailscale.com/control/controlclient+
tailscale.com/net/stun from tailscale.com/net/netcheck+ 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/tlsdial from tailscale.com/control/controlclient+
tailscale.com/net/tsaddr from tailscale.com/ipn+ tailscale.com/net/tsaddr from tailscale.com/ipn+
tailscale.com/net/tsdial from tailscale.com/control/controlclient+ 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 LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
tailscale.com/tka from tailscale.com/ipn/ipnlocal+ tailscale.com/tka from tailscale.com/ipn/ipnlocal+
W tailscale.com/tsconst from tailscale.com/net/interfaces 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/mono from tailscale.com/net/tstun+
tailscale.com/tstime/rate from tailscale.com/wgengine/filter+ tailscale.com/tstime/rate from tailscale.com/wgengine/filter+
tailscale.com/tsweb/varz from tailscale.com/cmd/tailscaled 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/clientmetric from tailscale.com/control/controlclient+
tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+ tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+
LW tailscale.com/util/cmpver from tailscale.com/net/dns+ 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+ 💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+ L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+
tailscale.com/util/dnsname from tailscale.com/hostinfo+ 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/hashx from tailscale.com/util/deephash
tailscale.com/util/httpm from tailscale.com/client/tailscale+ tailscale.com/util/httpm from tailscale.com/client/tailscale+
tailscale.com/util/lineread from tailscale.com/hostinfo+ 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/mak from tailscale.com/control/controlclient+
tailscale.com/util/multierr from tailscale.com/control/controlclient+ tailscale.com/util/multierr from tailscale.com/control/controlclient+
tailscale.com/util/must from tailscale.com/logpolicy 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/capture from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+ 💣 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/netlog from tailscale.com/wgengine
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+ 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+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+ 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/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/exp/slices from tailscale.com/ipn/ipnlocal+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+ golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from net+ 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+ bytes from bufio+
compress/flate from compress/gzip+ compress/flate from compress/gzip+
compress/gzip from golang.org/x/net/http2+ 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/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
container/list from crypto/tls+ container/list from crypto/tls+
context 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/tls from github.com/tcnksm/go-httpstat+
crypto/x509 from crypto/tls+ crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+ 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+ embed from tailscale.com+
encoding from encoding/json+ encoding from encoding/json+
encoding/asn1 from crypto/x509+ 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+ flag from net/http/httptest+
fmt from compress/flate+ fmt from compress/flate+
hash from crypto+ hash from crypto+
hash/adler32 from tailscale.com/ipn/ipnlocal+ hash/adler32 from tailscale.com/ipn/ipnlocal
hash/crc32 from compress/gzip+ hash/crc32 from compress/gzip+
hash/fnv from tailscale.com/wgengine/magicsock+ hash/fnv from tailscale.com/wgengine/magicsock+
hash/maphash from go4.org/mem 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/fs from crypto/x509+
io/ioutil from github.com/godbus/dbus/v5+ io/ioutil from github.com/godbus/dbus/v5+
log from expvar+ log from expvar+
log/internal from log
LD log/syslog from tailscale.com/ssh/tailssh LD log/syslog from tailscale.com/ssh/tailssh
math from compress/flate+ math from compress/flate+
math/big from crypto/dsa+ math/big from crypto/dsa+

View File

@ -18,7 +18,7 @@ import (
func configureTaildrop(logf logger.Logf, lb *ipnlocal.LocalBackend) { func configureTaildrop(logf logger.Logf, lb *ipnlocal.LocalBackend) {
dg := distro.Get() dg := distro.Get()
switch dg { 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 if they have a "Taildrop" share.
// See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319 // See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319
path, err := findTaildropDir(dg) path, err := findTaildropDir(dg)
@ -42,8 +42,6 @@ func findTaildropDir(dg distro.Distro) (string, error) {
return findTrueNASTaildropDir(name) return findTrueNASTaildropDir(name)
case distro.QNAP: case distro.QNAP:
return findQnapTaildropDir(name) return findQnapTaildropDir(name)
case distro.Unraid:
return findUnraidTaildropDir(name)
} }
return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg) 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) 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/logtail"
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/dnsfallback" "tailscale.com/net/dnsfallback"
"tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/proxymux" "tailscale.com/net/proxymux"
"tailscale.com/net/socks5" "tailscale.com/net/socks5"
@ -50,7 +49,6 @@ import (
"tailscale.com/safesocket" "tailscale.com/safesocket"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/syncs" "tailscale.com/syncs"
"tailscale.com/tsd"
"tailscale.com/tsweb/varz" "tailscale.com/tsweb/varz"
"tailscale.com/types/flagtype" "tailscale.com/types/flagtype"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -61,6 +59,7 @@ import (
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/version/distro" "tailscale.com/version/distro"
"tailscale.com/wgengine" "tailscale.com/wgengine"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/netstack" "tailscale.com/wgengine/netstack"
"tailscale.com/wgengine/router" "tailscale.com/wgengine/router"
) )
@ -330,19 +329,7 @@ var logPol *logpolicy.Policy
var debugMux *http.ServeMux var debugMux *http.ServeMux
func run() error { func run() error {
var logf logger.Logf = log.Printf pol := logpolicy.New(logtail.CollectionNode)
sys := new(tsd.System)
netMon, err := netmon.New(func(format string, args ...any) {
logf(format, args...)
})
if err != nil {
return fmt.Errorf("netmon.New: %w", err)
}
sys.Set(netMon)
pol := logpolicy.New(logtail.CollectionNode, netMon)
pol.SetVerbosityLevel(args.verbose) pol.SetVerbosityLevel(args.verbose)
logPol = pol logPol = pol
defer func() { defer func() {
@ -366,6 +353,7 @@ func run() error {
return nil return nil
} }
var logf logger.Logf = log.Printf
if envknob.Bool("TS_DEBUG_MEMORY") { if envknob.Bool("TS_DEBUG_MEMORY") {
logf = logger.RusagePrefixLog(logf) logf = logger.RusagePrefixLog(logf)
} }
@ -391,10 +379,10 @@ func run() error {
debugMux = newDebugMux() 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) ln, err := safesocket.Listen(args.socketpath)
if err != nil { if err != nil {
return fmt.Errorf("safesocket.Listen: %v", err) 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 { if debugMux != nil {
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus) debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)
} }
@ -438,7 +426,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
return return
} }
} }
lb, err := getLocalBackend(ctx, logf, logID, sys) lb, err := getLocalBackend(ctx, logf, logID)
if err == nil { if err == nil {
logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond)) logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond))
srv.SetLocalBackend(lb) srv.SetLocalBackend(lb)
@ -462,28 +450,35 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
return nil 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 { if logPol != nil {
logPol.Logtail.SetNetMon(sys.NetMon.Get()) logPol.Logtail.SetLinkMonitor(linkMon)
} }
socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr) socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr)
dialer := &tsdial.Dialer{Logf: logf} // mutated below (before used) dialer := &tsdial.Dialer{Logf: logf} // mutated below (before used)
sys.Set(dialer) e, onlyNetstack, err := createEngine(logf, linkMon, dialer)
onlyNetstack, err := createEngine(logf, sys)
if err != nil { if err != nil {
return nil, fmt.Errorf("createEngine: %w", err) 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 debugMux != nil {
if ms, ok := sys.MagicSock.GetOK(); ok { if ig, ok := e.(wgengine.InternalsGetter); ok {
debugMux.HandleFunc("/debug/magicsock", ms.ServeHTTPDebug) if _, mc, _, ok := ig.GetInternals(); ok {
debugMux.HandleFunc("/debug/magicsock", mc.ServeHTTPDebug)
}
} }
go runDebugServer(debugMux, args.debug) go runDebugServer(debugMux, args.debug)
} }
ns, err := newNetstack(logf, sys) ns, err := newNetstack(logf, dialer, e)
if err != nil { if err != nil {
return nil, fmt.Errorf("newNetstack: %w", err) 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() ns.ProcessSubnets = onlyNetstack || handleSubnetsInNetstack()
if onlyNetstack { if onlyNetstack {
e := sys.Engine.Get()
dialer.UseNetstackForIP = func(ip netip.Addr) bool { dialer.UseNetstackForIP = func(ip netip.Addr) bool {
_, ok := e.PeerForIP(ip) _, ok := e.PeerForIP(ip)
return ok return ok
@ -522,15 +516,16 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
tshttpproxy.SetSelfProxy(addrs...) tshttpproxy.SetSelfProxy(addrs...)
} }
e = wgengine.NewWatchdog(e)
opts := ipnServerOpts() opts := ipnServerOpts()
store, err := store.New(logf, statePathOrDefault()) store, err := store.New(logf, statePathOrDefault())
if err != nil { if err != nil {
return nil, fmt.Errorf("store.New: %w", err) 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 { if err != nil {
return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err) 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) lb.SetLogFlusher(logPol.Logtail.StartFlush)
} }
if root := lb.TailscaleVarRoot(); root != "" { 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) { lb.SetDecompressor(func() (controlclient.Decompressor, error) {
return smallzstd.NewDecoder(nil) 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 // onlyNetstack is true if the user has explicitly requested that we use netstack
// for all networking. // 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 == "" { if args.tunname == "" {
return false, errors.New("no --tun value specified") return nil, false, errors.New("no --tun value specified")
} }
var errs []error var errs []error
for _, name := range strings.Split(args.tunname, ",") { for _, name := range strings.Split(args.tunname, ",") {
logf("wgengine.NewUserspaceEngine(tun %q) ...", name) logf("wgengine.NewUserspaceEngine(tun %q) ...", name)
onlyNetstack, err = tryEngine(logf, sys, name) e, onlyNetstack, err = tryEngine(logf, linkMon, dialer, name)
if err == nil { if err == nil {
return onlyNetstack, nil return e, onlyNetstack, nil
} }
logf("wgengine.NewUserspaceEngine(tun %q) error: %v", name, err) logf("wgengine.NewUserspaceEngine(tun %q) error: %v", name, err)
errs = append(errs, err) errs = append(errs, err)
} }
return false, multierr.New(errs...) return nil, false, multierr.New(errs...)
} }
// handleSubnetsInNetstack reports whether netstack should handle subnet routers // handleSubnetsInNetstack reports whether netstack should handle subnet routers
@ -595,23 +590,21 @@ func handleSubnetsInNetstack() bool {
var tstunNew = tstun.New 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{ conf := wgengine.Config{
ListenPort: args.port, ListenPort: args.port,
NetMon: sys.NetMon.Get(), LinkMonitor: linkMon,
Dialer: sys.Dialer.Get(), Dialer: dialer,
SetSubsystem: sys.Set,
} }
onlyNetstack = name == "userspace-networking" onlyNetstack = name == "userspace-networking"
netstackSubnetRouter := onlyNetstack // but mutated later on some platforms
netns.SetEnabled(!onlyNetstack) netns.SetEnabled(!onlyNetstack)
if args.birdSocketPath != "" && createBIRDClient != nil { if args.birdSocketPath != "" && createBIRDClient != nil {
log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath) log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath)
conf.BIRDClient, err = createBIRDClient(args.birdSocketPath) conf.BIRDClient, err = createBIRDClient(args.birdSocketPath)
if err != nil { if err != nil {
return false, fmt.Errorf("createBIRDClient: %w", err) return nil, false, fmt.Errorf("createBIRDClient: %w", err)
} }
} }
if onlyNetstack { 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. // TODO(bradfitz): add a Synology-specific DNS manager.
conf.DNS, err = dns.NewOSConfigurator(logf, "") // empty interface name conf.DNS, err = dns.NewOSConfigurator(logf, "") // empty interface name
if err != nil { if err != nil {
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err) return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
} }
} }
} else { } else {
dev, devName, err := tstunNew(logf, name) dev, devName, err := tstunNew(logf, name)
if err != nil { if err != nil {
tstun.Diagnose(logf, name, err) 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 conf.Tun = dev
if strings.HasPrefix(name, "tap:") { if strings.HasPrefix(name, "tap:") {
conf.IsTAP = true conf.IsTAP = true
e, err := wgengine.NewUserspaceEngine(logf, conf) e, err := wgengine.NewUserspaceEngine(logf, conf)
if err != nil { return e, false, err
return false, err
}
sys.Set(e)
return false, err
} }
r, err := router.New(logf, dev, sys.NetMon.Get()) r, err := router.New(logf, dev, linkMon)
if err != nil { if err != nil {
dev.Close() 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) d, err := dns.NewOSConfigurator(logf, devName)
if err != nil { if err != nil {
dev.Close() dev.Close()
r.Close() r.Close()
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err) return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
} }
conf.DNS = d conf.DNS = d
conf.Router = r conf.Router = r
if handleSubnetsInNetstack() { if handleSubnetsInNetstack() {
conf.Router = netstack.NewSubnetRouterWrapper(conf.Router) 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 { if err != nil {
return onlyNetstack, err return nil, onlyNetstack, err
} }
e = wgengine.NewWatchdog(e) return e, onlyNetstack, nil
sys.Set(e)
sys.NetstackRouter.Set(netstackSubnetRouter)
return onlyNetstack, nil
} }
func newDebugMux() *http.ServeMux { 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) { func newNetstack(logf logger.Logf, dialer *tsdial.Dialer, e wgengine.Engine) (*netstack.Impl, error) {
return netstack.Create(logf, sys.Tun.Get(), sys.Engine.Get(), sys.MagicSock.Get(), sys.Dialer.Get(), sys.DNSManager.Get()) 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 // mustStartProxyListeners creates listeners for local SOCKS and HTTP

View File

@ -45,9 +45,7 @@ import (
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/logtail/backoff" "tailscale.com/logtail/backoff"
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/net/tstun" "tailscale.com/net/tstun"
"tailscale.com/tsd"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/logid" "tailscale.com/types/logid"
"tailscale.com/util/winutil" "tailscale.com/util/winutil"
@ -126,10 +124,6 @@ var syslogf logger.Logf = logger.Discard
// At this point we're still the parent process that // At this point we're still the parent process that
// Windows started. // Windows started.
func runWindowsService(pol *logpolicy.Policy) error { func runWindowsService(pol *logpolicy.Policy) error {
go func() {
winutil.LogSupportInfo(log.Printf)
}()
if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 { if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 {
syslog, err := eventlog.Open(serviceName) syslog, err := eventlog.Open(serviceName)
if err == nil { 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) publicLogID, _ := logid.ParsePublicID(logID)
err = startIPNServer(ctx, log.Printf, publicLogID, sys) err := startIPNServer(ctx, log.Printf, publicLogID)
if err != nil { if err != nil {
log.Fatalf("ipnserver: %v", err) log.Fatalf("ipnserver: %v", err)
} }

View File

@ -7,20 +7,16 @@
package flakytest package flakytest
import ( import (
"fmt"
"os" "os"
"regexp" "regexp"
"testing" "testing"
) )
// FlakyTestLogMessage is a sentinel value that is printed to stderr when a // InTestWrapper returns whether or not this binary is running under our test
// flaky test is marked. This is used by cmd/testwrapper to detect flaky tests // wrapper.
// and retry them. func InTestWrapper() bool {
const FlakyTestLogMessage = "flakytest: this is a known flaky test" return os.Getenv("TS_IN_TESTWRAPPER") != ""
}
// 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"
var issueRegexp = regexp.MustCompile(`\Ahttps://github\.com/tailscale/[a-zA-Z0-9_.-]+/issues/\d+\z`) 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) t.Fatalf("bad issue format: %q", issue)
} }
fmt.Fprintln(os.Stderr, FlakyTestLogMessage) // sentinel value for testwrapper if !InTestWrapper() {
t.Logf("flakytest: issue tracking this flaky test: %s", issue) 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 package flakytest
import ( import "testing"
"os"
"testing"
)
func TestIssueFormat(t *testing.T) { func TestIssueFormat(t *testing.T) {
testCases := []struct { 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 // Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
// testwrapper is a wrapper for retrying flaky tests. It is an alternative to // testwrapper is a wrapper for retrying flaky tests, using the -exec flag of
// `go test` and re-runs failed marked flaky tests (using the flakytest pkg). It // 'go test'. Tests that are flaky can use the 'flakytest' subpackage to mark
// takes different arguments than go test and requires the first positional // themselves as flaky and be retried on failure.
// argument to be the pattern to test.
package main package main
import ( import (
"bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"flag"
"fmt"
"io"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"sort"
"strings"
"time"
"golang.org/x/exp/maps"
"tailscale.com/cmd/testwrapper/flakytest"
) )
const maxAttempts = 3 const (
retryStatus = 123
type testAttempt struct { maxIterations = 3
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
}
func main() { func main() {
ctx := context.Background() 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 log.SetPrefix("testwrapper: ")
// for a test. We don't need to parse any other flags, so we just use the if !debug {
// flag package to parse the -v flag and then pass the rest of the args log.SetFlags(0)
// 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
} }
toRun := []*nextRun{ for i := 1; i <= maxIterations; i++ {
{ if i > 1 {
tests: []*packageTests{{pattern: pattern}}, log.Printf("retrying flaky tests (%d of %d)", i, maxIterations)
attempt: 1, }
}, cmd := exec.CommandContext(ctx, os.Args[1], os.Args[2:]...)
} cmd.Stdout = os.Stdout
printPkgOutcome := func(pkg, outcome string, attempt int) { cmd.Stderr = os.Stderr
if outcome == "skip" { cmd.Env = append(os.Environ(), "TS_IN_TESTWRAPPER=1")
fmt.Printf("?\t%s [skipped/no tests] \n", pkg) err := cmd.Run()
if err == nil {
return 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 exitErr *exec.ExitError
var thisRun *nextRun if !errors.As(err, &exitErr) {
thisRun, toRun = toRun[0], toRun[1:] if debug {
log.Printf("error isn't an ExitError")
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
}
} }
}
if failed {
fmt.Println("\n\nNot retrying flaky tests because non-flaky tests failed.")
os.Exit(1) 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/safesocket"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/wgengine" "tailscale.com/wgengine"
"tailscale.com/wgengine/netstack" "tailscale.com/wgengine/netstack"
"tailscale.com/words" "tailscale.com/words"
@ -47,7 +46,7 @@ import (
var ControlURL = ipn.DefaultControlURL var ControlURL = ipn.DefaultControlURL
func main() { 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 { if len(args) != 1 {
log.Fatal("Usage: newIPN(config)") log.Fatal("Usage: newIPN(config)")
return nil return nil
@ -97,19 +96,19 @@ func newIPN(jsConfig js.Value) map[string]any {
logtail := logtail.NewLogger(c, log.Printf) logtail := logtail.NewLogger(c, log.Printf)
logf := logtail.Logf logf := logtail.Logf
sys := new(tsd.System)
sys.Set(store)
dialer := &tsdial.Dialer{Logf: logf} dialer := &tsdial.Dialer{Logf: logf}
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Dialer: dialer, Dialer: dialer,
SetSubsystem: sys.Set,
}) })
if err != nil { if err != nil {
log.Fatal(err) 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 { if err != nil {
log.Fatalf("netstack.Create: %v", err) 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) { dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
return ns.DialContextTCP(ctx, dst) return ns.DialContextTCP(ctx, dst)
} }
sys.NetstackRouter.Set(true)
logid := lpc.PublicID logid := lpc.PublicID
srv := ipnserver.New(logf, logid, nil /* no netMon */) srv := ipnserver.New(logf, logid)
lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginEphemeral) lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, controlclient.LoginEphemeral)
if err != nil { if err != nil {
log.Fatalf("ipnlocal.NewLocalBackend: %v", err) log.Fatalf("ipnlocal.NewLocalBackend: %v", err)
} }
@ -148,7 +146,7 @@ func newIPN(jsConfig js.Value) map[string]any {
} }
return 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 { if len(args) != 1 {
log.Fatal(`Usage: run({ log.Fatal(`Usage: run({
notifyState(state: int): void, notifyState(state: int): void,
@ -161,7 +159,7 @@ func newIPN(jsConfig js.Value) map[string]any {
jsIPN.run(args[0]) jsIPN.run(args[0])
return nil 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 { if len(args) != 0 {
log.Printf("Usage: login()") log.Printf("Usage: login()")
return nil return nil
@ -169,7 +167,7 @@ func newIPN(jsConfig js.Value) map[string]any {
jsIPN.login() jsIPN.login()
return nil 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 { if len(args) != 0 {
log.Printf("Usage: logout()") log.Printf("Usage: logout()")
return nil return nil
@ -177,7 +175,7 @@ func newIPN(jsConfig js.Value) map[string]any {
jsIPN.logout() jsIPN.logout()
return nil 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 { if len(args) != 3 {
log.Printf("Usage: ssh(hostname, userName, termConfig)") log.Printf("Usage: ssh(hostname, userName, termConfig)")
return nil return nil
@ -187,7 +185,7 @@ func newIPN(jsConfig js.Value) map[string]any {
args[1].String(), args[1].String(),
args[2]) 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 { if len(args) != 1 {
log.Printf("Usage: fetch(url)") log.Printf("Usage: fetch(url)")
return nil return nil
@ -336,10 +334,10 @@ func (i *jsIPN) ssh(host, username string, termConfig js.Value) map[string]any {
go jsSSHSession.Run() go jsSSHSession.Run()
return map[string]any{ 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 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() rows := args[0].Int()
cols := args[1].Int() cols := args[1].Int()
return jsSSHSession.Resize(rows, cols) != nil return jsSSHSession.Resize(rows, cols) != nil
@ -428,7 +426,7 @@ func (s *jsSSHSession) Run() {
session.Stdout = termWriter{writeFn} session.Stdout = termWriter{writeFn}
session.Stderr = 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() input := args[0].String()
_, err := stdin.Write([]byte(input)) _, err := stdin.Write([]byte(input))
if err != nil { if err != nil {
@ -498,7 +496,7 @@ func (i *jsIPN) fetch(url string) js.Value {
return map[string]any{ return map[string]any{
"status": res.StatusCode, "status": res.StatusCode,
"statusText": res.Status, "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) { return makePromise(func() (any, error) {
defer res.Body.Close() defer res.Body.Close()
buf := new(bytes.Buffer) 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 // f is run on a goroutine and its return value is used to resolve the promise
// (or reject it if an error is returned). // (or reject it if an error is returned).
func makePromise(f func() (any, error)) js.Value { 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] resolve := args[0]
reject := args[1] reject := args[1]
go func() { go func() {

View File

@ -9,7 +9,7 @@ import (
"net/netip" "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 { type StructWithoutPtrs struct {
Int int Int int
@ -61,8 +61,3 @@ type StructWithSlices struct {
type OnlyGetClone struct { type OnlyGetClone struct {
SinViewerPorFavor bool SinViewerPorFavor bool
} }
type StructWithEmbedded struct {
A *StructWithPtrs
StructWithSlices
}

View File

@ -211,22 +211,3 @@ func (src *OnlyGetClone) Clone() *OnlyGetClone {
var _OnlyGetCloneCloneNeedsRegeneration = OnlyGetClone(struct { var _OnlyGetCloneCloneNeedsRegeneration = OnlyGetClone(struct {
SinViewerPorFavor bool 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" "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. // View returns a readonly view of StructWithPtrs.
func (p *StructWithPtrs) View() StructWithPtrsView { func (p *StructWithPtrs) View() StructWithPtrsView {
@ -325,59 +325,3 @@ var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
Prefixes []netip.Prefix Prefixes []netip.Prefix
Data []byte 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. // bufPool holds the temporary buffers for Conn.Read & Write.
var bufPool = &sync.Pool{ var bufPool = &sync.Pool{
New: func() any { New: func() interface{} {
return new(maxMsgBuffer) return new(maxMsgBuffer)
}, },
} }

View File

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

View File

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

View File

@ -19,12 +19,9 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
"tailscale.com/control/controlbase" "tailscale.com/control/controlbase"
"tailscale.com/control/controlhttp" "tailscale.com/control/controlhttp"
"tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/util/mak" "tailscale.com/util/mak"
"tailscale.com/util/multierr" "tailscale.com/util/multierr"
"tailscale.com/util/singleflight" "tailscale.com/util/singleflight"
@ -159,7 +156,6 @@ type NoiseClient struct {
sfDial singleflight.Group[struct{}, *noiseConn] sfDial singleflight.Group[struct{}, *noiseConn]
dialer *tsdial.Dialer dialer *tsdial.Dialer
dnsCache *dnscache.Resolver
privKey key.MachinePrivate privKey key.MachinePrivate
serverPubKey key.MachinePublic serverPubKey key.MachinePublic
host string // the host part of serverURL host string // the host part of serverURL
@ -171,9 +167,6 @@ type NoiseClient struct {
// be nil. // be nil.
dialPlan func() *tailcfg.ControlDialPlan dialPlan func() *tailcfg.ControlDialPlan
logf logger.Logf
netMon *netmon.Monitor
// mu only protects the following variables. // mu only protects the following variables.
mu sync.Mutex mu sync.Mutex
last *noiseConn // or nil last *noiseConn // or nil
@ -181,39 +174,12 @@ type NoiseClient struct {
connPool map[int]*noiseConn // active connections not yet closed; see noiseConn.Close 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. // NewNoiseClient returns a new noiseClient for the provided server and machine key.
// serverURL is of the form https://<host>:<port> (no trailing slash). // 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 // dialPlan may be nil
func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) { func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string, dialer *tsdial.Dialer, dialPlan func() *tailcfg.ControlDialPlan) (*NoiseClient, error) {
u, err := url.Parse(opts.ServerURL) u, err := url.Parse(serverURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -233,18 +199,14 @@ func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
httpPort = "80" httpPort = "80"
httpsPort = "443" httpsPort = "443"
} }
np := &NoiseClient{ np := &NoiseClient{
serverPubKey: opts.ServerPubKey, serverPubKey: serverPubKey,
privKey: opts.PrivKey, privKey: privKey,
host: u.Hostname(), host: u.Hostname(),
httpPort: httpPort, httpPort: httpPort,
httpsPort: httpsPort, httpsPort: httpsPort,
dialer: opts.Dialer, dialer: dialer,
dnsCache: opts.DNSCache, dialPlan: dialPlan,
dialPlan: opts.DialPlan,
logf: opts.Logf,
netMon: opts.NetMon,
} }
// Create the HTTP/2 Transport using a net/http.Transport // 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") 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) { func (nc *NoiseClient) getConn(ctx context.Context) (*noiseConn, error) {
nc.mu.Lock() nc.mu.Lock()
if last := nc.last; last != nil && last.canTakeNewRequest() { if last := nc.last; last != nil && last.canTakeNewRequest() {
@ -314,35 +257,11 @@ func (nc *NoiseClient) getConn(ctx context.Context) (*noiseConn, error) {
} }
nc.mu.Unlock() nc.mu.Unlock()
for { conn, err, _ := nc.sfDial.Do(struct{}{}, nc.dial)
// We singeflight the dial to avoid making multiple connections, however if err != nil {
// that means that we can't simply cancel the dial if the context is return nil, err
// 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()
} }
return conn, nil
} }
func (nc *NoiseClient) RoundTrip(req *http.Request) (*http.Response, error) { 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 // dial opens a new connection to tailcontrol, fetching the server noise key
// if not cached. // if not cached.
func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) { func (nc *NoiseClient) dial() (*noiseConn, error) {
nc.mu.Lock() nc.mu.Lock()
connID := nc.nextID connID := nc.nextID
nc.nextID++ nc.nextID++
@ -435,7 +354,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
} }
timeout := time.Duration(timeoutSec * float64(time.Second)) timeout := time.Duration(timeoutSec * float64(time.Second))
ctx, cancel := context.WithTimeout(ctx, timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
clientConn, err := (&controlhttp.Dialer{ clientConn, err := (&controlhttp.Dialer{
@ -446,10 +365,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
ControlKey: nc.serverPubKey, ControlKey: nc.serverPubKey,
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion), ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
Dialer: nc.dialer.SystemDial, Dialer: nc.dialer.SystemDial,
DNSCache: nc.dnsCache,
DialPlan: dialPlan, DialPlan: dialPlan,
Logf: nc.logf,
NetMon: nc.netMon,
}).Dial(ctx) }).Dial(ctx)
if err != nil { if err != nil {
return nil, err return nil, err

View File

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

View File

@ -374,22 +374,6 @@ func (a *Dialer) dialURL(ctx context.Context, u *url.URL, addr netip.Addr) (*Cli
}, nil }, 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 // 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 // is valid, then no DNS is used and the connection will be made to the
// provided address. // provided address.
@ -405,10 +389,14 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr,
SingleHostStaticResult: []netip.Addr{addr}, SingleHostStaticResult: []netip.Addr{addr},
SingleHost: u.Hostname(), SingleHost: u.Hostname(),
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
NetMon: a.NetMon,
} }
} else { } 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 var dialer dnscache.DialContextFunc

View File

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

View File

@ -583,20 +583,19 @@ func TestDialPlan(t *testing.T) {
}}, }},
want: goodAddr, want: goodAddr,
}, },
// TODO(#8442): fix this test {
// { name: "multiple-priority-fast-path",
// name: "multiple-priority-fast-path", plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{
// plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{ // Dials some good IPs and our bad one (which
// // Dials some good IPs and our bad one (which // hangs forever), which then hits the fast
// // hangs forever), which then hits the fast // path where we bail without waiting.
// // path where we bail without waiting. {IP: brokenAddr, Priority: 1, DialTimeoutSec: 10},
// {IP: brokenAddr, Priority: 1, DialTimeoutSec: 10}, {IP: goodAddr, Priority: 1, DialTimeoutSec: 10},
// {IP: goodAddr, Priority: 1, DialTimeoutSec: 10}, {IP: other2Addr, Priority: 1, DialTimeoutSec: 10},
// {IP: other2Addr, Priority: 1, DialTimeoutSec: 10}, {IP: otherAddr, Priority: 2, DialTimeoutSec: 10},
// {IP: otherAddr, Priority: 2, DialTimeoutSec: 10}, }},
// }}, want: otherAddr,
// want: otherAddr, },
// },
{ {
name: "multiple-priority-slow-path", name: "multiple-priority-slow-path",
plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{ 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) { switch set := set.(type) {
case nil: case nil:
s.clients[c.key] = singleClient{c} s.clients[c.key] = singleClient{c}
c.debugLogf("register single client") c.debug("register single client")
case singleClient: case singleClient:
s.dupClientKeys.Add(1) s.dupClientKeys.Add(1)
s.dupClientConns.Add(2) // both old and new count s.dupClientConns.Add(2) // both old and new count
@ -514,7 +514,7 @@ func (s *Server) registerClient(c *sclient) {
}, },
sendHistory: []*sclient{old}, sendHistory: []*sclient{old},
} }
c.debugLogf("register duplicate client") c.debug("register duplicate client")
case *dupClientSet: case *dupClientSet:
s.dupClientConns.Add(1) // the gauge s.dupClientConns.Add(1) // the gauge
s.dupClientConnTotal.Add(1) // the counter s.dupClientConnTotal.Add(1) // the counter
@ -522,7 +522,7 @@ func (s *Server) registerClient(c *sclient) {
set.set[c] = true set.set[c] = true
set.last = c set.last = c
set.sendHistory = append(set.sendHistory, 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 { if _, ok := s.clientsMesh[c.key]; !ok {
@ -555,7 +555,7 @@ func (s *Server) unregisterClient(c *sclient) {
case nil: case nil:
c.logf("[unexpected]; clients map is empty") c.logf("[unexpected]; clients map is empty")
case singleClient: case singleClient:
c.debugLogf("removed connection") c.logf("removed connection")
delete(s.clients, c.key) delete(s.clients, c.key)
if v, ok := s.clientsMesh[c.key]; ok && v == nil { if v, ok := s.clientsMesh[c.key]; ok && v == nil {
delete(s.clientsMesh, c.key) delete(s.clientsMesh, c.key)
@ -563,7 +563,7 @@ func (s *Server) unregisterClient(c *sclient) {
} }
s.broadcastPeerStateChangeLocked(c.key, false) s.broadcastPeerStateChangeLocked(c.key, false)
case *dupClientSet: case *dupClientSet:
c.debugLogf("removed duplicate client") c.debug("removed duplicate client")
if set.removeClient(c) { if set.removeClient(c) {
s.dupClientConns.Add(-1) s.dupClientConns.Add(-1)
} else { } else {
@ -712,12 +712,9 @@ func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, rem
if clientInfo != nil { if clientInfo != nil {
c.info = *clientInfo c.info = *clientInfo
if envknob.Bool("DERP_PROBER_DEBUG_LOGS") && clientInfo.IsProber { if envknob.Bool("DERP_PROBER_DEBUG_LOGS") && clientInfo.IsProber {
c.debug = true c.debugLogging = true
} }
} }
if s.debug {
c.debug = true
}
s.registerClient(c) s.registerClient(c)
defer s.unregisterClient(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) return c.run(ctx)
} }
func (s *Server) debugLogf(format string, v ...any) {
if s.debug {
s.logf(format, v...)
}
}
// for testing // for testing
var ( var (
timeSleep = time.Sleep timeSleep = time.Sleep
@ -753,20 +744,16 @@ func (c *sclient) run(ctx context.Context) error {
defer func() { defer func() {
cancelSender() cancelSender()
if err := grp.Wait(); err != nil && !c.s.isClosed() { if err := grp.Wait(); err != nil && !c.s.isClosed() {
if errors.Is(err, context.Canceled) { c.logf("sender failed: %v", err)
c.debugLogf("sender canceled by reader exiting")
} else {
c.logf("sender failed: %v", err)
}
} }
}() }()
for { for {
ft, fl, err := readFrameHeader(c.br) 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 err != nil {
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
c.debugLogf("read EOF") c.logf("read EOF")
return nil return nil
} }
if c.s.isClosed() { if c.s.isClosed() {
@ -923,7 +910,7 @@ func (c *sclient) handleFrameForwardPacket(ft frameType, fl uint32) error {
return nil 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{ return c.sendPkt(dst, pkt{
bs: contents, bs: contents,
@ -973,7 +960,7 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
if fwd != nil { if fwd != nil {
s.packetsForwardedOut.Add(1) s.packetsForwardedOut.Add(1)
err := fwd.ForwardPacket(c.key, dstKey, contents) 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 { if err != nil {
// TODO: // TODO:
return nil return nil
@ -987,10 +974,10 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
c.requestPeerGoneWriteLimited(dstKey, contents, PeerGoneReasonNotHere) c.requestPeerGoneWriteLimited(dstKey, contents, PeerGoneReasonNotHere)
} }
s.recordDrop(contents, c.key, dstKey, reason) 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 return nil
} }
c.debugLogf("SendPacket for %s, sending directly", dstKey.ShortString()) c.debug("SendPacket for %s, sending directly", dstKey.ShortString())
p := pkt{ p := pkt{
bs: contents, bs: contents,
@ -1000,8 +987,8 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
return c.sendPkt(dst, p) return c.sendPkt(dst, p)
} }
func (c *sclient) debugLogf(format string, v ...any) { func (c *sclient) debug(format string, v ...any) {
if c.debug { if c.debugLogging {
c.logf(format, v...) c.logf(format, v...)
} }
} }
@ -1024,8 +1011,7 @@ const (
func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, reason dropReason) { func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, reason dropReason) {
s.packetsDropped.Add(1) s.packetsDropped.Add(1)
s.packetsDroppedReasonCounters[reason].Add(1) s.packetsDroppedReasonCounters[reason].Add(1)
looksDisco := disco.LooksLikeDiscoWrapper(packetBytes) if disco.LooksLikeDiscoWrapper(packetBytes) {
if looksDisco {
s.packetsDroppedTypeDisco.Add(1) s.packetsDroppedTypeDisco.Add(1)
} else { } else {
s.packetsDroppedTypeOther.Add(1) 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()) msg := fmt.Sprintf("drop (%s) %s -> %s", srcKey.ShortString(), reason, dstKey.ShortString())
s.limitedLogf(msg) 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 { func (c *sclient) sendPkt(dst *sclient, p pkt) error {
@ -1056,13 +1044,13 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
select { select {
case <-dst.done: case <-dst.done:
s.recordDrop(p.bs, c.key, dstKey, dropReasonGoneDisconnected) 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 return nil
default: default:
} }
select { select {
case sendQueue <- p: case sendQueue <- p:
dst.debugLogf("sendPkt attempt %d enqueued", attempt) dst.debug("sendPkt attempt %d enqueued", attempt)
return nil return nil
default: 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 // contended queue with racing writers. Give up and tail-drop in
// this case to keep reader unblocked. // this case to keep reader unblocked.
s.recordDrop(p.bs, c.key, dstKey, dropReasonQueueTail) 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 return nil
} }
@ -1316,7 +1304,8 @@ type sclient struct {
canMesh bool // clientInfo had correct mesh token for inter-region routing canMesh bool // clientInfo had correct mesh token for inter-region routing
isDup atomic.Bool // whether more than 1 sclient for key is connected 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 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. // Owned by run, not thread-safe.
br *bufio.Reader 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.packetsSent.Add(1)
c.s.bytesSent.Add(int64(len(contents))) 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() c.setWriteDeadline()

View File

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

View File

@ -31,7 +31,6 @@ import (
"tailscale.com/derp" "tailscale.com/derp"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/net/dnscache" "tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/sockstats" "tailscale.com/net/sockstats"
"tailscale.com/net/tlsdial" "tailscale.com/net/tlsdial"
@ -40,7 +39,6 @@ import (
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/cmpx"
) )
// Client is a DERP-over-HTTP client. // Client is a DERP-over-HTTP client.
@ -57,7 +55,6 @@ type Client struct {
privateKey key.NodePrivate privateKey key.NodePrivate
logf logger.Logf 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) dialer func(ctx context.Context, network, addr string) (net.Conn, error)
// Either url or getRegion is non-nil: // 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. // NewRegionClient returns a new DERP-over-HTTP client. It connects lazily.
// To trigger a connection, use Connect. // 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, getRegion func() *tailcfg.DERPRegion) *Client {
func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmon.Monitor, getRegion func() *tailcfg.DERPRegion) *Client {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c := &Client{ c := &Client{
privateKey: privateKey, privateKey: privateKey,
logf: logf, logf: logf,
netMon: netMon,
getRegion: getRegion, getRegion: getRegion,
ctx: ctx, ctx: ctx,
cancelCtx: cancel, 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))) return c.dialer(ctx, "tcp", net.JoinHostPort(host, urlPort(c.url)))
} }
hostOrIP := host hostOrIP := host
dialer := netns.NewDialer(c.logf, c.netMon) dialer := netns.NewDialer(c.logf)
if c.DNSCache != nil { if c.DNSCache != nil {
ip, _, _, err := c.DNSCache.LookupIP(ctx, host) 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) { 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 // 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 // Start v4 dial
} }
} }
dst := cmpx.Or(dstPrimary, n.HostName) dst := dstPrimary
if dst == "" {
dst = n.HostName
}
port := "443" port := "443"
if n.DERPPort != 0 { if n.DERPPort != 0 {
port = fmt.Sprint(n.DERPPort) port = fmt.Sprint(n.DERPPort)

View File

@ -6,20 +6,22 @@ SA_NAME ?= tailscale
TS_KUBE_SECRET ?= tailscale TS_KUBE_SECRET ?= tailscale
rbac: rbac:
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" role.yaml @sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" role.yaml | kubectl apply -f -
@echo "---" @sed -e "s;{{SA_NAME}};$(SA_NAME);g" rolebinding.yaml | kubectl apply -f -
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" rolebinding.yaml @sed -e "s;{{SA_NAME}};$(SA_NAME);g" sa.yaml | kubectl apply -f -
@echo "---"
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" sa.yaml
sidecar: 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: 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: 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: 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 ```bash
export SA_NAME=tailscale export SA_NAME=tailscale
export TS_KUBE_SECRET=tailscale-auth export TS_KUBE_SECRET=tailscale-auth
make rbac | kubectl apply -f- make rbac
``` ```
### Sample Sidecar ### 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 1. Create and login to the sample nginx pod with a Tailscale sidecar
```bash ```bash
make sidecar | kubectl apply -f- make sidecar
# If not using an auth key, authenticate by grabbing the Login URL here: # If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs nginx ts-sidecar 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 1. Create and login to the sample nginx pod with a Tailscale sidecar
```bash ```bash
make userspace-sidecar | kubectl apply -f- make userspace-sidecar
# If not using an auth key, authenticate by grabbing the Login URL here: # If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs nginx ts-sidecar 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 1. Deploy the proxy pod
```bash ```bash
make proxy | kubectl apply -f- make proxy
# If not using an auth key, authenticate by grabbing the Login URL here: # If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs proxy kubectl logs proxy
``` ```
@ -133,7 +133,7 @@ the entire Kubernetes cluster network (assuming NetworkPolicies allow) over Tail
1. Deploy the subnet-router pod. 1. Deploy the subnet-router pod.
```bash ```bash
make subnet-router | kubectl apply -f- make subnet-router
# If not using an auth key, authenticate by grabbing the Login URL here: # If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs subnet-router kubectl logs subnet-router
``` ```

View File

@ -457,24 +457,13 @@ var applyDiskConfigErr error
// ApplyDiskConfigError returns the most recent result of ApplyDiskConfig. // ApplyDiskConfigError returns the most recent result of ApplyDiskConfig.
func ApplyDiskConfigError() error { return applyDiskConfigErr } func ApplyDiskConfigError() error { return applyDiskConfigErr }
// ApplyDiskConfig returns a platform-specific config file of environment // ApplyDiskConfig returns a platform-specific config file of environment keys/values and
// keys/values and applies them. On Linux and Unix operating systems, it's a // applies them. On Linux and Unix operating systems, it's a no-op and always returns nil.
// no-op and always returns nil. If no platform-specific config file is found, // If no platform-specific config file is found, it also returns nil.
// it also returns nil.
//
// It exists primarily for Windows and macOS to make it easy to apply
// environment variables to a running service in a way similar to modifying
// /etc/default/tailscaled on Linux.
// //
// It exists primarily for Windows to make it easy to apply environment variables to
// a running service in a way similar to modifying /etc/default/tailscaled on Linux.
// On Windows, you use %ProgramData%\Tailscale\tailscaled-env.txt instead. // On 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) { func ApplyDiskConfig() (err error) {
var f *os.File var f *os.File
defer func() { defer func() {
@ -523,15 +512,9 @@ func getPlatformEnvFile() string {
return "/etc/tailscale/tailscaled-env.txt" return "/etc/tailscale/tailscaled-env.txt"
} }
case "darwin": case "darwin":
if version.IsSandboxedMacOS() { // the two GUI variants (App Store or separate download) // TODO(bradfitz): figure this out. There are three ways to run
// This will be user-visible as ~/Library/Containers/$VARIANT/Data/tailscaled-env.txt // Tailscale on macOS (tailscaled, GUI App Store, GUI System Extension)
// where $VARIANT is "io.tailscale.ipn.macsys" for macsys (downloadable mac GUI builds) // and we should deal with all three.
// or "io.tailscale.ipn.macos.network-extension" for App Store builds.
return filepath.Join(os.Getenv("HOME"), "tailscaled-env.txt")
} else {
// Open source / homebrew variable, running tailscaled-on-macOS.
return "/etc/tailscale/tailscaled-env.txt"
}
} }
return "" return ""
} }

View File

@ -115,4 +115,4 @@
in in
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system); 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 go 1.20
require ( require (
filippo.io/mkcert v1.4.4 filippo.io/mkcert v1.4.3
github.com/Microsoft/go-winio v0.6.1 github.com/Microsoft/go-winio v0.6.0
github.com/akutz/memconn v0.1.0 github.com/akutz/memconn v0.1.0
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 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/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 v1.17.3
github.com/aws/aws-sdk-go-v2/config v1.18.22 github.com/aws/aws-sdk-go-v2/config v1.11.0
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.4
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.21.0
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.3 github.com/aws/aws-sdk-go-v2/service/ssm v1.35.0
github.com/coreos/go-iptables v0.6.0 github.com/coreos/go-iptables v0.6.0
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/creack/pty v1.1.18 github.com/creack/pty v1.1.17
github.com/dave/jennifer v1.6.1 github.com/dave/jennifer v1.4.1
github.com/dblohm7/wingoes v0.0.0-20230426155039-111c8c3b57c8 github.com/dblohm7/wingoes v0.0.0-20221124203957-6ac47ab19aa5
github.com/dsnet/try v0.0.3 github.com/dsnet/try v0.0.3
github.com/evanw/esbuild v0.14.53 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/fxamacker/cbor/v2 v2.4.0
github.com/go-json-experiment/json v0.0.0-20230321051131-ccbac49a6929 github.com/go-json-experiment/json v0.0.0-20221017203807-c5ed296b8c92
github.com/go-logr/zapr v1.2.4 github.com/go-logr/zapr v1.2.3
github.com/go-ole/go-ole v1.2.6 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/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-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/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/goreleaser/nfpm v1.10.3 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/iancoleman/strcase v0.2.0
github.com/illarion/gonotify v1.0.1 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/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/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/kortschak/wol v0.0.0-20200729010619-da482cc4850a
github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-isatty v0.0.18 github.com/mattn/go-isatty v0.0.14
github.com/mdlayher/genetlink v1.3.2 github.com/mdlayher/genetlink v1.2.0
github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/netlink v1.7.1
github.com/mdlayher/sdnotify v1.0.0 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/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/errors v0.9.1
github.com/pkg/sftp v1.13.5 github.com/pkg/sftp v1.13.4
github.com/prometheus/client_golang v1.15.1 github.com/prometheus/client_golang v1.14.0
github.com/prometheus/common v0.42.0 github.com/prometheus/common v0.41.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 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/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/mkctr v0.0.0-20220601142259-c0b937af2e89
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
github.com/tailscale/wireguard-go v0.0.0-20230410165232-af172621b4dd 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/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0 github.com/toqueteos/webbrowser v1.2.0
github.com/u-root/u-root v0.11.0 github.com/u-root/u-root v0.9.1-0.20230109201855-948a78c969ad
github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
go.uber.org/zap v1.24.0 go.uber.org/zap v1.21.0
go4.org/mem v0.0.0-20220726221520-4f986261bf13 go4.org/mem v0.0.0-20210711025021-927187094b94
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
golang.org/x/crypto v0.8.0 golang.org/x/crypto v0.6.0
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
golang.org/x/mod v0.10.0 golang.org/x/mod v0.7.0
golang.org/x/net v0.10.0 golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.7.0 golang.org/x/oauth2 v0.5.0
golang.org/x/sync v0.2.0 golang.org/x/sync v0.1.0
golang.org/x/sys v0.8.1-0.20230609144347-5059a07aa46a golang.org/x/sys v0.5.1-0.20230222185716-a3b23cc77e89
golang.org/x/term v0.8.0 golang.org/x/term v0.5.0
golang.org/x/time v0.3.0 golang.org/x/time v0.0.0-20220609170525-579cf78fd858
golang.org/x/tools v0.9.1 golang.org/x/tools v0.5.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard/windows v0.5.3 golang.zx2c4.com/wireguard/windows v0.5.3
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f gvisor.dev/gvisor v0.0.0-20230328175328-162ed5ef888d
honnef.co/go/tools v0.4.3 honnef.co/go/tools v0.4.2
inet.af/peercred v0.0.0-20210906144145-0893ea02156a inet.af/peercred v0.0.0-20210906144145-0893ea02156a
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626 inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
inet.af/wf v0.0.0-20221017222439-36129f591884 inet.af/wf v0.0.0-20220728202103-50d96caab2f6
k8s.io/api v0.27.2 k8s.io/api v0.25.0
k8s.io/apimachinery v0.27.2 k8s.io/apimachinery v0.25.0
k8s.io/client-go v0.27.2 k8s.io/client-go v0.25.0
nhooyr.io/websocket v1.8.7 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 sigs.k8s.io/yaml v1.3.0
software.sslmate.com/src/go-pkcs12 v0.2.0 software.sslmate.com/src/go-pkcs12 v0.2.0
) )
require ( require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.1.0 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect
filippo.io/edwards25519 v1.0.0 // indirect github.com/Antonboom/errname v0.1.5 // indirect
github.com/Abirdcfly/dupword v0.0.11 // indirect github.com/Antonboom/nilnil v0.1.0 // indirect
github.com/Antonboom/errname v0.1.9 // indirect
github.com/Antonboom/nilnil v0.1.4 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect
github.com/Djarvur/go-err113 v0.1.0 // 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/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // 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/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/OpenPeeDeeP/depguard v1.1.1 // indirect github.com/OpenPeeDeeP/depguard v1.0.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 // indirect github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
github.com/acomagu/bufpipe v1.0.4 // 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/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.2.0 // indirect
github.com/ashanbrown/forbidigo v1.5.1 // indirect github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.6.4 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.21 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // 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.27 // 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.21 // 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.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // 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/accept-encoding v1.9.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 // indirect
github.com/aws/smithy-go v1.13.5 // indirect github.com/aws/smithy-go v1.13.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.0 // indirect github.com/bkielbasa/cyclop v1.2.0 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/blizzy78/varnamelen v0.5.0 // indirect
github.com/bombsimon/wsl/v3 v3.4.0 // indirect github.com/bombsimon/wsl/v3 v3.3.0 // indirect
github.com/breml/bidichk v0.2.4 // indirect github.com/breml/bidichk v0.2.1 // indirect
github.com/breml/errchkjson v0.3.1 // indirect github.com/butuzov/ireturn v0.1.1 // indirect
github.com/butuzov/ireturn v0.2.0 // indirect github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect github.com/charithe/durationcheck v0.0.9 // indirect
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
github.com/cloudflare/circl v1.3.3 // indirect github.com/cloudflare/circl v1.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.2.9 // indirect
github.com/daixiang0/gci v0.10.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingaikin/go-header v0.4.3 // indirect github.com/denis-tingajkin/go-header v0.4.2 // indirect
github.com/docker/cli v23.0.5+incompatible // indirect github.com/docker/cli v20.10.16+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v23.0.5+incompatible // indirect github.com/docker/docker v20.10.16+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.12.0 // indirect
github.com/esimonov/ifshort v1.0.4 // indirect github.com/esimonov/ifshort v1.0.3 // indirect
github.com/ettle/strcase v0.1.1 // 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/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/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fzipp/gocyclo v0.3.1 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect github.com/gliderlabs/ssh v0.3.3 // indirect
github.com/go-critic/go-critic v0.8.0 // indirect github.com/go-critic/go-critic v0.6.1 // indirect
github.com/go-git/gcfg v1.5.0 // 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-billy/v5 v5.3.1 // indirect
github.com/go-git/go-git/v5 v5.6.1 // indirect github.com/go-git/go-git/v5 v5.4.2 // indirect
github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcast v1.0.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.0.0 // indirect
github.com/go-toolsmith/astequal v1.1.0 // indirect github.com/go-toolsmith/astequal v1.0.1 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astfmt v1.0.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/astp v1.0.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/strparse v1.0.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-toolsmith/typep v1.0.2 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // 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/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // 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/go-misc v0.0.0-20180628070357-927a3d87b613 // indirect
github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // 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/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
github.com/golangci/misspell v0.4.0 // indirect github.com/golangci/misspell v0.3.5 // indirect
github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2 // indirect
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.6.9 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.1.0 // indirect
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect
github.com/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 // indirect github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 // indirect
github.com/goreleaser/chglog v0.4.2 // indirect github.com/goreleaser/chglog v0.1.2 // indirect
github.com/goreleaser/fileglob v0.3.1 // indirect github.com/goreleaser/fileglob v0.3.1 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect
@ -211,12 +204,10 @@ require (
github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // 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/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect github.com/huandu/xstrings v1.3.2 // indirect
github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.12 // indirect
github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jgautheron/goconst v1.5.1 // indirect github.com/jgautheron/goconst v1.5.1 // indirect
github.com/jingyugao/rowserrcheck v1.1.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/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/julz/importas v0.1.0 // indirect github.com/julz/importas v0.0.0-20210922140945-27e0a5d4dee2 // indirect
github.com/junk1tm/musttag v0.5.0 // indirect github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kisielk/errcheck v1.6.0 // indirect
github.com/kisielk/errcheck v1.6.3 // indirect
github.com/kisielk/gotool v1.0.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/fs v0.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/kulti/thelper v0.6.3 // indirect github.com/kulti/thelper v0.4.0 // indirect
github.com/kunwardeep/paralleltest v1.0.6 // indirect github.com/kunwardeep/paralleltest v1.0.3 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/kyoh86/exportloopref v0.1.8 // indirect
github.com/ldez/gomoddirectives v0.2.3 // indirect github.com/ldez/gomoddirectives v0.2.2 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect github.com/ldez/tagliatelle v0.2.0 // indirect
github.com/leonklingele/grouper v1.1.1 // indirect github.com/magiconair/properties v1.8.5 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect github.com/mailru/easyjson v0.7.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/maratori/testpackage v1.0.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.0 // indirect
github.com/mgechev/revive v1.3.1 // 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/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.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/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nishanths/exhaustive v0.10.0 // indirect github.com/nishanths/exhaustive v0.7.11 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect github.com/nishanths/predeclared v0.2.1 // indirect
github.com/nunnatsa/ginkgolinter v0.11.2 // indirect
github.com/olekukonko/tablewriter v0.0.5 // 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/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polyfloyd/go-errorlint v1.4.1 // indirect github.com/polyfloyd/go-errorlint v0.0.0-20211125173453-6d6d39c5bb8b // indirect
github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect
github.com/quasilyte/go-ruleguard v0.3.19 // indirect github.com/quasilyte/go-ruleguard v0.3.13 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/ryancurrah/gomodguard v1.2.3 // indirect
github.com/ryancurrah/gomodguard v1.3.0 // indirect github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect
github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sassoftware/go-rpmutils v0.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect github.com/securego/gosec/v2 v2.9.3 // indirect
github.com/sassoftware/go-rpmutils v0.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect
github.com/securego/gosec/v2 v2.15.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.9.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.4.7 // indirect
github.com/sivchari/nosnakecase v1.7.0 // indirect github.com/sonatard/noctx v0.0.1 // indirect
github.com/sivchari/tenv v1.7.1 // indirect github.com/sourcegraph/go-diff v0.6.1 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect github.com/spf13/afero v1.6.0 // indirect
github.com/sonatard/noctx v0.0.2 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // 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/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect github.com/stretchr/objx v0.4.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/testify v1.8.0 // indirect
github.com/stretchr/testify v1.8.2 // indirect github.com/subosito/gotenv v1.2.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect github.com/sylvia7788/contextcheck v1.0.4 // indirect
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect github.com/tdakkota/asciicheck v0.1.1 // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
github.com/tetafro/godot v1.4.11 // indirect github.com/tetafro/godot v1.4.11 // indirect
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect
github.com/timonwong/loggercheck v0.9.4 // indirect github.com/tomarrell/wrapcheck/v2 v2.4.0 // indirect
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect github.com/tommy-muehle/go-mnd/v2 v2.4.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect github.com/ulikunitz/xz v0.5.10 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/ultraware/funlen v0.0.3 // indirect github.com/ultraware/funlen v0.0.3 // indirect
github.com/ultraware/whitespace v0.0.5 // indirect github.com/ultraware/whitespace v0.0.4 // indirect
github.com/uudashr/gocognit v1.0.6 // indirect github.com/uudashr/gocognit v1.0.5 // indirect
github.com/vbatts/tar-split v0.11.2 // 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/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.1.0 // indirect
github.com/yeya24/promlinter v0.2.0 // indirect go.uber.org/atomic v1.7.0 // indirect
gitlab.com/bosi/decorder v0.2.3 // indirect go.uber.org/multierr v1.6.0 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
go.uber.org/atomic v1.11.0 // indirect golang.org/x/image v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
golang.org/x/image v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // 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/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect howett.net/plist v1.0.0 // indirect
k8s.io/apiextensions-apiserver v0.27.2 // indirect k8s.io/apiextensions-apiserver v0.25.0 // indirect
k8s.io/component-base v0.27.2 // indirect k8s.io/component-base v0.25.0 // indirect
k8s.io/klog/v2 v2.100.1 // indirect k8s.io/klog/v2 v2.70.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
mvdan.cc/gofumpt v0.5.0 // indirect mvdan.cc/gofumpt v0.2.0 // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 // indirect mvdan.cc/unparam v0.0.0-20211002134041-24922b6997ca // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // 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 ( import (
"bufio" "bufio"
"bytes"
"io" "io"
"os" "os"
"os/exec"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strings" "strings"
@ -283,7 +281,7 @@ func inContainer() opt.Bool {
return nil return nil
}) })
lineread.File("/proc/mounts", func(line []byte) error { 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) ret.Set(true)
return io.EOF return io.EOF
} }
@ -407,7 +405,7 @@ func DisabledEtcAptSource() bool {
return false return false
} }
mod := fi.ModTime() 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 return c.disabled
} }
f, err := os.Open(path) f, err := os.Open(path)
@ -436,12 +434,3 @@ func etcAptSourceFileIsDisabled(r io.Reader) bool {
} }
return disabled 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" propFile = "/etc.defaults/VERSION"
case distro.OpenWrt: case distro.OpenWrt:
propFile = "/etc/openwrt_release" propFile = "/etc/openwrt_release"
case distro.Unraid:
propFile = "/etc/unraid-version"
case distro.WDMyCloud: case distro.WDMyCloud:
slurp, _ := os.ReadFile("/etc/version") slurp, _ := os.ReadFile("/etc/version")
meta.DistroVersion = string(bytes.TrimSpace(slurp)) meta.DistroVersion = string(bytes.TrimSpace(slurp))
@ -155,8 +153,6 @@ func linuxVersionMeta() (meta versionMeta) {
meta.DistroVersion = m["productversion"] meta.DistroVersion = m["productversion"]
case distro.OpenWrt: case distro.OpenWrt:
meta.DistroVersion = m["DISTRIB_RELEASE"] meta.DistroVersion = m["DISTRIB_RELEASE"]
case distro.Unraid:
meta.DistroVersion = m["version"]
} }
return return
} }

View File

@ -6,7 +6,6 @@
package tooldeps package tooldeps
import ( import (
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "github.com/tailscale/depaware/depaware" _ "github.com/tailscale/depaware/depaware"
_ "golang.org/x/tools/cmd/goimports" _ "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. // A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct { var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct {
HTTPS bool HTTPS bool
HTTP bool
TCPForward string TCPForward string
TerminateTLS 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) HTTPS() bool { return v.ж.HTTPS }
func (v TCPPortHandlerView) HTTP() bool { return v.ж.HTTP }
func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward } func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward }
func (v TCPPortHandlerView) TerminateTLS() string { return v.ж.TerminateTLS } 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. // A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct { var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct {
HTTPS bool HTTPS bool
HTTP bool
TCPForward string TCPForward string
TerminateTLS string TerminateTLS string
}{}) }{})

View File

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

View File

@ -31,7 +31,6 @@ import (
"time" "time"
"golang.org/x/crypto/acme" "golang.org/x/crypto/acme"
"golang.org/x/exp/slices"
"tailscale.com/atomicfile" "tailscale.com/atomicfile"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/hostinfo" "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 { if pair, err := getCertPEMCached(cs, domain, now); err == nil {
shouldRenew, err := shouldStartDomainRenewal(domain, now, pair) future := now.AddDate(0, 0, 14)
if err != nil { if b.shouldStartDomainRenewal(cs, domain, future) {
logf("error checking for certificate renewal: %v", err)
} else if shouldRenew {
logf("starting async renewal") logf("starting async renewal")
// Start renewal in the background. // 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 return pair, nil
} }
@ -120,41 +117,18 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
return pair, nil 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() renewMu.Lock()
defer renewMu.Unlock() defer renewMu.Unlock()
now := time.Now()
if last, ok := lastRenewCheck[domain]; ok && now.Sub(last) < time.Minute { if last, ok := lastRenewCheck[domain]; ok && now.Sub(last) < time.Minute {
// We checked very recently. Don't bother reparsing & // We checked very recently. Don't bother reparsing &
// validating the x509 cert. // validating the x509 cert.
return false, nil return false
} }
lastRenewCheck[domain] = now lastRenewCheck[domain] = now
_, err := getCertPEMCached(cs, domain, future)
block, _ := pem.Decode(pair.CertPEM) return errors.Is(err, errCertExpired)
if block == nil {
return false, fmt.Errorf("parsing certificate PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return false, fmt.Errorf("parsing certificate: %w", err)
}
certLifetime := cert.NotAfter.Sub(cert.NotBefore)
if certLifetime < 0 {
return false, fmt.Errorf("negative certificate lifetime %v", certLifetime)
}
// Per https://github.com/tailscale/tailscale/issues/8204, check
// whether we're more than 2/3 of the way through the certificate's
// lifetime, which is the officially-recommended best practice by Let's
// Encrypt.
renewalDuration := certLifetime * 2 / 3
renewAt := cert.NotBefore.Add(renewalDuration)
if now.After(renewAt) {
return true, nil
}
return false, nil
} }
// certStore provides a way to perist and retrieve TLS certificates. // 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 key := "_acme-challenge." + domain
// Do a best-effort lookup to see if we've already created this DNS name
// in a previous attempt. Don't burn too much time on it, though. Worst
// case we ask the server to create something that already exists.
var resolver net.Resolver var resolver net.Resolver
lookupCtx, lookupCancel := context.WithTimeout(ctx, 500*time.Millisecond) var ok bool
txts, _ := resolver.LookupTXT(lookupCtx, key) txts, _ := resolver.LookupTXT(ctx, key)
lookupCancel() for _, txt := range txts {
if slices.Contains(txts, rec) { if txt == rec {
logf("TXT record already existed") ok = true
} else { logf("TXT record already existed")
break
}
}
if !ok {
logf("starting SetDNS call...") logf("starting SetDNS call...")
err = b.SetDNS(ctx, key, rec) err = b.SetDNS(ctx, key, rec)
if err != nil { if err != nil {

View File

@ -6,19 +6,12 @@
package ipnlocal package ipnlocal
import ( import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"embed" "embed"
"encoding/pem"
"math/big"
"testing" "testing"
"time" "time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"golang.org/x/exp/maps"
"tailscale.com/ipn/store/mem" "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/dnstype"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
"tailscale.com/util/cloudenv" "tailscale.com/util/cloudenv"
"tailscale.com/util/cmpx"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
) )
@ -309,7 +308,10 @@ func TestDNSConfigForNetmap(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 var log tstest.MemLogger
got := dnsConfigForNetmap(tt.nm, tt.prefs.View(), log.Logf, verOS) got := dnsConfigForNetmap(tt.nm, tt.prefs.View(), log.Logf, verOS)
if !reflect.DeepEqual(got, tt.want) { 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 // nextExpiry being zero is a sentinel that we haven't yet set
// an expiry; otherwise, only update if this node's expiry is // an expiry; otherwise, only update if this node's expiry is
// sooner than the currently-stored one (since we want the // 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) { if nextExpiry.IsZero() || peer.KeyExpiry.Before(nextExpiry) {
nextExpiry = peer.KeyExpiry nextExpiry = peer.KeyExpiry
} }

View File

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

View File

@ -4,6 +4,7 @@
package ipnlocal package ipnlocal
import ( import (
"bytes"
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
@ -17,6 +18,7 @@ import (
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"os/exec"
"os/user" "os/user"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -30,7 +32,6 @@ import (
"go4.org/mem" "go4.org/mem"
"go4.org/netipx" "go4.org/netipx"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gvisor.dev/gvisor/pkg/tcpip"
"tailscale.com/client/tailscale/apitype" "tailscale.com/client/tailscale/apitype"
"tailscale.com/control/controlclient" "tailscale.com/control/controlclient"
"tailscale.com/doctor" "tailscale.com/doctor"
@ -59,7 +60,6 @@ import (
"tailscale.com/syncs" "tailscale.com/syncs"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tka" "tailscale.com/tka"
"tailscale.com/tsd"
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
"tailscale.com/types/empty" "tailscale.com/types/empty"
"tailscale.com/types/key" "tailscale.com/types/key"
@ -70,7 +70,6 @@ import (
"tailscale.com/types/preftype" "tailscale.com/types/preftype"
"tailscale.com/types/ptr" "tailscale.com/types/ptr"
"tailscale.com/types/views" "tailscale.com/types/views"
"tailscale.com/util/cmpx"
"tailscale.com/util/deephash" "tailscale.com/util/deephash"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/util/mak" "tailscale.com/util/mak"
@ -138,13 +137,12 @@ type LocalBackend struct {
logf logger.Logf // general logging logf logger.Logf // general logging
keyLogf logger.Logf // for printing list of peers on change keyLogf logger.Logf // for printing list of peers on change
statsLogf logger.Logf // for printing peers stats on change statsLogf logger.Logf // for printing peers stats on change
sys *tsd.System e wgengine.Engine
e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys
pm *profileManager pm *profileManager
store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys store ipn.StateStore
dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys dialer *tsdial.Dialer // non-nil
backendLogID logid.PublicID backendLogID logid.PublicID
unregisterNetMon func() unregisterLinkMon func()
unregisterHealthWatch func() unregisterHealthWatch func()
portpoll *portlist.Poller // may be nil portpoll *portlist.Poller // may be nil
portpollOnce sync.Once // guards starting readPoller portpollOnce sync.Once // guards starting readPoller
@ -269,10 +267,10 @@ type clientGen func(controlclient.Options) (controlclient.Client, error)
// but is not actually running. // but is not actually running.
// //
// If dialer is nil, a new one is made. // 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) { func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStore, dialer *tsdial.Dialer, e wgengine.Engine, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
e := sys.Engine.Get() if e == nil {
store := sys.StateStore.Get() panic("ipn.NewLocalBackend: engine must not be nil")
dialer := sys.Dialer.Get() }
pm, err := newProfileManager(store, logf) pm, err := newProfileManager(store, logf)
if err != nil { if err != nil {
@ -292,7 +290,10 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
osshare.SetFileSharingEnabled(false, logf) osshare.SetFileSharingEnabled(false, logf)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
portpoll := new(portlist.Poller) portpoll, err := portlist.NewPoller()
if err != nil {
logf("skipping portlist: %s", err)
}
b := &LocalBackend{ b := &LocalBackend{
ctx: ctx, ctx: ctx,
@ -300,11 +301,10 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
logf: logf, logf: logf,
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now), keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now), statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
sys: sys,
e: e, e: e,
dialer: dialer,
store: store,
pm: pm, pm: pm,
store: store,
dialer: dialer,
backendLogID: logID, backendLogID: logID,
state: ipn.NoState, state: ipn.NoState,
portpoll: portpoll, portpoll: portpoll,
@ -313,8 +313,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
loginFlags: loginFlags, loginFlags: loginFlags,
} }
netMon := sys.NetMon.Get() b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID)
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon)
if err != nil { if err != nil {
log.Printf("error setting up sockstat logger: %v", err) 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.statusChanged = sync.NewCond(&b.statusLock)
b.e.SetStatusCallback(b.setWgengineStatus) 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 // Call our linkChange code once with the current state, and
// then also whenever it changes: // then also whenever it changes:
b.linkChange(false, netMon.InterfaceState()) b.linkChange(false, linkMon.InterfaceState())
b.unregisterNetMon = netMon.RegisterChangeCallback(b.linkChange) b.unregisterLinkMon = linkMon.RegisterChangeCallback(b.linkChange)
b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange) b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange)
if tunWrap, ok := b.sys.Tun.GetOK(); ok { wiredPeerAPIPort := false
tunWrap.PeerAPIPort = b.GetPeerAPIPort if ig, ok := e.(wgengine.InternalsGetter); ok {
} else { 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) 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. // unchanged when the timer actually fires.
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if ls := b.componentLogUntil[component]; ls.until.Equal(until) { if ls := b.componentLogUntil[component]; ls.until == until {
setEnabled(false) setEnabled(false)
b.logf("debugging logging for component %q disabled (by timer)", component) 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. // Dialer returns the backend's dialer.
// It is always non-nil.
func (b *LocalBackend) Dialer() *tsdial.Dialer { func (b *LocalBackend) Dialer() *tsdial.Dialer {
return b.dialer return b.dialer
} }
@ -495,7 +499,7 @@ func (b *LocalBackend) maybePauseControlClientLocked() {
b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || !networkUp) 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. // major is whether ifst is different than earlier.
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) { func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
b.mu.Lock() b.mu.Lock()
@ -572,7 +576,7 @@ func (b *LocalBackend) Shutdown() {
b.sockstatLogger.Shutdown() b.sockstatLogger.Shutdown()
} }
b.unregisterNetMon() b.unregisterLinkMon()
b.unregisterHealthWatch() b.unregisterHealthWatch()
if cc != nil { if cc != nil {
cc.Shutdown() cc.Shutdown()
@ -640,7 +644,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
defer b.mu.Unlock() defer b.mu.Unlock()
sb.MutateStatus(func(s *ipnstate.Status) { sb.MutateStatus(func(s *ipnstate.Status) {
s.Version = version.Long() s.Version = version.Long()
s.TUN = !b.sys.IsNetstack() s.TUN = !wgengine.IsNetstack(b.e)
s.BackendState = b.state.String() s.BackendState = b.state.String()
s.AuthURL = b.authURLSticky s.AuthURL = b.authURLSticky
if err := health.OverallError(); err != nil { if err := health.OverallError(); err != nil {
@ -742,6 +746,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
HostName: p.Hostinfo.Hostname(), HostName: p.Hostinfo.Hostname(),
DNSName: p.Name, DNSName: p.Name,
OS: p.Hostinfo.OS(), OS: p.Hostinfo.OS(),
KeepAlive: p.KeepAlive,
LastSeen: lastSeen, LastSeen: lastSeen,
Online: p.Online != nil && *p.Online, Online: p.Online != nil && *p.Online,
ShareeNode: p.Hostinfo.ShareeNode(), ShareeNode: p.Hostinfo.ShareeNode(),
@ -1072,7 +1077,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
b.e.SetDERPMap(st.NetMap.DERPMap) b.e.SetDERPMap(st.NetMap.DERPMap)
// Update our cached DERP map // Update our cached DERP map
dnsfallback.UpdateCache(st.NetMap.DERPMap, b.logf) dnsfallback.UpdateCache(st.NetMap.DERPMap)
b.send(ipn.Notify{NetMap: st.NetMap}) b.send(ipn.Notify{NetMap: st.NetMap})
} }
@ -1310,8 +1315,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
hostinfo := hostinfo.New() hostinfo := hostinfo.New()
hostinfo.BackendLogID = b.backendLogID.String() hostinfo.BackendLogID = b.backendLogID.String()
hostinfo.FrontendLogID = opts.FrontendLogID hostinfo.FrontendLogID = opts.FrontendLogID
hostinfo.Userspace.Set(b.sys.IsNetstack()) hostinfo.Userspace.Set(wgengine.IsNetstack(b.e))
hostinfo.UserspaceRouter.Set(b.sys.IsNetstackRouter()) hostinfo.UserspaceRouter.Set(wgengine.IsNetstackRouter(b.e))
if b.cc != nil { if b.cc != nil {
// TODO(apenwarr): avoid the need to reinit controlclient. // TODO(apenwarr): avoid the need to reinit controlclient.
@ -1373,6 +1378,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
if b.portpoll != nil { if b.portpoll != nil {
b.portpollOnce.Do(func() { b.portpollOnce.Do(func() {
go b.portpoll.Run(b.ctx)
go b.readPoller() go b.readPoller()
// Give the poller a second to get results to // Give the poller a second to get results to
@ -1395,7 +1401,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
var err error var err error
isNetstack := b.sys.IsNetstackRouter() isNetstack := wgengine.IsNetstackRouter(b.e)
debugFlags := controlDebugFlags debugFlags := controlDebugFlags
if isNetstack { if isNetstack {
debugFlags = append([]string{"netstack"}, debugFlags...) debugFlags = append([]string{"netstack"}, debugFlags...)
@ -1417,7 +1423,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
HTTPTestClient: httpTestClient, HTTPTestClient: httpTestClient,
DiscoPublicKey: discoPublic, DiscoPublicKey: discoPublic,
DebugFlags: debugFlags, DebugFlags: debugFlags,
NetMon: b.sys.NetMon.Get(), LinkMonitor: b.e.GetLinkMonitor(),
Pinger: b, Pinger: b,
PopBrowserURL: b.tellClientToBrowseToURL, PopBrowserURL: b.tellClientToBrowseToURL,
OnClientVersion: b.onClientVersion, OnClientVersion: b.onClientVersion,
@ -1807,30 +1813,11 @@ func dnsMapsEqual(new, old *netmap.NetworkMap) bool {
// readPoller is a goroutine that receives service lists from // readPoller is a goroutine that receives service lists from
// b.portpoll and propagates them into the controlclient's HostInfo. // b.portpoll and propagates them into the controlclient's HostInfo.
func (b *LocalBackend) readPoller() { func (b *LocalBackend) readPoller() {
isFirst := true n := 0
ticker := time.NewTicker(portlist.PollInterval())
defer ticker.Stop()
initChan := make(chan struct{})
close(initChan)
for { for {
select { ports, ok := <-b.portpoll.Updates()
case <-ticker.C: if !ok {
case <-b.ctx.Done():
return 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{} sl := []tailcfg.Service{}
for _, p := range ports { for _, p := range ports {
@ -1854,8 +1841,8 @@ func (b *LocalBackend) readPoller() {
b.doSetHostinfoFilterServices(hi) b.doSetHostinfoFilterServices(hi)
if isFirst { n++
isFirst = false if n == 1 {
close(b.gotPortPollRes) 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) // [GRINDER STATS LINES] - please don't remove (used for log parsing)
if peerStats.Len() > 0 { if peerStats.Len() > 0 {
b.keyLogf("[v1] peer keys: %s", strings.TrimSpace(peerKeys.String())) 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 return ret
} }
@ -2580,7 +2567,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
if distro.Get() == distro.QNAP && !envknob.UseWIPCode() { if distro.Get() == distro.QNAP && !envknob.UseWIPCode() {
return errors.New("The Tailscale SSH server does not run on QNAP.") return errors.New("The Tailscale SSH server does not run on QNAP.")
} }
b.updateSELinuxHealthWarning() checkSELinux()
// otherwise okay // otherwise okay
case "darwin": case "darwin":
// okay only in tailscaled mode for now. // 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 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 remote parameter is the remote address.
// The local parameter is the local address (either a Tailscale IPv4 // The local parameter is the local address (either a Tailscale IPv4
// or IPv6 IP and the peerapi port for that address). // or IPv6 IP and the peerapi port for that address).
// //
// The connection will be closed by handlePeerAPIConn. // The connection will be closed by ServePeerAPIConnection.
func (b *LocalBackend) handlePeerAPIConn(remote, local netip.AddrPort, c net.Conn) { func (b *LocalBackend) ServePeerAPIConnection(remote, local netip.AddrPort, c net.Conn) {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners { for _, pln := range b.peerAPIListeners {
@ -2847,48 +2834,6 @@ func (b *LocalBackend) handlePeerAPIConn(remote, local netip.AddrPort, c net.Con
return 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) { func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) {
for _, pln := range b.peerAPIListeners { for _, pln := range b.peerAPIListeners {
proto := tailcfg.PeerAPI4 proto := tailcfg.PeerAPI4
@ -3372,12 +3317,14 @@ func (b *LocalBackend) initPeerAPIListener() {
directFileMode: b.directFileRoot != "", directFileMode: b.directFileRoot != "",
directFileDoFinalRename: b.directFileDoFinalRename, directFileDoFinalRename: b.directFileDoFinalRename,
} }
if dm, ok := b.sys.DNSManager.GetOK(); ok { if re, ok := b.e.(wgengine.ResolvingEngine); ok {
ps.resolver = dm.Resolver() if r, ok := re.GetResolver(); ok {
ps.resolver = r
}
} }
b.peerAPIServer = ps b.peerAPIServer = ps
isNetstack := b.sys.IsNetstack() isNetstack := wgengine.IsNetstack(b.e)
for i, a := range b.netMap.Addresses { for i, a := range b.netMap.Addresses {
var ln net.Listener var ln net.Listener
var err error var err error
@ -3683,19 +3630,6 @@ func (b *LocalBackend) hasNodeKey() bool {
return p.Valid() && p.Persist().Valid() && !p.Persist().PrivateNodeKey().IsZero() 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 // nextState returns the state the backend seems to be in, based on
// its internal state. // its internal state.
func (b *LocalBackend) nextState() ipn.State { func (b *LocalBackend) nextState() ipn.State {
@ -3973,7 +3907,10 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
b.dialer.SetNetMap(nm) b.dialer.SetNetMap(nm)
var login string var login string
if nm != nil { 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 b.netMap = nm
if login != b.activeLogin { if login != b.activeLogin {
@ -4103,7 +4040,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
b.setServeProxyHandlersLocked() b.setServeProxyHandlersLocked()
// don't listen on netmap addresses if we're in userspace mode // don't listen on netmap addresses if we're in userspace mode
if !b.sys.IsNetstack() { if !wgengine.IsNetstack(b.e) {
b.updateServeTCPPortNetMapAddrListenersLocked(servePorts) b.updateServeTCPPortNetMapAddrListenersLocked(servePorts)
} }
} }
@ -4128,10 +4065,6 @@ func (b *LocalBackend) setServeProxyHandlersLocked() {
b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) { b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) { conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
backend := h.Proxy() backend := h.Proxy()
if backend == "" {
// Only create proxy handlers for servers with a proxy backend.
return true
}
mak.Set(&backends, backend, true) mak.Set(&backends, backend, true)
if _, ok := b.serveProxyHandlers.Load(backend); ok { if _, ok := b.serveProxyHandlers.Load(backend); ok {
return true return true
@ -4458,7 +4391,7 @@ func nodeIP(n *tailcfg.Node, pred func(netip.Addr) bool) netip.Addr {
} }
func (b *LocalBackend) CheckIPForwarding() error { func (b *LocalBackend) CheckIPForwarding() error {
if b.sys.IsNetstackRouter() { if wgengine.IsNetstackRouter(b.e) {
return nil return nil
} }
@ -4604,9 +4537,13 @@ func (b *LocalBackend) DebugReSTUN() error {
} }
func (b *LocalBackend) magicConn() (*magicsock.Conn, error) { func (b *LocalBackend) magicConn() (*magicsock.Conn, error) {
mc, ok := b.sys.MagicSock.GetOK() ig, ok := b.e.(wgengine.InternalsGetter)
if !ok { 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 return mc, nil
} }
@ -4706,29 +4643,33 @@ func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) {
var warnSSHSELinux = health.NewWarnable() var warnSSHSELinux = health.NewWarnable()
func (b *LocalBackend) updateSELinuxHealthWarning() { func checkSELinux() {
if hostinfo.IsSELinuxEnforcing() { 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")) warnSSHSELinux.Set(errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux"))
} else { } else {
warnSSHSELinux.Set(nil) 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() s, err := b.sshServerOrInit()
if err != nil { if err != nil {
return err return err
} }
b.updateSELinuxHealthWarning() checkSELinux()
return s.HandleSSHConn(c) return s.HandleSSHConn(c)
} }
// HandleQuad100Port80Conn serves http://100.100.100.100/ on port 80 (and // HandleQuad100Port80Conn serves http://100.100.100.100/ on port 80 (and
// the equivalent tsaddr.TailscaleServiceIPv6 address). // the equivalent tsaddr.TailscaleServiceIPv6 address).
func (b *LocalBackend) HandleQuad100Port80Conn(c net.Conn) error { func (b *LocalBackend) HandleQuad100Port80Conn(c net.Conn) {
var s http.Server var s http.Server
s.Handler = http.HandlerFunc(b.handleQuad100Port80Conn) s.Handler = http.HandlerFunc(b.handleQuad100Port80Conn)
return s.Serve(netutil.NewOneConnListener(c, nil)) s.Serve(netutil.NewOneConnListener(c, nil))
} }
func validQuad100Host(h string) bool { func validQuad100Host(h string) bool {

View File

@ -20,7 +20,6 @@ import (
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -502,16 +501,13 @@ func TestLazyMachineKeyGeneration(t *testing.T) {
tstest.Replace(t, &panicOnMachineKeyGeneration, func() bool { return true }) tstest.Replace(t, &panicOnMachineKeyGeneration, func() bool { return true })
var logf logger.Logf = logger.Discard var logf logger.Logf = logger.Discard
sys := new(tsd.System)
store := new(mem.Store) store := new(mem.Store)
sys.Set(store) eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err) t.Fatalf("NewFakeUserspaceEngine: %v", err)
} }
t.Cleanup(eng.Close) t.Cleanup(eng.Close)
sys.Set(eng) lb, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, eng, 0)
lb, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil { if err != nil {
t.Fatalf("NewLocalBackend: %v", err) t.Fatalf("NewLocalBackend: %v", err)
} }
@ -769,16 +765,13 @@ func TestPacketFilterPermitsUnlockedNodes(t *testing.T) {
func TestStatusWithoutPeers(t *testing.T) { func TestStatusWithoutPeers(t *testing.T) {
logf := tstest.WhileTestRunningLogger(t) logf := tstest.WhileTestRunningLogger(t)
store := new(testStateStorage) store := new(testStateStorage)
sys := new(tsd.System) e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err) t.Fatalf("NewFakeUserspaceEngine: %v", err)
} }
sys.Set(e)
t.Cleanup(e.Close) 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 { if err != nil {
t.Fatalf("NewLocalBackend: %v", err) t.Fatalf("NewLocalBackend: %v", err)
} }

View File

@ -12,7 +12,6 @@ import (
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -48,17 +47,14 @@ func TestLocalLogLines(t *testing.T) {
idA := logid(0xaa) idA := logid(0xaa)
// set up a LocalBackend, super bare bones. No functional data. // set up a LocalBackend, super bare bones. No functional data.
sys := new(tsd.System)
store := new(mem.Store) store := new(mem.Store)
sys.Set(store) e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(e.Close) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -158,9 +158,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
return nil 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() ourNodeKey := prefs.Persist().PublicNodeKey()
@ -199,7 +197,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
health.SetTKAHealth(nil) health.SetTKAHealth(nil)
} }
} else { } 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() filtered[i] = b.tka.filtered[i].Clone()
} }
stateID1, _ := b.tka.authority.StateIDs()
return &ipnstate.NetworkLockStatus{ return &ipnstate.NetworkLockStatus{
Enabled: true, Enabled: true,
Head: &head, Head: &head,
@ -461,7 +457,6 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
NodeKeySigned: selfAuthorized, NodeKeySigned: selfAuthorized,
TrustedKeys: outKeys, TrustedKeys: outKeys,
FilteredPeers: filtered, 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 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) { func signNodeKey(nodeInfo tailcfg.TKASignInfo, signer key.NLPrivate) (*tka.NodeKeySignature, error) {
p, err := nodeInfo.NodePublic.MarshalBinary() p, err := nodeInfo.NodePublic.MarshalBinary()
if err != nil { if err != nil {

View File

@ -875,7 +875,7 @@ func TestTKAForceDisable(t *testing.T) {
} }
if b.tka != nil { 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/tailcfg"
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/multierr" "tailscale.com/util/multierr"
"tailscale.com/version/distro" "tailscale.com/wgengine"
"tailscale.com/wgengine/filter" "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 = "" ipStr = ""
} }
@ -605,16 +605,6 @@ func (h *peerAPIHandler) logf(format string, a ...any) {
h.ps.b.logf("peerapi: "+format, a...) 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 { func (h *peerAPIHandler) validateHost(r *http.Request) error {
if r.Host == "peer" { if r.Host == "peer" {
return nil return nil
@ -623,8 +613,9 @@ func (h *peerAPIHandler) validateHost(r *http.Request) error {
if err != nil { if err != nil {
return err return err
} }
if !h.isAddressValid(ap.Addr()) { hostIPPfx := netip.PrefixFrom(ap.Addr(), ap.Addr().BitLen())
return fmt.Errorf("%v not found in self addresses", ap.Addr()) if !slices.Contains(h.selfNode.Addresses, hostIPPfx) {
return fmt.Errorf("%v not found in self addresses", hostIPPfx)
} }
return nil return nil
} }
@ -780,7 +771,7 @@ func (h *peerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Reque
return return
} }
getConnOrReset := func() (net.Conn, bool) { getConn := func() (net.Conn, bool) {
conn, _, err := w.(http.Hijacker).Hijack() conn, _, err := w.(http.Hijacker).Hijack()
if err != nil { if err != nil {
h.logf("ingress: failed hijacking conn") 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) 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) { 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, "</tfoot>")
fmt.Fprintln(w, "</table>") 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 { type incomingFile struct {
@ -1090,10 +1075,6 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
http.Error(w, errNoTaildrop.Error(), http.StatusInternalServerError) http.Error(w, errNoTaildrop.Error(), http.StatusInternalServerError)
return 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() rawPath := r.URL.EscapedPath()
suffix, ok := strings.CutPrefix(rawPath, "/v0/put/") suffix, ok := strings.CutPrefix(rawPath, "/v0/put/")
if !ok { 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) http.Error(w, "denied; no debug access", http.StatusForbidden)
return return
} }
if mc, ok := h.ps.b.sys.MagicSock.GetOK(); ok { eng := h.ps.b.e
mc.ServeHTTPDebug(w, r) if ig, ok := eng.(wgengine.InternalsGetter); ok {
return if _, mc, _, ok := ig.GetInternals(); ok {
mc.ServeHTTPDebug(w, r)
return
}
} }
http.Error(w, "miswired", 500) http.Error(w, "miswired", 500)
} }

View File

@ -22,10 +22,6 @@ import (
"tailscale.com/util/winutil" "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 // profileManager is a wrapper around a StateStore that manages
// multiple profiles and the current profile. // multiple profiles and the current profile.
type profileManager struct { type profileManager struct {
@ -44,13 +40,6 @@ type profileManager struct {
isNewProfile bool 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 // CurrentUserID returns the current user ID. It is only non-empty on
// Windows where we have a multi-user system. // Windows where we have a multi-user system.
func (pm *profileManager) CurrentUserID() ipn.WindowsUserID { 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 // Read the CurrentProfileKey from the store which stores
// the selected profile for the current user. // the selected profile for the current user.
b, err := pm.store.ReadState(ipn.CurrentProfileKey(string(uid))) 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 err == ipn.ErrStateNotExist || len(b) == 0 {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
pm.dlogf("SetCurrentUserID: windows: migrating from legacy preferences") if err := pm.migrateFromLegacyPrefs(); err != nil {
if err := pm.migrateFromLegacyPrefs(); err != nil && !errors.Is(err, errAlreadyMigrated) {
return err return err
} }
} else { } else {
@ -92,7 +79,6 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
pk := ipn.StateKey(string(b)) pk := ipn.StateKey(string(b))
prof := pm.findProfileByKey(pk) prof := pm.findProfileByKey(pk)
if prof == nil { if prof == nil {
pm.dlogf("SetCurrentUserID: no profile found for key: %q", pk)
pm.NewProfile() pm.NewProfile()
return nil return nil
} }
@ -558,16 +544,8 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, goos stri
if err := pm.setPrefsLocked(prefs); err != nil { if err := pm.setPrefsLocked(prefs); err != nil {
return nil, err return nil, err
} }
// Most platform behavior is controlled by the goos parameter, however } else if len(knownProfiles) == 0 && goos != "windows" {
// some behavior is implied by build tag and fails when run on Windows,
// so we explicitly avoid that behavior when running on Windows.
// Specifically this reaches down into legacy preference loading that is
// specialized by profiles_windows.go and fails in tests on an invalid
// uid passed in from the unix tests. The uid's used for Windows tests
// and runtime must be valid Windows security identifier structures.
} else if len(knownProfiles) == 0 && goos != "windows" && runtime.GOOS != "windows" {
// No known profiles, try a migration. // No known profiles, try a migration.
pm.dlogf("no known profiles; trying to migrate from legacy prefs")
if err := pm.migrateFromLegacyPrefs(); err != nil { if err := pm.migrateFromLegacyPrefs(); err != nil {
return nil, err return nil, err
} }
@ -584,15 +562,13 @@ func (pm *profileManager) migrateFromLegacyPrefs() error {
sentinel, prefs, err := pm.loadLegacyPrefs() sentinel, prefs, err := pm.loadLegacyPrefs()
if err != nil { if err != nil {
metricMigrationError.Add(1) 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 { if err := pm.SetPrefs(prefs); err != nil {
metricMigrationError.Add(1) metricMigrationError.Add(1)
return fmt.Errorf("migrating _daemon profile: %w", err) return fmt.Errorf("migrating _daemon profile: %w", err)
} }
pm.completeMigration(sentinel) pm.completeMigration(sentinel)
pm.dlogf("completed legacy preferences migration with sentinel=%q", sentinel)
metricMigrationSuccess.Add(1) metricMigrationSuccess.Add(1)
return nil return nil
} }

View File

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

View File

@ -6,7 +6,6 @@ package ipnlocal
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
@ -22,6 +21,8 @@ const (
legacyPrefsExt = ".conf" legacyPrefsExt = ".conf"
) )
var errAlreadyMigrated = errors.New("profile migration already completed")
func legacyPrefsDir(uid ipn.WindowsUserID) (string, error) { func legacyPrefsDir(uid ipn.WindowsUserID) (string, error) {
// TODO(aaron): Ideally we'd have the impersonation token for the pipe's // TODO(aaron): Ideally we'd have the impersonation token for the pipe's
// client and use it to call SHGetKnownFolderPath, thus yielding the correct // 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) { func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
userLegacyPrefsDir, err := legacyPrefsDir(pm.currentUserID) userLegacyPrefsDir, err := legacyPrefsDir(pm.currentUserID)
if err != nil { if err != nil {
pm.dlogf("no legacy preferences directory for %q: %v", pm.currentUserID, err)
return "", ipn.PrefsView{}, err return "", ipn.PrefsView{}, err
} }
@ -48,20 +48,14 @@ func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
// verify that migration sentinel is not present // verify that migration sentinel is not present
_, err = os.Stat(migrationSentinel) _, err = os.Stat(migrationSentinel)
if err == nil { if err == nil {
pm.dlogf("migration sentinel %q already exists", migrationSentinel)
return "", ipn.PrefsView{}, errAlreadyMigrated return "", ipn.PrefsView{}, errAlreadyMigrated
} }
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
pm.dlogf("os.Stat(%q) = %v", migrationSentinel, err)
return "", ipn.PrefsView{}, err return "", ipn.PrefsView{}, err
} }
prefsPath := filepath.Join(userLegacyPrefsDir, legacyPrefsFile+legacyPrefsExt) prefsPath := filepath.Join(userLegacyPrefsDir, legacyPrefsFile+legacyPrefsExt)
prefs, err := ipn.LoadPrefs(prefsPath) 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 { if err != nil {
return "", ipn.PrefsView{}, err return "", ipn.PrefsView{}, err
} }

View File

@ -17,6 +17,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
pathpkg "path"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -143,7 +144,7 @@ func (s *serveListener) Run() {
} }
func (s *serveListener) shouldWarnAboutListenError(err error) bool { 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 // Machine likely doesn't have IPv6 enabled (or the IP is still being
// assigned). No need to warn. Notably, WSL2 (Issue 6303). // assigned). No need to warn. Notably, WSL2 (Issue 6303).
return false return false
@ -162,13 +163,12 @@ func (s *serveListener) handleServeListenersAccept(ln net.Listener) error {
return err return err
} }
srcAddr := conn.RemoteAddr().(*net.TCPAddr).AddrPort() srcAddr := conn.RemoteAddr().(*net.TCPAddr).AddrPort()
handler := s.b.tcpHandlerForServe(s.ap.Port(), srcAddr) getConn := func() (net.Conn, bool) { return conn, true }
if handler == nil { sendRST := func() {
s.b.logf("serve RST for %v", srcAddr) s.b.logf("serve RST for %v", srcAddr)
conn.Close() 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 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() b.mu.Lock()
sc := b.serveConfig sc := b.serveConfig
b.mu.Unlock() b.mu.Unlock()
@ -290,7 +290,7 @@ func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ip
if b.getTCPHandlerForFunnelFlow != nil { if b.getTCPHandlerForFunnelFlow != nil {
handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport) handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport)
if handler != nil { if handler != nil {
c, ok := getConnOrReset() c, ok := getConn()
if !ok { if !ok {
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport) b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
return return
@ -299,41 +299,39 @@ func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ip
return return
} }
} }
// TODO(bradfitz): pass ingressPeer etc in context to tcpHandlerForServe, // TODO(bradfitz): pass ingressPeer etc in context to HandleInterceptedTCPConn,
// extend serveHTTPContext or similar. // extend serveHTTPContext or similar.
handler := b.tcpHandlerForServe(dport, srcAddr) b.HandleInterceptedTCPConn(dport, srcAddr, getConn, sendRST)
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)
} }
// tcpHandlerForServe returns a handler for a TCP connection to be served via func (b *LocalBackend) HandleInterceptedTCPConn(dport uint16, srcAddr netip.AddrPort, getConn func() (net.Conn, bool), sendRST func()) {
// the ipn.ServeConfig.
func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort) (handler func(net.Conn) error) {
b.mu.Lock() b.mu.Lock()
sc := b.serveConfig sc := b.serveConfig
b.mu.Unlock() b.mu.Unlock()
if !sc.Valid() { if !sc.Valid() {
b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport) 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) tcph, ok := sc.TCP().GetOk(dport)
if !ok { if !ok {
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr) 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{ hs := &http.Server{
TLSConfig: &tls.Config{
GetCertificate: b.getTLSServeCertForPort(dport),
},
Handler: http.HandlerFunc(b.serveWebHandler), Handler: http.HandlerFunc(b.serveWebHandler),
BaseContext: func(_ net.Listener) context.Context { BaseContext: func(_ net.Listener) context.Context {
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{ return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
@ -342,92 +340,79 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
}) })
}, },
} }
if tcph.HTTPS() { hs.ServeTLS(netutil.NewOneConnListener(conn, nil), "", "")
hs.TLSConfig = &tls.Config{ return
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))
}
} }
if backDst := tcph.TCPForward(); backDst != "" { if backDst := tcph.TCPForward(); backDst != "" {
return func(conn net.Conn) error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer conn.Close() backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) cancel()
backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst) if err != nil {
cancel() b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
if err != nil { sendRST()
b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err) return
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
} }
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) b.logf("closing TCP conn to port %v (from %v) with actionless TCPPortHandler", dport, srcAddr)
return nil sendRST()
}
func getServeHTTPContext(r *http.Request) (c *serveHTTPContext, ok bool) {
c, ok = r.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext)
return c, ok
} }
func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, at string, ok bool) { func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, at string, ok bool) {
var z ipn.HTTPHandlerView // zero value var z ipn.HTTPHandlerView // zero value
hostname := r.Host
if r.TLS == nil { if r.TLS == nil {
tcd := "." + b.Status().CurrentTailnet.MagicDNSSuffix return z, "", false
if !strings.HasSuffix(hostname, tcd) {
hostname += tcd
}
} else {
hostname = r.TLS.ServerName
} }
sctx, ok := getServeHTTPContext(r) sctx, ok := r.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext)
if !ok { if !ok {
b.logf("[unexpected] localbackend: no serveHTTPContext in request") b.logf("[unexpected] localbackend: no serveHTTPContext in request")
return z, "", false return z, "", false
} }
wsc, ok := b.webServerConfig(hostname, sctx.DestPort) wsc, ok := b.webServerConfig(r.TLS.ServerName, sctx.DestPort)
if !ok { if !ok {
return z, "", false 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 { if h, ok := wsc.Handlers().GetOk(r.URL.Path); ok {
return h, r.URL.Path, true return h, r.URL.Path, true
} }
pth := path.Clean(r.URL.Path) path := path.Clean(r.URL.Path)
for { for {
withSlash := pth + "/" withSlash := path + "/"
if h, ok := wsc.Handlers().GetOk(withSlash); ok { if h, ok := wsc.Handlers().GetOk(withSlash); ok {
return h, withSlash, true return h, withSlash, true
} }
if h, ok := wsc.Handlers().GetOk(pth); ok { if h, ok := wsc.Handlers().GetOk(path); ok {
return h, pth, true return h, path, true
} }
if pth == "/" { if path == "/" {
return z, "", false 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) { Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(u) r.SetURL(u)
r.Out.Host = r.In.Host r.Out.Host = r.In.Host
addProxyForwardedHeaders(r) if c, ok := r.Out.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext); ok {
b.addTailscaleIdentityHeaders(r) r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
}
}, },
Transport: &http.Transport{ Transport: &http.Transport{
DialContext: b.dialer.SystemDial, DialContext: b.dialer.SystemDial,
@ -482,40 +468,6 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.Reverse
return rp, nil 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) { func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
h, mountPoint, ok := b.getServeHandler(r) h, mountPoint, ok := b.getServeHandler(r)
if !ok { if !ok {
@ -648,8 +600,8 @@ func allNumeric(s string) bool {
return s != "" return s != ""
} }
func (b *LocalBackend) webServerConfig(hostname string, port uint16) (c ipn.WebServerConfigView, ok bool) { func (b *LocalBackend) webServerConfig(sniName string, port uint16) (c ipn.WebServerConfigView, ok bool) {
key := ipn.HostPort(fmt.Sprintf("%s:%v", hostname, port)) key := ipn.HostPort(fmt.Sprintf("%s:%v", sniName, port))
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()

View File

@ -10,22 +10,12 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/netip"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"tailscale.com/ipn" "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) { func TestExpandProxyArg(t *testing.T) {
@ -150,7 +140,10 @@ func TestGetServeHandler(t *testing.T) {
}, },
TLS: &tls.ConnectionState{ServerName: serverName}, 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{ req = req.WithContext(context.WithValue(req.Context(), serveHTTPContextKey{}, &serveHTTPContext{
DestPort: port, 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) { func TestServeFileOrDirectory(t *testing.T) {
td := t.TempDir() td := t.TempDir()
writeFile := func(suffix, contents string) { writeFile := func(suffix, contents string) {

View File

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

View File

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

View File

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

View File

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

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