Compare commits
3 Commits
main
...
danderson/
Author | SHA1 | Date |
---|---|---|
![]() |
67a5f45817 | |
![]() |
fe3604c47c | |
![]() |
8620422166 |
|
@ -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 .
|
|
|
@ -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"
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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: |
|
||||||
{
|
{
|
|
@ -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"
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
5
Makefile
5
Makefile
|
@ -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) ..."
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1.45.0
|
1.39.0
|
||||||
|
|
30
api.md
30
api.md
|
@ -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"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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" "$@"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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+
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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
|
||||||
})(),
|
})(),
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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+
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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+
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
|
||||||
}{})
|
|
||||||
|
|
|
@ -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
|
|
||||||
}{})
|
|
||||||
|
|
|
@ -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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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.
|
|
|
@ -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()
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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-
|
||||||
|
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
@ -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 ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
429
go.mod
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
sha256-fgCrmtJs1svFz0Xn7iwLNrbBNlcO6V0yqGPMY0+V1VQ=
|
sha256-LfHkNXQbg7/u6y882gtINuQtwinYakg3abKJTDrrADo=
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
tailscale.go1.21
|
tailscale.go1.20
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
492f6d9d792fa6e4caa388e4d7bab46b48d07ad5
|
ddff070c02790cb571006e820e58cce9627569cf
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}{})
|
}{})
|
||||||
|
|
|
@ -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
|
||||||
}{})
|
}{})
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue