diff --git a/.github/actions/go-cache/action.sh b/.github/actions/go-cache/action.sh index f49d5bb779f4d..5cfafe4767fb2 100755 --- a/.github/actions/go-cache/action.sh +++ b/.github/actions/go-cache/action.sh @@ -23,22 +23,27 @@ if [ -z "${URL:-}" ]; then exit 0 fi -GOPATH=$(command -v go || true) -if [ -z "${GOPATH}" ]; then - if [ ! -f "tool/go" ]; then - echo "Go not available, unable to proceed" - exit 1 +BIN_PATH="$(PATH="$PATH:$HOME/bin" command -v cigocacher || true)" +if [ -z "${BIN_PATH}" ]; then + echo "cigocacher not found in PATH, attempting to build or fetch it" + + GOPATH=$(command -v go || true) + if [ -z "${GOPATH}" ]; then + if [ ! -f "tool/go" ]; then + echo "Go not available, unable to proceed" + exit 1 + fi + GOPATH="./tool/go" fi - GOPATH="./tool/go" -fi -BIN_PATH="${RUNNER_TEMP:-/tmp}/cigocacher$(${GOPATH} env GOEXE)" -if [ -d "cmd/cigocacher" ]; then - echo "cmd/cigocacher found locally, building from local source" - "${GOPATH}" build -o "${BIN_PATH}" ./cmd/cigocacher -else - echo "cmd/cigocacher not found locally, fetching from tailscale.com/cmd/cigocacher" - "${GOPATH}" build -o "${BIN_PATH}" tailscale.com/cmd/cigocacher + BIN_PATH="${RUNNER_TEMP:-/tmp}/cigocacher$(${GOPATH} env GOEXE)" + if [ -d "cmd/cigocacher" ]; then + echo "cmd/cigocacher found locally, building from local source" + "${GOPATH}" build -o "${BIN_PATH}" ./cmd/cigocacher + else + echo "cmd/cigocacher not found locally, fetching from tailscale.com/cmd/cigocacher" + "${GOPATH}" build -o "${BIN_PATH}" tailscale.com/cmd/cigocacher + fi fi CIGOCACHER_TOKEN="$("${BIN_PATH}" --auth --cigocached-url "${URL}" --cigocached-host "${HOST}" )" diff --git a/.github/workflows/checklocks.yml b/.github/workflows/checklocks.yml index ee950b4fc9212..5768cf05af634 100644 --- a/.github/workflows/checklocks.yml +++ b/.github/workflows/checklocks.yml @@ -18,7 +18,7 @@ jobs: runs-on: [ ubuntu-latest ] steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build checklocks run: ./tool/go build -o /tmp/checklocks gvisor.dev/gvisor/tools/checklocks/cmd/checklocks diff --git a/.github/workflows/cigocacher.yml b/.github/workflows/cigocacher.yml index c4dd0c3c509a5..15aec8af90904 100644 --- a/.github/workflows/cigocacher.yml +++ b/.github/workflows/cigocacher.yml @@ -17,14 +17,14 @@ jobs: GOARCH: "${{ matrix.GOARCH }}" CGO_ENABLED: "0" steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build run: | OUT="cigocacher$(./tool/go env GOEXE)" ./tool/go build -o "${OUT}" ./cmd/cigocacher/ tar -zcf cigocacher-${{ matrix.GOOS }}-${{ matrix.GOARCH }}.tar.gz "${OUT}" - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: cigocacher-${{ matrix.GOOS }}-${{ matrix.GOARCH }} path: cigocacher-${{ matrix.GOOS }}-${{ matrix.GOARCH }}.tar.gz @@ -36,7 +36,7 @@ jobs: contents: write steps: - name: Download all artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: pattern: 'cigocacher-*' merge-multiple: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e66d6454a9847..51bae5a068df5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -45,17 +45,17 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Install a more recent Go that understands modern go.mod content. - name: Install Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: go.mod # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/autobuild@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 diff --git a/.github/workflows/docker-base.yml b/.github/workflows/docker-base.yml index a47669f6ade8a..a3eac2c24e691 100644 --- a/.github/workflows/docker-base.yml +++ b/.github/workflows/docker-base.yml @@ -9,7 +9,7 @@ jobs: build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: "build and test" run: | set -e diff --git a/.github/workflows/docker-file-build.yml b/.github/workflows/docker-file-build.yml index 9a56fd05758a9..7ee2468682695 100644 --- a/.github/workflows/docker-file-build.yml +++ b/.github/workflows/docker-file-build.yml @@ -8,6 +8,6 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: "Build Docker image" run: docker build . diff --git a/.github/workflows/flakehub-publish-tagged.yml b/.github/workflows/flakehub-publish-tagged.yml index 8b3f44338026a..c781e30e5154f 100644 --- a/.github/workflows/flakehub-publish-tagged.yml +++ b/.github/workflows/flakehub-publish-tagged.yml @@ -17,10 +17,10 @@ jobs: id-token: "write" contents: "read" steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}" - - uses: DeterminateSystems/nix-installer-action@786fff0690178f1234e4e1fe9b536e94f5433196 # v20 + - uses: DeterminateSystems/nix-installer-action@c5a866b6ab867e88becbed4467b93592bce69f8a # v21 - uses: DeterminateSystems/flakehub-push@71f57208810a5d299fc6545350981de98fdbc860 # v6 with: visibility: "public" diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 684a094e26560..66b8497e65441 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -27,17 +27,17 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: go.mod cache: true - name: golangci-lint - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 + uses: golangci/golangci-lint-action@b7bcab6379029e905e3f389a6bf301f1bc220662 # head as of 2026-03-04 with: - version: v2.4.0 + version: v2.10.1 # Show only new issues if it's a pull request. only-new-issues: true diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index c99cb11d3eff7..2b46aa9b06e57 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Check out code into the Go module directory - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install govulncheck run: ./tool/go install golang.org/x/vuln/cmd/govulncheck@latest diff --git a/.github/workflows/installer.yml b/.github/workflows/installer.yml index d7db30782470b..6fc8913c4e19c 100644 --- a/.github/workflows/installer.yml +++ b/.github/workflows/installer.yml @@ -99,7 +99,7 @@ jobs: contains(matrix.image, 'parrotsec') || contains(matrix.image, 'kalilinux') - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: run installer run: scripts/installer.sh env: diff --git a/.github/workflows/kubemanifests.yaml b/.github/workflows/kubemanifests.yaml index 6812b69d6e702..40734a015dad3 100644 --- a/.github/workflows/kubemanifests.yaml +++ b/.github/workflows/kubemanifests.yaml @@ -17,7 +17,7 @@ jobs: runs-on: [ ubuntu-latest ] steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build and lint Helm chart run: | eval `./tool/go run ./cmd/mkversion` diff --git a/.github/workflows/natlab-integrationtest.yml b/.github/workflows/natlab-integrationtest.yml index 3e87ba4345180..162153cb23293 100644 --- a/.github/workflows/natlab-integrationtest.yml +++ b/.github/workflows/natlab-integrationtest.yml @@ -7,18 +7,24 @@ concurrency: cancel-in-progress: true on: + push: + branches: + - "main" + - "release-branch/*" pull_request: - paths: - - "tstest/integration/nat/nat_test.go" + # all PRs on all branches + merge_group: + branches: + - "main" jobs: natlab-integrationtest: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install qemu run: | - sudo rm /var/lib/man-db/auto-update + sudo rm -f /var/lib/man-db/auto-update sudo apt-get -y update sudo apt-get -y remove man-db sudo apt-get install -y qemu-system-x86 qemu-utils diff --git a/.github/workflows/pin-github-actions.yml b/.github/workflows/pin-github-actions.yml index 7c1816d134cd6..836ae46dbfa89 100644 --- a/.github/workflows/pin-github-actions.yml +++ b/.github/workflows/pin-github-actions.yml @@ -22,7 +22,7 @@ jobs: name: pin-github-actions runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: pin run: make pin-github-actions - name: check for changed workflow files diff --git a/.github/workflows/request-dataplane-review.yml b/.github/workflows/request-dataplane-review.yml index 7ca3b98022ce7..2b66fc7899428 100644 --- a/.github/workflows/request-dataplane-review.yml +++ b/.github/workflows/request-dataplane-review.yml @@ -16,9 +16,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Get access token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: generate-token with: # Get token for app: https://github.com/apps/change-visibility-bot diff --git a/.github/workflows/ssh-integrationtest.yml b/.github/workflows/ssh-integrationtest.yml index 342b8e9362c30..afe2dd2f74683 100644 --- a/.github/workflows/ssh-integrationtest.yml +++ b/.github/workflows/ssh-integrationtest.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run SSH integration tests run: | make sshintegrationtest \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e99e75b22f8a6..4f6068e6e33cd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,7 @@ env: # toplevel directories "src" (for the checked out source code), and "gomodcache" # and other caches as siblings to follow. GOMODCACHE: ${{ github.workspace }}/gomodcache + CMD_GO_USE_GIT_HASH: "true" on: push: @@ -48,7 +49,7 @@ jobs: cache-key: ${{ steps.hash.outputs.key }} steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Compute cache key from go.{mod,sum} @@ -57,7 +58,7 @@ jobs: # See if the cache entry already exists to avoid downloading it # and doing the cache write again. - id: check-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache # relative to workspace; see env note at top of file key: ${{ steps.hash.outputs.key }} @@ -69,7 +70,7 @@ jobs: run: go mod download - name: Cache Go modules if: steps.check-cache.outputs.cache-hit != 'true' - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache # relative to workspace; see env note at top of file key: ${{ steps.hash.outputs.key }} @@ -88,11 +89,11 @@ jobs: - shard: '4/4' steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -126,18 +127,18 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only restoring the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -208,7 +209,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only saving the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -239,18 +240,12 @@ jobs: shard: "2/2" steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: ${{ github.workspace }}/src - - name: Install Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 - with: - go-version-file: ${{ github.workspace }}/src/go.mod - cache: false - - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -268,7 +263,9 @@ jobs: - name: test if: matrix.key != 'win-bench' # skip on bench builder working-directory: src - run: go run ./cmd/testwrapper sharded:${{ matrix.shard }} + run: ./tool/go run ./cmd/testwrapper sharded:${{ matrix.shard }} + env: + NOPWSHDEBUG: "true" # to quiet tool/gocross/gocross-wrapper.ps1 in CI - name: bench all if: matrix.key == 'win-bench' @@ -276,7 +273,9 @@ jobs: # Don't use -bench=. -benchtime=1x. # Somewhere in the layers (powershell?) # the equals signs cause great confusion. - run: go test ./... -bench . -benchtime 1x -run "^$" + run: ./tool/go test ./... -bench . -benchtime 1x -run "^$" + env: + NOPWSHDEBUG: "true" # to quiet tool/gocross/gocross-wrapper.ps1 in CI - name: Print stats shell: pwsh @@ -286,18 +285,62 @@ jobs: run: | Invoke-Expression "$env:GOCACHEPROG --stats" | jq . - win-tool-go: - runs-on: windows-latest + macos: + runs-on: macos-latest needs: gomod-cache - name: Windows (win-tool-go) steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - - name: test-tool-go + - name: Restore Go module cache + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: gomodcache + key: ${{ needs.gomod-cache.outputs.cache-key }} + enableCrossOsArchive: true + - name: Restore Cache + id: restore-cache + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/Library/Caches/go-build + key: ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}-${{ github.job }}-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}-${{ github.job }}- + ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}- + ${{ runner.os }}-go-test- + - name: build test wrapper + working-directory: src + run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper + - name: test all + working-directory: src + run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... + - name: check that no tracked files changed + working-directory: src + run: git diff --no-ext-diff --name-only --exit-code || (echo "Build/test modified the files above."; exit 1) + - name: check that no new files were added + working-directory: src + run: | + # Note: The "error: pathspec..." you see below is normal! + # In the success case in which there are no new untracked files, + # git ls-files complains about the pathspec not matching anything. + # That's OK. It's not worth the effort to suppress. Please ignore it. + if git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*' + then + echo "Build/test created untracked files in the repo (file names above)." + exit 1 + fi + - name: Tidy cache working-directory: src - run: ./tool/go version + run: | + find $(./tool/go env GOCACHE) -type f -mmin +90 -delete + - name: Save Cache + # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. + if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/Library/Caches/go-build + key: ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}-${{ github.job }}-${{ github.run_id }} privileged: needs: gomod-cache @@ -307,11 +350,11 @@ jobs: options: --privileged steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -330,11 +373,11 @@ jobs: if: github.repository == 'tailscale/tailscale' steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -386,18 +429,18 @@ jobs: runs-on: ubuntu-24.04 steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only restoring the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -432,7 +475,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only saving the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -447,11 +490,11 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -485,18 +528,18 @@ jobs: runs-on: ubuntu-24.04 steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only restoring the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -524,7 +567,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only saving the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -541,7 +584,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src # Super minimal Android build that doesn't even use CGO and doesn't build everything that's needed @@ -549,7 +592,7 @@ jobs: # some Android breakages early. # TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482 - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -566,18 +609,18 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only restoring the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -610,7 +653,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only saving the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -624,11 +667,11 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set GOMODCACHE env run: echo "GOMODCACHE=$HOME/.cache/go-mod" >> $GITHUB_ENV - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -653,9 +696,9 @@ jobs: steps: - name: build fuzzers id: build - # As of 21 October 2025, this repo doesn't tag releases, so this commit + # As of 12 February 2026, this repo doesn't tag releases, so this commit # hash is just the tip of master. - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@1242ccb5b6352601e73c00f189ac2ae397242264 + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@f277aafb36f358582fdb24a41a9a52f2e097a2fd # continue-on-error makes steps.build.conclusion be 'success' even if # steps.build.outcome is 'failure'. This means this step does not # contribute to the job's overall pass/fail evaluation. @@ -685,9 +728,9 @@ jobs: # report a failure because TS_FUZZ_CURRENTLY_BROKEN is set to the wrong # value. if: steps.build.outcome == 'success' - # As of 21 October 2025, this repo doesn't tag releases, so this commit + # As of 12 February 2026, this repo doesn't tag releases, so this commit # hash is just the tip of master. - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@1242ccb5b6352601e73c00f189ac2ae397242264 + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@f277aafb36f358582fdb24a41a9a52f2e097a2fd with: oss-fuzz-project-name: 'tailscale' fuzz-seconds: 150 @@ -698,7 +741,7 @@ jobs: run: | echo "artifacts_path=$(realpath .)" >> $GITHUB_ENV - name: upload crash - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: steps.run.outcome != 'success' && steps.build.outcome == 'success' with: name: artifacts @@ -709,13 +752,13 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Set GOMODCACHE env run: echo "GOMODCACHE=$HOME/.cache/go-mod" >> $GITHUB_ENV - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -729,11 +772,11 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -748,21 +791,21 @@ jobs: echo git diff --name-only --exit-code || (echo "The files above need updating. Please run 'go generate'."; exit 1) - go_mod_tidy: + make_tidy: runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - - name: check that 'go mod tidy' is clean + - name: check that 'make tidy' is clean working-directory: src run: | make tidy @@ -775,11 +818,11 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -829,11 +872,11 @@ jobs: steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -850,10 +893,11 @@ jobs: notify_slack: if: always() # Any of these jobs failing causes a slack notification. - needs: + needs: - android - test - windows + - macos - vm - cross - ios @@ -862,7 +906,7 @@ jobs: - fuzz - depaware - go_generate - - go_mod_tidy + - make_tidy - licenses - staticcheck runs-on: ubuntu-24.04 @@ -899,6 +943,7 @@ jobs: - android - test - windows + - macos - vm - cross - ios @@ -907,7 +952,7 @@ jobs: - fuzz - depaware - go_generate - - go_mod_tidy + - make_tidy - licenses - staticcheck steps: @@ -931,7 +976,7 @@ jobs: - tailscale_go - depaware - go_generate - - go_mod_tidy + - make_tidy - licenses - staticcheck steps: @@ -948,6 +993,7 @@ jobs: - check_mergeability_strict - test - windows + - macos - vm - wasm - fuzz diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 69c954384e9bc..4c0da7831b5ba 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -21,13 +21,13 @@ jobs: steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run update-flakes run: ./update-flake.sh - name: Get access token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: generate-token with: # Get token for app: https://github.com/apps/tailscale-code-updater @@ -35,7 +35,7 @@ jobs: private-key: ${{ secrets.CODE_UPDATER_APP_PRIVATE_KEY }} - name: Send pull request - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 #v8.0.0 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 #v8.1.0 with: token: ${{ steps.generate-token.outputs.token }} author: Flakes Updater diff --git a/.github/workflows/update-webclient-prebuilt.yml b/.github/workflows/update-webclient-prebuilt.yml index c302e4f2091ca..a3d78e1a5b4a8 100644 --- a/.github/workflows/update-webclient-prebuilt.yml +++ b/.github/workflows/update-webclient-prebuilt.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run go get run: | @@ -23,7 +23,7 @@ jobs: ./tool/go mod tidy - name: Get access token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: generate-token with: # Get token for app: https://github.com/apps/tailscale-code-updater @@ -32,7 +32,7 @@ jobs: - name: Send pull request id: pull-request - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 #v8.0.0 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 #v8.1.0 with: token: ${{ steps.generate-token.outputs.token }} author: OSS Updater diff --git a/.github/workflows/vet.yml b/.github/workflows/vet.yml index b7862889daa7f..574852e62beee 100644 --- a/.github/workflows/vet.yml +++ b/.github/workflows/vet.yml @@ -6,6 +6,7 @@ env: # toplevel directories "src" (for the checked out source code), and "gomodcache" # and other caches as siblings to follow. GOMODCACHE: ${{ github.workspace }}/gomodcache + CMD_GO_USE_GIT_HASH: "true" on: push: @@ -25,7 +26,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src diff --git a/.github/workflows/webclient.yml b/.github/workflows/webclient.yml index 4fc19901d0ef6..1a65eacf56414 100644 --- a/.github/workflows/webclient.yml +++ b/.github/workflows/webclient.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install deps run: ./tool/yarn --cwd client/web - name: Run lint diff --git a/.gitignore b/.gitignore index 3941fd06ef6d5..4bfabc80f0415 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ client/web/build/assets # Ignore personal IntelliJ settings .idea/ + +# Ignore syncthing state directory. +/.stfolder diff --git a/.stignore b/.stignore new file mode 120000 index 0000000000000..3e4e48b0b5fe6 --- /dev/null +++ b/.stignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 03d5932c04746..0000000000000 --- a/AUTHORS +++ /dev/null @@ -1,17 +0,0 @@ -# This is the official list of Tailscale -# authors for copyright purposes. -# -# Names should be added to this file as one of -# Organization's name -# Individual's name -# Individual's name -# -# Please keep the list sorted. -# -# You do not need to add entries to this list, and we don't actively -# populate this list. If you do want to be acknowledged explicitly as -# a copyright holder, though, then please send a PR referencing your -# earlier contributions and clarifying whether it's you or your -# company that owns the rights to your contribution. - -Tailscale Inc. diff --git a/Dockerfile b/Dockerfile index 7122f99782fec..ee12922f2d719 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # Note that this Dockerfile is currently NOT used to build any of the published @@ -36,7 +36,7 @@ # $ docker exec tailscaled tailscale status -FROM golang:1.25-alpine AS build-env +FROM golang:1.26-alpine AS build-env WORKDIR /go/src/tailscale diff --git a/Dockerfile.base b/Dockerfile.base index 9b7ae512b9945..295950c461339 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause FROM alpine:3.22 diff --git a/LICENSE b/LICENSE index 394db19e4aa5c..ed6e4bb6d6ba6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020 Tailscale Inc & AUTHORS. +Copyright (c) 2020 Tailscale Inc & contributors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/appc/appconnector.go b/appc/appconnector.go index d41f9e8ba6357..ee495bd10f100 100644 --- a/appc/appconnector.go +++ b/appc/appconnector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package appc implements App Connectors. diff --git a/appc/appconnector_test.go b/appc/appconnector_test.go index 5c362d6fd1217..a860da6a7c737 100644 --- a/appc/appconnector_test.go +++ b/appc/appconnector_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appc diff --git a/appc/appctest/appctest.go b/appc/appctest/appctest.go index 9726a2b97d72b..c5eabf6761ec3 100644 --- a/appc/appctest/appctest.go +++ b/appc/appctest/appctest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package appctest contains code to help test App Connectors. diff --git a/appc/conn25.go b/appc/conn25.go index b4890c26c0268..08b2a1ade6826 100644 --- a/appc/conn25.go +++ b/appc/conn25.go @@ -1,110 +1,72 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appc import ( - "net/netip" - "sync" + "cmp" + "slices" "tailscale.com/tailcfg" + "tailscale.com/types/appctype" + "tailscale.com/util/mak" + "tailscale.com/util/set" ) -// Conn25 holds the developing state for the as yet nascent next generation app connector. -// There is currently (2025-12-08) no actual app connecting functionality. -type Conn25 struct { - mu sync.Mutex - transitIPs map[tailcfg.NodeID]map[netip.Addr]netip.Addr -} - -const dupeTransitIPMessage = "Duplicate transit address in ConnectorTransitIPRequest" +const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experimental" -// HandleConnectorTransitIPRequest creates a ConnectorTransitIPResponse in response to a ConnectorTransitIPRequest. -// It updates the connectors mapping of TransitIP->DestinationIP per peer (tailcfg.NodeID). -// If a peer has stored this mapping in the connector Conn25 will route traffic to TransitIPs to DestinationIPs for that peer. -func (c *Conn25) HandleConnectorTransitIPRequest(nid tailcfg.NodeID, ctipr ConnectorTransitIPRequest) ConnectorTransitIPResponse { - resp := ConnectorTransitIPResponse{} - seen := map[netip.Addr]bool{} - for _, each := range ctipr.TransitIPs { - if seen[each.TransitIP] { - resp.TransitIPs = append(resp.TransitIPs, TransitIPResponse{ - Code: OtherFailure, - Message: dupeTransitIPMessage, - }) +// PickSplitDNSPeers looks at the netmap peers capabilities and finds which peers +// want to be connectors for which domains. +func PickSplitDNSPeers(hasCap func(c tailcfg.NodeCapability) bool, self tailcfg.NodeView, peers map[tailcfg.NodeID]tailcfg.NodeView) map[string][]tailcfg.NodeView { + var m map[string][]tailcfg.NodeView + if !hasCap(AppConnectorsExperimentalAttrName) { + return m + } + apps, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.AppConnectorAttr](self.CapMap(), AppConnectorsExperimentalAttrName) + if err != nil { + return m + } + tagToDomain := make(map[string][]string) + for _, app := range apps { + for _, tag := range app.Connectors { + tagToDomain[tag] = append(tagToDomain[tag], app.Domains...) + } + } + // NodeIDs are Comparable, and we have a map of NodeID to NodeView anyway, so + // use a Set of NodeIDs to deduplicate, and populate into a []NodeView later. + var work map[string]set.Set[tailcfg.NodeID] + for _, peer := range peers { + if !peer.Valid() || !peer.Hostinfo().Valid() { + continue + } + if isConn, _ := peer.Hostinfo().AppConnector().Get(); !isConn { continue } - tipresp := c.handleTransitIPRequest(nid, each) - seen[each.TransitIP] = true - resp.TransitIPs = append(resp.TransitIPs, tipresp) + for _, t := range peer.Tags().All() { + domains := tagToDomain[t] + for _, domain := range domains { + if work[domain] == nil { + mak.Set(&work, domain, set.Set[tailcfg.NodeID]{}) + } + work[domain].Add(peer.ID()) + } + } } - return resp -} -func (c *Conn25) handleTransitIPRequest(nid tailcfg.NodeID, tipr TransitIPRequest) TransitIPResponse { - c.mu.Lock() - defer c.mu.Unlock() - if c.transitIPs == nil { - c.transitIPs = make(map[tailcfg.NodeID]map[netip.Addr]netip.Addr) - } - peerMap, ok := c.transitIPs[nid] - if !ok { - peerMap = make(map[netip.Addr]netip.Addr) - c.transitIPs[nid] = peerMap + // Populate m. Make a []tailcfg.NodeView from []tailcfg.NodeID using the peers map. + // And sort it to our preference. + for domain, ids := range work { + nodes := make([]tailcfg.NodeView, 0, ids.Len()) + for id := range ids { + nodes = append(nodes, peers[id]) + } + // The ordering of the nodes in the map vals is semantic (dnsConfigForNetmap uses the first node it can + // get a peer api url for as its split dns target). We can think of it as a preference order, except that + // we don't (currently 2026-01-14) have any preference over which node is chosen. + slices.SortFunc(nodes, func(a, b tailcfg.NodeView) int { + return cmp.Compare(a.ID(), b.ID()) + }) + mak.Set(&m, domain, nodes) } - peerMap[tipr.TransitIP] = tipr.DestinationIP - return TransitIPResponse{} -} - -func (c *Conn25) transitIPTarget(nid tailcfg.NodeID, tip netip.Addr) netip.Addr { - c.mu.Lock() - defer c.mu.Unlock() - return c.transitIPs[nid][tip] -} - -// TransitIPRequest details a single TransitIP allocation request from a client to a -// connector. -type TransitIPRequest struct { - // TransitIP is the intermediate destination IP that will be received at this - // connector and will be replaced by DestinationIP when performing DNAT. - TransitIP netip.Addr `json:"transitIP,omitzero"` - - // DestinationIP is the final destination IP that connections to the TransitIP - // should be mapped to when performing DNAT. - DestinationIP netip.Addr `json:"destinationIP,omitzero"` -} - -// ConnectorTransitIPRequest is the request body for a PeerAPI request to -// /connector/transit-ip and can include zero or more TransitIP allocation requests. -type ConnectorTransitIPRequest struct { - // TransitIPs is the list of requested mappings. - TransitIPs []TransitIPRequest `json:"transitIPs,omitempty"` -} - -// TransitIPResponseCode appears in TransitIPResponse and signifies success or failure status. -type TransitIPResponseCode int - -const ( - // OK indicates that the mapping was created as requested. - OK TransitIPResponseCode = 0 - - // OtherFailure indicates that the mapping failed for a reason that does not have - // another relevant [TransitIPResponsecode]. - OtherFailure TransitIPResponseCode = 1 -) - -// TransitIPResponse is the response to a TransitIPRequest -type TransitIPResponse struct { - // Code is an error code indicating success or failure of the [TransitIPRequest]. - Code TransitIPResponseCode `json:"code,omitzero"` - // Message is an error message explaining what happened, suitable for logging but - // not necessarily suitable for displaying in a UI to non-technical users. It - // should be empty when [Code] is [OK]. - Message string `json:"message,omitzero"` -} - -// ConnectorTransitIPResponse is the response to a ConnectorTransitIPRequest -type ConnectorTransitIPResponse struct { - // TransitIPs is the list of outcomes for each requested mapping. Elements - // correspond to the order of [ConnectorTransitIPRequest.TransitIPs]. - TransitIPs []TransitIPResponse `json:"transitIPs,omitempty"` + return m } diff --git a/appc/conn25_test.go b/appc/conn25_test.go index ab6c4be37c592..a9cb0fb7ebf9c 100644 --- a/appc/conn25_test.go +++ b/appc/conn25_test.go @@ -1,188 +1,133 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appc import ( - "net/netip" + "encoding/json" + "reflect" "testing" "tailscale.com/tailcfg" + "tailscale.com/types/appctype" + "tailscale.com/types/opt" ) -// TestHandleConnectorTransitIPRequestZeroLength tests that if sent a -// ConnectorTransitIPRequest with 0 TransitIPRequests, we respond with a -// ConnectorTransitIPResponse with 0 TransitIPResponses. -func TestHandleConnectorTransitIPRequestZeroLength(t *testing.T) { - c := &Conn25{} - req := ConnectorTransitIPRequest{} - nid := tailcfg.NodeID(1) - - resp := c.HandleConnectorTransitIPRequest(nid, req) - if len(resp.TransitIPs) != 0 { - t.Fatalf("n TransitIPs in response: %d, want 0", len(resp.TransitIPs)) - } -} - -// TestHandleConnectorTransitIPRequestStoresAddr tests that if sent a -// request with a transit addr and a destination addr we store that mapping -// and can retrieve it. If sent another req with a different dst for that transit addr -// we store that instead. -func TestHandleConnectorTransitIPRequestStoresAddr(t *testing.T) { - c := &Conn25{} - nid := tailcfg.NodeID(1) - tip := netip.MustParseAddr("0.0.0.1") - dip := netip.MustParseAddr("1.2.3.4") - dip2 := netip.MustParseAddr("1.2.3.5") - mr := func(t, d netip.Addr) ConnectorTransitIPRequest { - return ConnectorTransitIPRequest{ - TransitIPs: []TransitIPRequest{ - {TransitIP: t, DestinationIP: d}, - }, +func TestPickSplitDNSPeers(t *testing.T) { + getBytesForAttr := func(name string, domains []string, tags []string) []byte { + attr := appctype.AppConnectorAttr{ + Name: name, + Domains: domains, + Connectors: tags, } + bs, err := json.Marshal(attr) + if err != nil { + t.Fatalf("test setup: %v", err) + } + return bs } + appOneBytes := getBytesForAttr("app1", []string{"example.com"}, []string{"tag:one"}) + appTwoBytes := getBytesForAttr("app2", []string{"a.example.com"}, []string{"tag:two"}) + appThreeBytes := getBytesForAttr("app3", []string{"woo.b.example.com", "hoo.b.example.com"}, []string{"tag:three1", "tag:three2"}) + appFourBytes := getBytesForAttr("app4", []string{"woo.b.example.com", "c.example.com"}, []string{"tag:four1", "tag:four2"}) - resp := c.HandleConnectorTransitIPRequest(nid, mr(tip, dip)) - if len(resp.TransitIPs) != 1 { - t.Fatalf("n TransitIPs in response: %d, want 1", len(resp.TransitIPs)) - } - got := resp.TransitIPs[0].Code - if got != TransitIPResponseCode(0) { - t.Fatalf("TransitIP Code: %d, want 0", got) - } - gotAddr := c.transitIPTarget(nid, tip) - if gotAddr != dip { - t.Fatalf("Connector stored destination for tip: %v, want %v", gotAddr, dip) - } - - // mapping can be overwritten - resp2 := c.HandleConnectorTransitIPRequest(nid, mr(tip, dip2)) - if len(resp2.TransitIPs) != 1 { - t.Fatalf("n TransitIPs in response: %d, want 1", len(resp2.TransitIPs)) - } - got2 := resp.TransitIPs[0].Code - if got2 != TransitIPResponseCode(0) { - t.Fatalf("TransitIP Code: %d, want 0", got2) - } - gotAddr2 := c.transitIPTarget(nid, tip) - if gotAddr2 != dip2 { - t.Fatalf("Connector stored destination for tip: %v, want %v", gotAddr, dip2) - } -} + makeNodeView := func(id tailcfg.NodeID, name string, tags []string) tailcfg.NodeView { + return (&tailcfg.Node{ + ID: id, + Name: name, + Tags: tags, + Hostinfo: (&tailcfg.Hostinfo{AppConnector: opt.NewBool(true)}).View(), + }).View() + } + nvp1 := makeNodeView(1, "p1", []string{"tag:one"}) + nvp2 := makeNodeView(2, "p2", []string{"tag:four1", "tag:four2"}) + nvp3 := makeNodeView(3, "p3", []string{"tag:two", "tag:three1"}) + nvp4 := makeNodeView(4, "p4", []string{"tag:two", "tag:three2", "tag:four2"}) -// TestHandleConnectorTransitIPRequestMultipleTIP tests that we can -// get a req with multiple mappings and we store them all. Including -// multiple transit addrs for the same destination. -func TestHandleConnectorTransitIPRequestMultipleTIP(t *testing.T) { - c := &Conn25{} - nid := tailcfg.NodeID(1) - tip := netip.MustParseAddr("0.0.0.1") - tip2 := netip.MustParseAddr("0.0.0.2") - tip3 := netip.MustParseAddr("0.0.0.3") - dip := netip.MustParseAddr("1.2.3.4") - dip2 := netip.MustParseAddr("1.2.3.5") - req := ConnectorTransitIPRequest{ - TransitIPs: []TransitIPRequest{ - {TransitIP: tip, DestinationIP: dip}, - {TransitIP: tip2, DestinationIP: dip2}, - // can store same dst addr for multiple transit addrs - {TransitIP: tip3, DestinationIP: dip}, + for _, tt := range []struct { + name string + want map[string][]tailcfg.NodeView + peers []tailcfg.NodeView + config []tailcfg.RawMessage + }{ + { + name: "empty", }, - } - resp := c.HandleConnectorTransitIPRequest(nid, req) - if len(resp.TransitIPs) != 3 { - t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) - } - - for i := 0; i < 3; i++ { - got := resp.TransitIPs[i].Code - if got != TransitIPResponseCode(0) { - t.Fatalf("i=%d TransitIP Code: %d, want 0", i, got) - } - } - gotAddr1 := c.transitIPTarget(nid, tip) - if gotAddr1 != dip { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip, gotAddr1, dip) - } - gotAddr2 := c.transitIPTarget(nid, tip2) - if gotAddr2 != dip2 { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip2, gotAddr2, dip2) - } - gotAddr3 := c.transitIPTarget(nid, tip3) - if gotAddr3 != dip { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip3, gotAddr3, dip) - } -} - -// TestHandleConnectorTransitIPRequestSameTIP tests that if we get -// a req that has more than one TransitIPRequest for the same transit addr -// only the first is stored, and the subsequent ones get an error code and -// message in the response. -func TestHandleConnectorTransitIPRequestSameTIP(t *testing.T) { - c := &Conn25{} - nid := tailcfg.NodeID(1) - tip := netip.MustParseAddr("0.0.0.1") - tip2 := netip.MustParseAddr("0.0.0.2") - dip := netip.MustParseAddr("1.2.3.4") - dip2 := netip.MustParseAddr("1.2.3.5") - dip3 := netip.MustParseAddr("1.2.3.6") - req := ConnectorTransitIPRequest{ - TransitIPs: []TransitIPRequest{ - {TransitIP: tip, DestinationIP: dip}, - // cannot have dupe TransitIPs in one ConnectorTransitIPRequest - {TransitIP: tip, DestinationIP: dip2}, - {TransitIP: tip2, DestinationIP: dip3}, + { + name: "bad-config", // bad config should return a nil map rather than error. + config: []tailcfg.RawMessage{tailcfg.RawMessage(`hey`)}, }, - } - - resp := c.HandleConnectorTransitIPRequest(nid, req) - if len(resp.TransitIPs) != 3 { - t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) - } - - got := resp.TransitIPs[0].Code - if got != TransitIPResponseCode(0) { - t.Fatalf("i=0 TransitIP Code: %d, want 0", got) - } - msg := resp.TransitIPs[0].Message - if msg != "" { - t.Fatalf("i=0 TransitIP Message: \"%s\", want \"%s\"", msg, "") - } - got1 := resp.TransitIPs[1].Code - if got1 != TransitIPResponseCode(1) { - t.Fatalf("i=1 TransitIP Code: %d, want 1", got1) - } - msg1 := resp.TransitIPs[1].Message - if msg1 != dupeTransitIPMessage { - t.Fatalf("i=1 TransitIP Message: \"%s\", want \"%s\"", msg1, dupeTransitIPMessage) - } - got2 := resp.TransitIPs[2].Code - if got2 != TransitIPResponseCode(0) { - t.Fatalf("i=2 TransitIP Code: %d, want 0", got2) - } - msg2 := resp.TransitIPs[2].Message - if msg2 != "" { - t.Fatalf("i=2 TransitIP Message: \"%s\", want \"%s\"", msg, "") - } - - gotAddr1 := c.transitIPTarget(nid, tip) - if gotAddr1 != dip { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip, gotAddr1, dip) - } - gotAddr2 := c.transitIPTarget(nid, tip2) - if gotAddr2 != dip3 { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip2, gotAddr2, dip3) - } -} - -// TestGetDstIPUnknownTIP tests that unknown transit addresses can be looked up without problem. -func TestTransitIPTargetUnknownTIP(t *testing.T) { - c := &Conn25{} - nid := tailcfg.NodeID(1) - tip := netip.MustParseAddr("0.0.0.1") - got := c.transitIPTarget(nid, tip) - want := netip.Addr{} - if got != want { - t.Fatalf("Unknown transit addr, want: %v, got %v", want, got) + { + name: "no-peers", + config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)}, + }, + { + name: "peers-that-are-not-connectors", + config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)}, + peers: []tailcfg.NodeView{ + (&tailcfg.Node{ + ID: 5, + Name: "p5", + Tags: []string{"tag:one"}, + }).View(), + (&tailcfg.Node{ + ID: 6, + Name: "p6", + Tags: []string{"tag:one"}, + }).View(), + }, + }, + { + name: "peers-that-dont-match-tags", + config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)}, + peers: []tailcfg.NodeView{ + makeNodeView(5, "p5", []string{"tag:seven"}), + makeNodeView(6, "p6", nil), + }, + }, + { + name: "matching-tagged-connector-peers", + config: []tailcfg.RawMessage{ + tailcfg.RawMessage(appOneBytes), + tailcfg.RawMessage(appTwoBytes), + tailcfg.RawMessage(appThreeBytes), + tailcfg.RawMessage(appFourBytes), + }, + peers: []tailcfg.NodeView{ + nvp1, + nvp2, + nvp3, + nvp4, + makeNodeView(5, "p5", nil), + }, + want: map[string][]tailcfg.NodeView{ + // p5 has no matching tags and so doesn't appear + "example.com": {nvp1}, + "a.example.com": {nvp3, nvp4}, + "woo.b.example.com": {nvp2, nvp3, nvp4}, + "hoo.b.example.com": {nvp3, nvp4}, + "c.example.com": {nvp2, nvp4}, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + selfNode := &tailcfg.Node{} + if tt.config != nil { + selfNode.CapMap = tailcfg.NodeCapMap{ + tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): tt.config, + } + } + selfView := selfNode.View() + peers := map[tailcfg.NodeID]tailcfg.NodeView{} + for _, p := range tt.peers { + peers[p.ID()] = p + } + got := PickSplitDNSPeers(func(_ tailcfg.NodeCapability) bool { + return true + }, selfView, peers) + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("got %v, want %v", got, tt.want) + } + }) } } diff --git a/appc/observe.go b/appc/observe.go index 06dc04f9dcfdf..3cb2db662b564 100644 --- a/appc/observe.go +++ b/appc/observe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_appconnectors diff --git a/appc/observe_disabled.go b/appc/observe_disabled.go index 45aa285eaa758..743c28a590f8e 100644 --- a/appc/observe_disabled.go +++ b/appc/observe_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_appconnectors diff --git a/assert_ts_toolchain_match.go b/assert_ts_toolchain_match.go index 40b24b334674f..4df0eeb1570d3 100644 --- a/assert_ts_toolchain_match.go +++ b/assert_ts_toolchain_match.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go @@ -17,7 +17,10 @@ func init() { panic("binary built with tailscale_go build tag but failed to read build info or find tailscale.toolchain.rev in build info") } want := strings.TrimSpace(GoToolchainRev) - if tsRev != want { + // Also permit the "next" toolchain rev, which is used in the main branch and will eventually become the new "current" rev. + // This allows building with TS_GO_NEXT=1 and then running the resulting binary without TS_GO_NEXT=1. + wantAlt := strings.TrimSpace(GoToolchainNextRev) + if tsRev != want && tsRev != wantAlt { if os.Getenv("TS_PERMIT_TOOLCHAIN_MISMATCH") == "1" { fmt.Fprintf(os.Stderr, "tailscale.toolchain.rev = %q, want %q; but ignoring due to TS_PERMIT_TOOLCHAIN_MISMATCH=1\n", tsRev, want) return diff --git a/atomicfile/atomicfile.go b/atomicfile/atomicfile.go index 9cae9bb750fa8..1fa4c0641f74e 100644 --- a/atomicfile/atomicfile.go +++ b/atomicfile/atomicfile.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package atomicfile contains code related to writing to filesystems diff --git a/atomicfile/atomicfile_notwindows.go b/atomicfile/atomicfile_notwindows.go index 1ce2bb8acda7a..7104ddd5d9ff6 100644 --- a/atomicfile/atomicfile_notwindows.go +++ b/atomicfile/atomicfile_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/atomicfile/atomicfile_test.go b/atomicfile/atomicfile_test.go index a081c90409788..6dbf4eb430372 100644 --- a/atomicfile/atomicfile_test.go +++ b/atomicfile/atomicfile_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !windows diff --git a/atomicfile/atomicfile_windows.go b/atomicfile/atomicfile_windows.go index c67762df2b56c..d9c6ecf32ac5e 100644 --- a/atomicfile/atomicfile_windows.go +++ b/atomicfile/atomicfile_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package atomicfile diff --git a/atomicfile/atomicfile_windows_test.go b/atomicfile/atomicfile_windows_test.go index 4dec1493e0224..8748fc324f61a 100644 --- a/atomicfile/atomicfile_windows_test.go +++ b/atomicfile/atomicfile_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package atomicfile diff --git a/atomicfile/mksyscall.go b/atomicfile/mksyscall.go index d8951a77c5ac6..2b0e4f9e58939 100644 --- a/atomicfile/mksyscall.go +++ b/atomicfile/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package atomicfile diff --git a/chirp/chirp.go b/chirp/chirp.go index 9653877221778..ed87542bc9a93 100644 --- a/chirp/chirp.go +++ b/chirp/chirp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package chirp implements a client to communicate with the BIRD Internet diff --git a/chirp/chirp_test.go b/chirp/chirp_test.go index c545c277d6e87..eedc17f48afa9 100644 --- a/chirp/chirp_test.go +++ b/chirp/chirp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package chirp diff --git a/client/freedesktop/freedesktop.go b/client/freedesktop/freedesktop.go new file mode 100644 index 0000000000000..6ed1e8ccf88fc --- /dev/null +++ b/client/freedesktop/freedesktop.go @@ -0,0 +1,43 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package freedesktop provides helpers for freedesktop systems. +package freedesktop + +import "strings" + +const needsEscape = " \t\n\"'\\><~|&;$*?#()`" + +var escaper = strings.NewReplacer(`"`, `\"`, "`", "\\`", `$`, `\$`, `\`, `\\`) + +// Quote quotes according to the Desktop Entry Specification, as below: +// +// Arguments may be quoted in whole. If an argument contains a reserved +// character the argument must be quoted. The rules for quoting of arguments is +// also applicable to the executable name or path of the executable program as +// provided. +// +// Quoting must be done by enclosing the argument between double quotes and +// escaping the double quote character, backtick character ("`"), dollar sign +// ("$") and backslash character ("\") by preceding it with an additional +// backslash character. Implementations must undo quoting before expanding field +// codes and before passing the argument to the executable program. Reserved +// characters are space (" "), tab, newline, double quote, single quote ("'"), +// backslash character ("\"), greater-than sign (">"), less-than sign ("<"), +// tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon (";"), dollar +// sign ("$"), asterisk ("*"), question mark ("?"), hash mark ("#"), parenthesis +// ("(") and (")") and backtick character ("`"). +func Quote(s string) string { + if s == "" { + return `""` + } + if !strings.ContainsAny(s, needsEscape) { + return s + } + + var b strings.Builder + b.WriteString(`"`) + escaper.WriteString(&b, s) + b.WriteString(`"`) + return b.String() +} diff --git a/client/freedesktop/freedesktop_test.go b/client/freedesktop/freedesktop_test.go new file mode 100644 index 0000000000000..07a1104f36940 --- /dev/null +++ b/client/freedesktop/freedesktop_test.go @@ -0,0 +1,145 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package freedesktop + +import ( + "strings" + "testing" +) + +func TestEscape(t *testing.T) { + tests := []struct { + name, input, want string + }{ + { + name: "no illegal chars", + input: "/home/user", + want: "/home/user", + }, + { + name: "empty string", + input: "", + want: "\"\"", + }, + { + name: "space", + input: " ", + want: "\" \"", + }, + { + name: "tab", + input: "\t", + want: "\"\t\"", + }, + { + name: "newline", + input: "\n", + want: "\"\n\"", + }, + { + name: "double quote", + input: "\"", + want: "\"\\\"\"", + }, + { + name: "single quote", + input: "'", + want: "\"'\"", + }, + { + name: "backslash", + input: "\\", + want: "\"\\\\\"", + }, + { + name: "greater than", + input: ">", + want: "\">\"", + }, + { + name: "less than", + input: "<", + want: "\"<\"", + }, + { + name: "tilde", + input: "~", + want: "\"~\"", + }, + { + name: "pipe", + input: "|", + want: "\"|\"", + }, + { + name: "ampersand", + input: "&", + want: "\"&\"", + }, + { + name: "semicolon", + input: ";", + want: "\";\"", + }, + { + name: "dollar", + input: "$", + want: "\"\\$\"", + }, + { + name: "asterisk", + input: "*", + want: "\"*\"", + }, + { + name: "question mark", + input: "?", + want: "\"?\"", + }, + { + name: "hash", + input: "#", + want: "\"#\"", + }, + { + name: "open paren", + input: "(", + want: "\"(\"", + }, + { + name: "close paren", + input: ")", + want: "\")\"", + }, + { + name: "backtick", + input: "`", + want: "\"\\`\"", + }, + { + name: "char without escape", + input: "/home/user\t", + want: "\"/home/user\t\"", + }, + { + name: "char with escape", + input: "/home/user\\", + want: "\"/home/user\\\\\"", + }, + { + name: "all illegal chars", + input: "/home/user" + needsEscape, + want: "\"/home/user \t\n\\\"'\\\\><~|&;\\$*?#()\\`\"", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Quote(tt.input) + if strings.Compare(got, tt.want) != 0 { + t.Errorf("expected %s, got %s", tt.want, got) + } + }) + } +} diff --git a/client/local/cert.go b/client/local/cert.go index bfaac7303297b..701bfe026ceed 100644 --- a/client/local/cert.go +++ b/client/local/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_acme diff --git a/client/local/debugportmapper.go b/client/local/debugportmapper.go index 04ed1c109a54f..1cbb3ee0a303e 100644 --- a/client/local/debugportmapper.go +++ b/client/local/debugportmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debugportmapper diff --git a/client/local/local.go b/client/local/local.go index 195a91b1ef4a9..a7b8b83b10a77 100644 --- a/client/local/local.go +++ b/client/local/local.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package local contains a Go client for the Tailscale LocalAPI. @@ -446,6 +446,11 @@ func (lc *Client) EventBusGraph(ctx context.Context) ([]byte, error) { return lc.get200(ctx, "/localapi/v0/debug-bus-graph") } +// EventBusQueues returns a JSON snapshot of event bus queue depths per client. +func (lc *Client) EventBusQueues(ctx context.Context) ([]byte, error) { + return lc.get200(ctx, "/localapi/v0/debug-bus-queues") +} + // StreamBusEvents returns an iterator of Tailscale bus events as they arrive. // Each pair is a valid event and a nil error, or a zero event a non-nil error. // In case of error, the iterator ends after the pair reporting the error. @@ -813,25 +818,6 @@ func (lc *Client) CheckUDPGROForwarding(ctx context.Context) error { return nil } -// CheckReversePathFiltering asks the local Tailscale daemon whether strict -// reverse path filtering is enabled, which would break exit node usage on Linux. -func (lc *Client) CheckReversePathFiltering(ctx context.Context) error { - body, err := lc.get200(ctx, "/localapi/v0/check-reverse-path-filtering") - if err != nil { - return err - } - var jres struct { - Warning string - } - if err := json.Unmarshal(body, &jres); err != nil { - return fmt.Errorf("invalid JSON from check-reverse-path-filtering: %w", err) - } - if jres.Warning != "" { - return errors.New(jres.Warning) - } - return nil -} - // SetUDPGROForwarding enables UDP GRO forwarding for the main interface of this // node. This can be done to improve performance of tailnet nodes acting as exit // nodes or subnet routers. diff --git a/client/local/local_test.go b/client/local/local_test.go index 0e01e74cd1813..a5377fbd677a9 100644 --- a/client/local/local_test.go +++ b/client/local/local_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/local/serve.go b/client/local/serve.go index 51d15e7e5439b..7f9a16a03f825 100644 --- a/client/local/serve.go +++ b/client/local/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/client/local/syspolicy.go b/client/local/syspolicy.go index 6eff177833786..49708fa154d9a 100644 --- a/client/local/syspolicy.go +++ b/client/local/syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/client/local/tailnetlock.go b/client/local/tailnetlock.go index 9d37d2f3553d5..0084cb42e3ab0 100644 --- a/client/local/tailnetlock.go +++ b/client/local/tailnetlock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/client/systray/logo.go b/client/systray/logo.go index 3467d1b741f93..4cd19778dc3a7 100644 --- a/client/systray/logo.go +++ b/client/systray/logo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo || !darwin diff --git a/client/systray/startup-creator.go b/client/systray/startup-creator.go index cb354856d7f97..369190012ce6c 100644 --- a/client/systray/startup-creator.go +++ b/client/systray/startup-creator.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo || !darwin @@ -10,20 +10,34 @@ import ( "bufio" "bytes" _ "embed" + "errors" "fmt" "os" "os/exec" "path/filepath" "strings" + + "tailscale.com/client/freedesktop" ) //go:embed tailscale-systray.service var embedSystemd string +//go:embed tailscale-systray.desktop +var embedFreedesktop string + +//go:embed tailscale.svg +var embedLogoSvg string + +//go:embed tailscale.png +var embedLogoPng string + func InstallStartupScript(initSystem string) error { switch initSystem { case "systemd": return installSystemd() + case "freedesktop": + return installFreedesktop() default: return fmt.Errorf("unsupported init system '%s'", initSystem) } @@ -58,7 +72,7 @@ func installSystemd() error { systemdDir := filepath.Join(configDir, "systemd", "user") if err := os.MkdirAll(systemdDir, 0o755); err != nil { - return fmt.Errorf("failed creating systemd uuser dir: %w", err) + return fmt.Errorf("failed creating systemd user dir: %w", err) } serviceFile := filepath.Join(systemdDir, "tailscale-systray.service") @@ -74,3 +88,129 @@ func installSystemd() error { return nil } + +func installFreedesktop() error { + tmpDir, err := os.MkdirTemp("", "tailscale-systray") + if err != nil { + return fmt.Errorf("unable to make tmpDir: %w", err) + } + defer os.RemoveAll(tmpDir) + + // Install icon, and use it if it works, and if not change to some generic + // network/vpn icon. + iconName := "tailscale" + if err := installIcon(tmpDir); err != nil { + iconName = "network-transmit" + fmt.Printf("unable to install icon, continuing without: %s\n", err.Error()) + } + + // Create desktop file in a tmp dir + desktopTmpPath := filepath.Join(tmpDir, "tailscale-systray.desktop") + if err := os.WriteFile(desktopTmpPath, []byte(embedFreedesktop), + 0o0755); err != nil { + return fmt.Errorf("unable to create desktop file: %w", err) + } + + // Ensure autostart dir exists and install the desktop file + configDir, err := os.UserConfigDir() + if err != nil { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("unable to locate user home: %w", err) + } + configDir = filepath.Join(homeDir, ".config") + } + + autostartDir := filepath.Join(configDir, "autostart") + if err := os.MkdirAll(autostartDir, 0o644); err != nil { + return fmt.Errorf("failed creating freedesktop autostart dir: %w", err) + } + + desktopCmd := exec.Command("desktop-file-install", "--dir", autostartDir, + desktopTmpPath) + if output, err := desktopCmd.Output(); err != nil { + return fmt.Errorf("unable to install desktop file: %w - %s", err, output) + } + + // Find the path to tailscale, just in case it's not where the example file + // has it placed, and replace that before writing the file. + tailscaleBin, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to find tailscale binary %w", err) + } + tailscaleBin = freedesktop.Quote(tailscaleBin) + + // Make possible changes to the desktop file + runEdit := func(args ...string) error { + cmd := exec.Command("desktop-file-edit", args...) + out, err := cmd.Output() + if err != nil { + return fmt.Errorf("cmd: %s: %w\n%s", cmd.String(), err, out) + } + return nil + } + + edits := [][]string{ + {"--set-key=Exec", "--set-value=" + tailscaleBin + " systray"}, + {"--set-key=TryExec", "--set-value=" + tailscaleBin}, + {"--set-icon=" + iconName}, + } + + var errs []error + desktopFile := filepath.Join(autostartDir, "tailscale-systray.desktop") + for _, args := range edits { + args = append(args, desktopFile) + if err := runEdit(args...); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return fmt.Errorf( + "failed changing autostart file, try rebooting: %w", errors.Join(errs...)) + } + + fmt.Printf("Successfully installed freedesktop autostart service to: %s\n", desktopFile) + fmt.Println("The service will run upon logging in.") + + return nil +} + +// installIcon installs an icon using the freedesktop tools. SVG support +// is still on its way for some distros, notably missing on Ubuntu 25.10 as of +// 2026-02-19. Try to install both icons and let the DE decide from what is +// available. +// Reference: https://gitlab.freedesktop.org/xdg/xdg-utils/-/merge_requests/116 +func installIcon(tmpDir string) error { + svgPath := filepath.Join(tmpDir, "tailscale.svg") + if err := os.WriteFile(svgPath, []byte(embedLogoSvg), 0o0644); err != nil { + return fmt.Errorf("unable to create svg: %w", err) + } + + pngPath := filepath.Join(tmpDir, "tailscale.png") + if err := os.WriteFile(pngPath, []byte(embedLogoPng), 0o0644); err != nil { + return fmt.Errorf("unable to create png: %w", err) + } + + var errs []error + installed := false + svgCmd := exec.Command("xdg-icon-resource", "install", "--size", "scalable", + "--novendor", svgPath, "tailscale") + if output, err := svgCmd.Output(); err != nil { + errs = append(errs, fmt.Errorf("unable to install svg: %s - %s", err, output)) + } else { + installed = true + } + pngCmd := exec.Command("xdg-icon-resource", "install", "--size", "512", + "--novendor", pngPath, "tailscale") + if output, err := pngCmd.Output(); err != nil { + errs = append(errs, fmt.Errorf("unable to install png: %s - %s", err, output)) + } else { + installed = true + } + + if !installed { + return errors.Join(errs...) + } + return nil +} diff --git a/client/systray/systray.go b/client/systray/systray.go index b9e8fcc59043c..65c1bec20a184 100644 --- a/client/systray/systray.go +++ b/client/systray/systray.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo || !darwin @@ -171,6 +171,11 @@ tailscale systray See https://tailscale.com/kb/1597/linux-systray for more information.`) } setAppIcon(disconnected) + + // set initial title, which is used by the systray package as the ID of the StatusNotifierItem. + // This value will get overwritten later as the client status changes. + systray.SetTitle("tailscale") + menu.rebuild() menu.mu.Lock() @@ -526,6 +531,15 @@ func (menu *Menu) watchIPNBusInner() error { if err != nil { return fmt.Errorf("ipnbus error: %w", err) } + if url := n.BrowseToURL; url != nil { + // Avoid opening the browser when running as root, just in case. + runningAsRoot := os.Getuid() == 0 + if !runningAsRoot { + if err := webbrowser.Open(*url); err != nil { + log.Printf("failed to open BrowseToURL: %v", err) + } + } + } var rebuild bool if n.State != nil { log.Printf("new state: %v", n.State) diff --git a/client/systray/tailscale-systray.desktop b/client/systray/tailscale-systray.desktop new file mode 100644 index 0000000000000..b79b72d181ddc --- /dev/null +++ b/client/systray/tailscale-systray.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=Tailscale System Tray +Comment=Tailscale system tray applet for managing Tailscale +Exec=/usr/bin/tailscale systray +TryExec=/usr/bin/tailscale +Terminal=false +NoDisplay=true +StartupNotify=false +Icon=tailscale +Categories=Network;System; +X-GNOME-Autostart-enabled=true diff --git a/client/systray/tailscale-systray.service b/client/systray/tailscale-systray.service index 01d0b383c0634..bc4b470043bf7 100644 --- a/client/systray/tailscale-systray.service +++ b/client/systray/tailscale-systray.service @@ -1,6 +1,9 @@ [Unit] Description=Tailscale System Tray -After=graphical.target +Documentation=https://tailscale.com/kb/1597/linux-systray +Requires=dbus.service +After=dbus.service +PartOf=default.target [Service] Type=simple diff --git a/client/systray/tailscale.png b/client/systray/tailscale.png new file mode 100644 index 0000000000000..d476e88fc262f Binary files /dev/null and b/client/systray/tailscale.png differ diff --git a/client/systray/tailscale.svg b/client/systray/tailscale.svg new file mode 100644 index 0000000000000..9e6990271e472 --- /dev/null +++ b/client/systray/tailscale.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/client/tailscale/acl.go b/client/tailscale/acl.go index 929ec2b3b1ca9..e69d45a2bff5d 100644 --- a/client/tailscale/acl.go +++ b/client/tailscale/acl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/apitype/apitype.go b/client/tailscale/apitype/apitype.go index 6d239d082cd95..d7d1440be9f8a 100644 --- a/client/tailscale/apitype/apitype.go +++ b/client/tailscale/apitype/apitype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package apitype contains types for the Tailscale LocalAPI and control plane API. diff --git a/client/tailscale/apitype/controltype.go b/client/tailscale/apitype/controltype.go index d9d79f0ade38b..2259bb8861aad 100644 --- a/client/tailscale/apitype/controltype.go +++ b/client/tailscale/apitype/controltype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package apitype diff --git a/client/tailscale/cert.go b/client/tailscale/cert.go index 4f351ab990984..797c5535d17f5 100644 --- a/client/tailscale/cert.go +++ b/client/tailscale/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_acme diff --git a/client/tailscale/devices.go b/client/tailscale/devices.go index 0664f9e63edb1..2b2cf7a0cd049 100644 --- a/client/tailscale/devices.go +++ b/client/tailscale/devices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/dns.go b/client/tailscale/dns.go index bbdc7c56c65f7..427caea0fc593 100644 --- a/client/tailscale/dns.go +++ b/client/tailscale/dns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/example/servetls/servetls.go b/client/tailscale/example/servetls/servetls.go index 0ade420887634..864dafd07b242 100644 --- a/client/tailscale/example/servetls/servetls.go +++ b/client/tailscale/example/servetls/servetls.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The servetls program shows how to run an HTTPS server diff --git a/client/tailscale/keys.go b/client/tailscale/keys.go index 79e19e99880f7..6edbae034a759 100644 --- a/client/tailscale/keys.go +++ b/client/tailscale/keys.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/client/tailscale/localclient_aliases.go b/client/tailscale/localclient_aliases.go index e3492e841b1c9..98a72068a5eba 100644 --- a/client/tailscale/localclient_aliases.go +++ b/client/tailscale/localclient_aliases.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/client/tailscale/required_version.go b/client/tailscale/required_version.go index d6bca1c6d8ff9..fb994e55fb604 100644 --- a/client/tailscale/required_version.go +++ b/client/tailscale/required_version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !go1.23 diff --git a/client/tailscale/routes.go b/client/tailscale/routes.go index b72f2743ff9fb..aa6e49e3b7fc2 100644 --- a/client/tailscale/routes.go +++ b/client/tailscale/routes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/tailnet.go b/client/tailscale/tailnet.go index 9453962c908c8..75ca7dfd60a33 100644 --- a/client/tailscale/tailnet.go +++ b/client/tailscale/tailnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index 76e44454b2fc2..d5585a052bb99 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/tailscale_test.go b/client/tailscale/tailscale_test.go index 67379293bd580..fe2fbe383b679 100644 --- a/client/tailscale/tailscale_test.go +++ b/client/tailscale/tailscale_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/client/web/assets.go b/client/web/assets.go index c4f4e9e3bcf66..b9e4226299dd1 100644 --- a/client/web/assets.go +++ b/client/web/assets.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package web diff --git a/client/web/auth.go b/client/web/auth.go index 27eb24ee444c5..4e25b049b30ac 100644 --- a/client/web/auth.go +++ b/client/web/auth.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package web diff --git a/client/web/qnap.go b/client/web/qnap.go index 9bde64bf5885b..132b95aed086d 100644 --- a/client/web/qnap.go +++ b/client/web/qnap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // qnap.go contains handlers and logic, such as authentication, diff --git a/client/web/src/api.ts b/client/web/src/api.ts index e780c76459dfd..246f74ff231c2 100644 --- a/client/web/src/api.ts +++ b/client/web/src/api.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useCallback } from "react" diff --git a/client/web/src/components/acl-tag.tsx b/client/web/src/components/acl-tag.tsx index cb34375ed293c..95ab764c4a56d 100644 --- a/client/web/src/components/acl-tag.tsx +++ b/client/web/src/components/acl-tag.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/address-copy-card.tsx b/client/web/src/components/address-copy-card.tsx index 6b4f25bed73f4..697086f15c58d 100644 --- a/client/web/src/components/address-copy-card.tsx +++ b/client/web/src/components/address-copy-card.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import * as Primitive from "@radix-ui/react-popover" diff --git a/client/web/src/components/app.tsx b/client/web/src/components/app.tsx index 981dd8889c4b2..b885125b7f278 100644 --- a/client/web/src/components/app.tsx +++ b/client/web/src/components/app.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/control-components.tsx b/client/web/src/components/control-components.tsx index ffb0a2999558f..42ed25107c986 100644 --- a/client/web/src/components/control-components.tsx +++ b/client/web/src/components/control-components.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/exit-node-selector.tsx b/client/web/src/components/exit-node-selector.tsx index c0fd5e730b04c..a564ebbfc56b1 100644 --- a/client/web/src/components/exit-node-selector.tsx +++ b/client/web/src/components/exit-node-selector.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/login-toggle.tsx b/client/web/src/components/login-toggle.tsx index f5c4efe3ce2ac..397cb2ee1f8f1 100644 --- a/client/web/src/components/login-toggle.tsx +++ b/client/web/src/components/login-toggle.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/nice-ip.tsx b/client/web/src/components/nice-ip.tsx index f00d763f96db9..1f90d1cd73802 100644 --- a/client/web/src/components/nice-ip.tsx +++ b/client/web/src/components/nice-ip.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/update-available.tsx b/client/web/src/components/update-available.tsx index 763007de889c7..9d678d9073aa7 100644 --- a/client/web/src/components/update-available.tsx +++ b/client/web/src/components/update-available.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/views/device-details-view.tsx b/client/web/src/components/views/device-details-view.tsx index fa58e52aea473..e24aacf520743 100644 --- a/client/web/src/components/views/device-details-view.tsx +++ b/client/web/src/components/views/device-details-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/views/disconnected-view.tsx b/client/web/src/components/views/disconnected-view.tsx index 492c69e469ef1..5ec86aae4643b 100644 --- a/client/web/src/components/views/disconnected-view.tsx +++ b/client/web/src/components/views/disconnected-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/views/home-view.tsx b/client/web/src/components/views/home-view.tsx index 8073823466b34..e9051f22ba1cd 100644 --- a/client/web/src/components/views/home-view.tsx +++ b/client/web/src/components/views/home-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/views/login-view.tsx b/client/web/src/components/views/login-view.tsx index f8c15b16dbcaa..a6c4a9ae2c7ab 100644 --- a/client/web/src/components/views/login-view.tsx +++ b/client/web/src/components/views/login-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/views/ssh-view.tsx b/client/web/src/components/views/ssh-view.tsx index 1337b9fdd10fd..67c324fa53563 100644 --- a/client/web/src/components/views/ssh-view.tsx +++ b/client/web/src/components/views/ssh-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/views/subnet-router-view.tsx b/client/web/src/components/views/subnet-router-view.tsx index 26369112c1cbf..7f4c682996033 100644 --- a/client/web/src/components/views/subnet-router-view.tsx +++ b/client/web/src/components/views/subnet-router-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/views/updating-view.tsx b/client/web/src/components/views/updating-view.tsx index 0c844c7d09faa..d39dc5c63fd27 100644 --- a/client/web/src/components/views/updating-view.tsx +++ b/client/web/src/components/views/updating-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/hooks/auth.ts b/client/web/src/hooks/auth.ts index 51eb0c400bae9..c3d0cdc877022 100644 --- a/client/web/src/hooks/auth.ts +++ b/client/web/src/hooks/auth.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useCallback, useEffect, useState } from "react" diff --git a/client/web/src/hooks/exit-nodes.ts b/client/web/src/hooks/exit-nodes.ts index 5e47fbc227cd4..78f8a383dbb00 100644 --- a/client/web/src/hooks/exit-nodes.ts +++ b/client/web/src/hooks/exit-nodes.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useMemo } from "react" diff --git a/client/web/src/hooks/self-update.ts b/client/web/src/hooks/self-update.ts index eb10463c1abe1..e63d6eddaeebf 100644 --- a/client/web/src/hooks/self-update.ts +++ b/client/web/src/hooks/self-update.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useCallback, useEffect, useState } from "react" diff --git a/client/web/src/hooks/toaster.ts b/client/web/src/hooks/toaster.ts index 41fb4f42d0918..8c30cab58a6a5 100644 --- a/client/web/src/hooks/toaster.ts +++ b/client/web/src/hooks/toaster.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useRawToasterForHook } from "src/ui/toaster" diff --git a/client/web/src/hooks/ts-web-connected.ts b/client/web/src/hooks/ts-web-connected.ts index 3145663d7654d..bd020c9e9b595 100644 --- a/client/web/src/hooks/ts-web-connected.ts +++ b/client/web/src/hooks/ts-web-connected.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useCallback, useEffect, useState } from "react" diff --git a/client/web/src/index.tsx b/client/web/src/index.tsx index 31ac7890f45f2..2b970ebca8ed7 100644 --- a/client/web/src/index.tsx +++ b/client/web/src/index.tsx @@ -1,10 +1,10 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Preserved js license comment for web client app. /** * @license - * Copyright (c) Tailscale Inc & AUTHORS + * Copyright (c) Tailscale Inc & contributors * SPDX-License-Identifier: BSD-3-Clause */ diff --git a/client/web/src/types.ts b/client/web/src/types.ts index 62fa4c59f1fbf..ebf11d442fc52 100644 --- a/client/web/src/types.ts +++ b/client/web/src/types.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { assertNever } from "src/utils/util" diff --git a/client/web/src/ui/badge.tsx b/client/web/src/ui/badge.tsx index c0caa6403b37e..de8c21e3568ab 100644 --- a/client/web/src/ui/badge.tsx +++ b/client/web/src/ui/badge.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/button.tsx b/client/web/src/ui/button.tsx index 18dc2939f1889..e38f58f02bd2e 100644 --- a/client/web/src/ui/button.tsx +++ b/client/web/src/ui/button.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/card.tsx b/client/web/src/ui/card.tsx index 4e17c3df6d29e..7d3c9b89e8202 100644 --- a/client/web/src/ui/card.tsx +++ b/client/web/src/ui/card.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/collapsible.tsx b/client/web/src/ui/collapsible.tsx index 6aa8c0b9f5ca1..bd0b0eedad84a 100644 --- a/client/web/src/ui/collapsible.tsx +++ b/client/web/src/ui/collapsible.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import * as Primitive from "@radix-ui/react-collapsible" diff --git a/client/web/src/ui/dialog.tsx b/client/web/src/ui/dialog.tsx index d5af834ce05d2..6b3bb792b8565 100644 --- a/client/web/src/ui/dialog.tsx +++ b/client/web/src/ui/dialog.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import * as DialogPrimitive from "@radix-ui/react-dialog" diff --git a/client/web/src/ui/empty-state.tsx b/client/web/src/ui/empty-state.tsx index 6ac7fd4fa87e6..3964a55590ab4 100644 --- a/client/web/src/ui/empty-state.tsx +++ b/client/web/src/ui/empty-state.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/input.tsx b/client/web/src/ui/input.tsx index 756e0fc2ea4d6..7cff6bf5bf074 100644 --- a/client/web/src/ui/input.tsx +++ b/client/web/src/ui/input.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/loading-dots.tsx b/client/web/src/ui/loading-dots.tsx index 6b47552a95844..83c60da93a937 100644 --- a/client/web/src/ui/loading-dots.tsx +++ b/client/web/src/ui/loading-dots.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/popover.tsx b/client/web/src/ui/popover.tsx index c0f01c833f465..0139894bb5e4a 100644 --- a/client/web/src/ui/popover.tsx +++ b/client/web/src/ui/popover.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import * as PopoverPrimitive from "@radix-ui/react-popover" diff --git a/client/web/src/ui/portal-container-context.tsx b/client/web/src/ui/portal-container-context.tsx index d25b30bae1731..922cd0d14ea52 100644 --- a/client/web/src/ui/portal-container-context.tsx +++ b/client/web/src/ui/portal-container-context.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/ui/profile-pic.tsx b/client/web/src/ui/profile-pic.tsx index 343fb29b490e0..4bbdad878ab4a 100644 --- a/client/web/src/ui/profile-pic.tsx +++ b/client/web/src/ui/profile-pic.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/quick-copy.tsx b/client/web/src/ui/quick-copy.tsx index bc8f916c84144..0c51f820ccf33 100644 --- a/client/web/src/ui/quick-copy.tsx +++ b/client/web/src/ui/quick-copy.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/search-input.tsx b/client/web/src/ui/search-input.tsx index debba371caec6..9b99984acc014 100644 --- a/client/web/src/ui/search-input.tsx +++ b/client/web/src/ui/search-input.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/spinner.tsx b/client/web/src/ui/spinner.tsx index 51f6e887b836d..be3dc8d5b4fa5 100644 --- a/client/web/src/ui/spinner.tsx +++ b/client/web/src/ui/spinner.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/toaster.tsx b/client/web/src/ui/toaster.tsx index 18f491f3b2552..677ccde4d5d9b 100644 --- a/client/web/src/ui/toaster.tsx +++ b/client/web/src/ui/toaster.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/toggle.tsx b/client/web/src/ui/toggle.tsx index 4922830058f16..83ca92608a4dc 100644 --- a/client/web/src/ui/toggle.tsx +++ b/client/web/src/ui/toggle.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/utils/clipboard.ts b/client/web/src/utils/clipboard.ts index f003bc24079ab..3ca5f281ebb93 100644 --- a/client/web/src/utils/clipboard.ts +++ b/client/web/src/utils/clipboard.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { isPromise } from "src/utils/util" diff --git a/client/web/src/utils/util.test.ts b/client/web/src/utils/util.test.ts index 148f6cc365589..2a598d6505654 100644 --- a/client/web/src/utils/util.test.ts +++ b/client/web/src/utils/util.test.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { isTailscaleIPv6, pluralize } from "src/utils/util" diff --git a/client/web/src/utils/util.ts b/client/web/src/utils/util.ts index 5f8eda7b77572..81fc904034c08 100644 --- a/client/web/src/utils/util.ts +++ b/client/web/src/utils/util.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /** diff --git a/client/web/synology.go b/client/web/synology.go index 922489d78af16..e39cbc9c5c82e 100644 --- a/client/web/synology.go +++ b/client/web/synology.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // synology.go contains handlers and logic, such as authentication, diff --git a/client/web/web.go b/client/web/web.go index dbd3d5df0be86..f8a9e7c1769a2 100644 --- a/client/web/web.go +++ b/client/web/web.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package web provides the Tailscale client for web. diff --git a/client/web/web_test.go b/client/web/web_test.go index 9ba16bccf4884..6b9a51002b33b 100644 --- a/client/web/web_test.go +++ b/client/web/web_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package web diff --git a/client/web/yarn.lock b/client/web/yarn.lock index e8e5f5bb66450..106a104b98d26 100644 --- a/client/web/yarn.lock +++ b/client/web/yarn.lock @@ -4088,9 +4088,9 @@ lodash.merge@^4.6.2: integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index 3a0a8d03e0425..d52241483812a 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package clientupdate implements tailscale client update for all supported @@ -11,11 +11,11 @@ import ( "bufio" "bytes" "compress/gzip" - "context" "encoding/json" "errors" "fmt" "io" + "io/fs" "maps" "net/http" "os" @@ -27,6 +27,7 @@ import ( "strconv" "strings" + "tailscale.com/envknob" "tailscale.com/feature" "tailscale.com/hostinfo" "tailscale.com/types/lazy" @@ -37,8 +38,9 @@ import ( ) const ( - StableTrack = "stable" - UnstableTrack = "unstable" + StableTrack = "stable" + UnstableTrack = "unstable" + ReleaseCandidateTrack = "release-candidate" ) var CurrentTrack = func() string { @@ -79,6 +81,8 @@ type Arguments struct { // running binary // - StableTrack and UnstableTrack will use the latest versions of the // corresponding tracks + // - ReleaseCandidateTrack will use the newest version from StableTrack + // and ReleaseCandidateTrack. // // Leaving this empty will use Version or fall back to CurrentTrack if both // Track and Version are empty. @@ -113,7 +117,7 @@ func (args Arguments) validate() error { return fmt.Errorf("only one of Version(%q) or Track(%q) can be set", args.Version, args.Track) } switch args.Track { - case StableTrack, UnstableTrack, "": + case StableTrack, UnstableTrack, ReleaseCandidateTrack, "": // All valid values. default: return fmt.Errorf("unsupported track %q", args.Track) @@ -288,6 +292,10 @@ func Update(args Arguments) error { } func (up *Updater) confirm(ver string) bool { + if envknob.Bool("TS_UPDATE_SKIP_VERSION_CHECK") { + up.Logf("current version: %v, latest version %v; forcing an update due to TS_UPDATE_SKIP_VERSION_CHECK", up.currentVersion, ver) + return true + } // Only check version when we're not switching tracks. if up.Track == "" || up.Track == CurrentTrack { switch c := cmpver.Compare(up.currentVersion, ver); { @@ -491,10 +499,10 @@ func (up *Updater) updateDebLike() error { const aptSourcesFile = "/etc/apt/sources.list.d/tailscale.list" // updateDebianAptSourcesList updates the /etc/apt/sources.list.d/tailscale.list -// file to make sure it has the provided track (stable or unstable) in it. +// file to make sure it has the provided track (stable, unstable, or release-candidate) in it. // -// If it already has the right track (including containing both stable and -// unstable), it does nothing. +// If it already has the right track (including containing both stable, +// unstable, and release-candidate), it does nothing. func updateDebianAptSourcesList(dstTrack string) (rewrote bool, err error) { was, err := os.ReadFile(aptSourcesFile) if err != nil { @@ -517,7 +525,7 @@ func updateDebianAptSourcesListBytes(was []byte, dstTrack string) (newContent [] bs := bufio.NewScanner(bytes.NewReader(was)) hadCorrect := false commentLine := regexp.MustCompile(`^\s*\#`) - pkgsURL := regexp.MustCompile(`\bhttps://pkgs\.tailscale\.com/((un)?stable)/`) + pkgsURL := regexp.MustCompile(`\bhttps://pkgs\.tailscale\.com/(stable|unstable|release-candidate)/`) for bs.Scan() { line := bs.Bytes() if !commentLine.Match(line) { @@ -611,15 +619,15 @@ func (up *Updater) updateFedoraLike(packageManager string) func() error { } // updateYUMRepoTrack updates the repoFile file to make sure it has the -// provided track (stable or unstable) in it. +// provided track (stable, unstable, or release-candidate) in it. func updateYUMRepoTrack(repoFile, dstTrack string) (rewrote bool, err error) { was, err := os.ReadFile(repoFile) if err != nil { return false, err } - urlRe := regexp.MustCompile(`^(baseurl|gpgkey)=https://pkgs\.tailscale\.com/(un)?stable/`) - urlReplacement := fmt.Sprintf("$1=https://pkgs.tailscale.com/%s/", dstTrack) + urlRe := regexp.MustCompile(`^(baseurl|gpgkey)=https://pkgs\.tailscale\.com/(stable|unstable|release-candidate)`) + urlReplacement := fmt.Sprintf("$1=https://pkgs.tailscale.com/%s", dstTrack) s := bufio.NewScanner(bytes.NewReader(was)) newContent := bytes.NewBuffer(make([]byte, 0, len(was))) @@ -653,7 +661,7 @@ func updateYUMRepoTrack(repoFile, dstTrack string) (rewrote bool, err error) { func (up *Updater) updateAlpineLike() (err error) { if up.Version != "" { - return errors.New("installing a specific version on Alpine-based distros is not supported") + return errors.New("installing a specific version on apk-based distros is not supported") } if err := requireRoot(); err != nil { return err @@ -683,7 +691,7 @@ func (up *Updater) updateAlpineLike() (err error) { return fmt.Errorf(`failed to parse latest version from "apk info tailscale": %w`, err) } if !up.confirm(ver) { - if err := checkOutdatedAlpineRepo(up.Logf, ver, up.Track); err != nil { + if err := checkOutdatedAlpineRepo(up.Logf, apkDirPaths, ver, up.Track); err != nil { up.Logf("failed to check whether Alpine release is outdated: %v", err) } return nil @@ -723,9 +731,12 @@ func parseAlpinePackageVersion(out []byte) (string, error) { return "", errors.New("tailscale version not found in output") } -var apkRepoVersionRE = regexp.MustCompile(`v[0-9]+\.[0-9]+`) +var ( + apkRepoVersionRE = regexp.MustCompile(`v[0-9]+\.[0-9]+`) + apkDirPaths = []string{"/etc/apk/repositories", "/etc/apk/repositories.d/distfeeds.list"} +) -func checkOutdatedAlpineRepo(logf logger.Logf, apkVer, track string) error { +func checkOutdatedAlpineRepo(logf logger.Logf, filePaths []string, apkVer, track string) error { latest, err := LatestTailscaleVersion(track) if err != nil { return err @@ -734,22 +745,34 @@ func checkOutdatedAlpineRepo(logf logger.Logf, apkVer, track string) error { // Actually on latest release. return nil } - f, err := os.Open("/etc/apk/repositories") - if err != nil { - return err - } - defer f.Close() - // Read the first repo line. Typically, there are multiple repos that all - // contain the same version in the path, like: - // https://dl-cdn.alpinelinux.org/alpine/v3.20/main - // https://dl-cdn.alpinelinux.org/alpine/v3.20/community - s := bufio.NewScanner(f) - if !s.Scan() { - return s.Err() - } - alpineVer := apkRepoVersionRE.FindString(s.Text()) - if alpineVer != "" { - logf("The latest Tailscale release for Linux is %q, but your apk repository only provides %q.\nYour Alpine version is %q, you may need to upgrade the system to get the latest Tailscale version: https://wiki.alpinelinux.org/wiki/Upgrading_Alpine", latest, apkVer, alpineVer) + + // OpenWrt uses a different repo file in repositories.d, check for that as well. + for _, repoFile := range filePaths { + f, err := os.Open(repoFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } else { + return err + } + } + defer f.Close() + // Read the first repo line. Typically, there are multiple repos that all + // contain the same version in the path, like: + // https://dl-cdn.alpinelinux.org/alpine/v3.20/main + // https://dl-cdn.alpinelinux.org/alpine/v3.20/community + s := bufio.NewScanner(f) + if !s.Scan() { + if s.Err() != nil { + return s.Err() + } + logf("The latest Tailscale release for Linux is %q, but your apk repository only provides %q.\nYou may need to upgrade your Alpine system to get the latest Tailscale version: https://wiki.alpinelinux.org/wiki/Upgrading_Alpine", latest, apkVer) + } + alpineVer := apkRepoVersionRE.FindString(s.Text()) + if alpineVer != "" { + logf("The latest Tailscale release for Linux is %q, but your apk repository only provides %q.\nYour Alpine version is %q, you may need to upgrade the system to get the latest Tailscale version: https://wiki.alpinelinux.org/wiki/Upgrading_Alpine", latest, apkVer, alpineVer) + } + return nil } return nil } @@ -865,12 +888,17 @@ func (up *Updater) updateLinuxBinary() error { if err := os.Remove(dlPath); err != nil { up.Logf("failed to clean up %q: %v", dlPath, err) } - if err := restartSystemdUnit(context.Background()); err != nil { + + err = restartSystemdUnit(up.Logf) + if errors.Is(err, errors.ErrUnsupported) { + err = restartInitD() if errors.Is(err, errors.ErrUnsupported) { - up.Logf("Tailscale binaries updated successfully.\nPlease restart tailscaled to finish the update.") - } else { - up.Logf("Tailscale binaries updated successfully, but failed to restart tailscaled: %s.\nPlease restart tailscaled to finish the update.", err) + err = errors.New("tailscaled is not running under systemd or init.d") } + } + if err != nil { + up.Logf("Tailscale binaries updated successfully, but failed to restart tailscaled: %s.\nPlease restart tailscaled to finish the update.", err) + } else { up.Logf("Success") } @@ -878,13 +906,13 @@ func (up *Updater) updateLinuxBinary() error { return nil } -func restartSystemdUnit(ctx context.Context) error { +func restartSystemdUnit(logf logger.Logf) error { if _, err := exec.LookPath("systemctl"); err != nil { // Likely not a systemd-managed distro. return errors.ErrUnsupported } if out, err := exec.Command("systemctl", "daemon-reload").CombinedOutput(); err != nil { - return fmt.Errorf("systemctl daemon-reload failed: %w\noutput: %s", err, out) + logf("systemctl daemon-reload failed: %w\noutput: %s", err, out) } if out, err := exec.Command("systemctl", "restart", "tailscaled.service").CombinedOutput(); err != nil { return fmt.Errorf("systemctl restart failed: %w\noutput: %s", err, out) @@ -892,6 +920,40 @@ func restartSystemdUnit(ctx context.Context) error { return nil } +// restartInitD attempts best-effort restart of tailscale on init.d systems +// (for example, GL.iNet KVM devices running busybox). It returns +// errors.ErrUnsupported if the expected service script is not found. +// +// There's probably a million variations of init.d configs out there, and this +// function does not intend to support all of them. +func restartInitD() error { + const initDir = "/etc/init.d/" + files, err := os.ReadDir(initDir) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return errors.ErrUnsupported + } + return err + } + for _, f := range files { + // Skip anything other than regular files. + if !f.Type().IsRegular() { + continue + } + // The script will be called something like /etc/init.d/tailscale or + // /etc/init.d/S99tailscale. + if n := f.Name(); strings.HasSuffix(n, "tailscale") { + path := filepath.Join(initDir, n) + if out, err := exec.Command(path, "restart").CombinedOutput(); err != nil { + return fmt.Errorf("%q failed: %w\noutput: %s", path+" restart", err, out) + } + return nil + } + } + // Init script for tailscale not found. + return errors.ErrUnsupported +} + func (up *Updater) downloadLinuxTarball(ver string) (string, error) { dlDir, err := os.UserCacheDir() if err != nil { @@ -1199,8 +1261,10 @@ type trackPackages struct { SPKsVersion string } +var tailscaleHTTPEndpoint = "https://pkgs.tailscale.com" + func latestPackages(track string) (*trackPackages, error) { - url := fmt.Sprintf("https://pkgs.tailscale.com/%s/?mode=json&os=%s", track, runtime.GOOS) + url := fmt.Sprintf("%s/%s/?mode=json&os=%s", tailscaleHTTPEndpoint, track, runtime.GOOS) res, err := http.Get(url) if err != nil { return nil, fmt.Errorf("fetching latest tailscale version: %w", err) diff --git a/clientupdate/clientupdate_downloads.go b/clientupdate/clientupdate_downloads.go index 18d3176b42afe..9458f88fe8a18 100644 --- a/clientupdate/clientupdate_downloads.go +++ b/clientupdate/clientupdate_downloads.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || windows diff --git a/clientupdate/clientupdate_not_downloads.go b/clientupdate/clientupdate_not_downloads.go index 057b4f2cd7574..aaffb76f05b3e 100644 --- a/clientupdate/clientupdate_not_downloads.go +++ b/clientupdate/clientupdate_not_downloads.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !((linux && !android) || windows) diff --git a/clientupdate/clientupdate_notwindows.go b/clientupdate/clientupdate_notwindows.go index edadc210c8a15..12035ff73495a 100644 --- a/clientupdate/clientupdate_notwindows.go +++ b/clientupdate/clientupdate_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/clientupdate/clientupdate_test.go b/clientupdate/clientupdate_test.go index b265d56411bdc..13fc8f08a6a2e 100644 --- a/clientupdate/clientupdate_test.go +++ b/clientupdate/clientupdate_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package clientupdate @@ -6,9 +6,12 @@ package clientupdate import ( "archive/tar" "compress/gzip" + "encoding/json" "fmt" "io/fs" "maps" + "net/http" + "net/http/httptest" "os" "path/filepath" "slices" @@ -86,29 +89,8 @@ func TestUpdateDebianAptSourcesListBytes(t *testing.T) { } } -func TestUpdateYUMRepoTrack(t *testing.T) { - tests := []struct { - desc string - before string - track string - after string - rewrote bool - wantErr bool - }{ - { - desc: "same track", - before: ` -[tailscale-stable] -name=Tailscale stable -baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch -enabled=1 -type=rpm -repo_gpgcheck=1 -gpgcheck=0 -gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg -`, - track: StableTrack, - after: ` +var YUMRepos = map[string]string{ + StableTrack: ` [tailscale-stable] name=Tailscale stable baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch @@ -118,35 +100,30 @@ repo_gpgcheck=1 gpgcheck=0 gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg `, - }, - { - desc: "change track", - before: ` -[tailscale-stable] -name=Tailscale stable -baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch + + UnstableTrack: ` +[tailscale-unstable] +name=Tailscale unstable +baseurl=https://pkgs.tailscale.com/unstable/fedora/$basearch enabled=1 type=rpm repo_gpgcheck=1 gpgcheck=0 -gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg +gpgkey=https://pkgs.tailscale.com/unstable/fedora/repo.gpg `, - track: UnstableTrack, - after: ` -[tailscale-unstable] -name=Tailscale unstable -baseurl=https://pkgs.tailscale.com/unstable/fedora/$basearch + + ReleaseCandidateTrack: ` +[tailscale-release-candidate] +name=Tailscale release-candidate +baseurl=https://pkgs.tailscale.com/release-candidate/fedora/$basearch enabled=1 type=rpm repo_gpgcheck=1 gpgcheck=0 -gpgkey=https://pkgs.tailscale.com/unstable/fedora/repo.gpg +gpgkey=https://pkgs.tailscale.com/release-candidate/fedora/repo.gpg `, - rewrote: true, - }, - { - desc: "non-tailscale repo file", - before: ` + + "FakeRepo": ` [fedora] name=Fedora $releasever - $basearch #baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/ @@ -158,8 +135,41 @@ repo_gpgcheck=0 type=rpm gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch -skip_if_unavailable=False -`, +skip_if_unavailable=False`, +} + +func TestUpdateYUMRepoTrack(t *testing.T) { + tests := []struct { + desc string + before string + track string + after string + rewrote bool + wantErr bool + }{ + { + desc: "same track", + before: YUMRepos[StableTrack], + track: StableTrack, + after: YUMRepos[StableTrack], + }, + { + desc: "change track", + before: YUMRepos[StableTrack], + track: UnstableTrack, + after: YUMRepos[UnstableTrack], + rewrote: true, + }, + { + desc: "change track RC", + before: YUMRepos[StableTrack], + track: ReleaseCandidateTrack, + after: YUMRepos[ReleaseCandidateTrack], + rewrote: true, + }, + { + desc: "non-tailscale repo file", + before: YUMRepos["FakeRepo"], track: StableTrack, wantErr: true, }, @@ -292,6 +302,127 @@ tailscale-1.58.2-r0 installed size: } } +func TestCheckOutdatedAlpineRepo(t *testing.T) { + anyToString := func(a any) string { + str, ok := a.(string) + if !ok { + panic("failed to parse param as string") + } + return str + } + + tests := []struct { + name string + fileContent string + latestHTTPVersion string + latestApkVersion string + wantHTTPVersion string + wantApkVersion string + wantAlpineVersion string + track string + }{ + { + name: "Up to date", + fileContent: "https://dl-cdn.alpinelinux.org/alpine/v3.20/main", + latestHTTPVersion: "1.95.3", + latestApkVersion: "1.95.3", + track: "unstable", + }, + { + name: "Behind unstable", + fileContent: "https://dl-cdn.alpinelinux.org/alpine/v3.20/main", + latestHTTPVersion: "1.95.4", + latestApkVersion: "1.95.3", + wantHTTPVersion: "1.95.4", + wantApkVersion: "1.95.3", + wantAlpineVersion: "v3.20", + track: "unstable", + }, + { + name: "Behind stable", + fileContent: "https://dl-cdn.alpinelinux.org/alpine/v2.40/main", + latestHTTPVersion: "1.94.3", + latestApkVersion: "1.92.1", + wantHTTPVersion: "1.94.3", + wantApkVersion: "1.92.1", + wantAlpineVersion: "v2.40", + track: "stable", + }, + { + name: "Nothing in dist file", + fileContent: "", + latestHTTPVersion: "1.94.3", + latestApkVersion: "1.92.1", + wantHTTPVersion: "1.94.3", + wantApkVersion: "1.92.1", + track: "stable", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := os.MkdirTemp("", "example") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + t.Cleanup(func() { os.RemoveAll(dir) }) // clean up + + file := filepath.Join(dir, "distfile") + if err := os.WriteFile(file, []byte(tt.fileContent), 0o666); err != nil { + t.Fatalf("error creating dist file: %v", err) + } + + testServ := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, _ *http.Request) { + version := trackPackages{ + MSIsVersion: tt.latestHTTPVersion, + MacZipsVersion: tt.latestHTTPVersion, + TarballsVersion: tt.latestHTTPVersion, + SPKsVersion: tt.latestHTTPVersion, + } + jsonData, err := json.Marshal(version) + if err != nil { + t.Errorf("failed to marshal version string: %v", err) + } + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(jsonData); err != nil { + t.Errorf("failed to write json blob: %v", err) + } + }, + )) + defer testServ.Close() + + oldEndpoint := tailscaleHTTPEndpoint + tailscaleHTTPEndpoint = testServ.URL + defer func() { tailscaleHTTPEndpoint = oldEndpoint }() + + var paramLatest string + var paramApkVer string + var paramAlpineVer string + logf := func(_ string, params ...any) { + paramLatest = anyToString(params[0]) + paramApkVer = anyToString(params[1]) + if len(params) > 2 { + paramAlpineVer = anyToString(params[2]) + } + } + + err = checkOutdatedAlpineRepo(logf, []string{file}, tt.latestApkVersion, tt.track) + if err != nil { + t.Errorf("did not expect error, got: %v", err) + } + if paramLatest != tt.wantHTTPVersion { + t.Errorf("expected HTTP version '%s', got '%s'", tt.wantHTTPVersion, paramLatest) + } + if paramApkVer != tt.wantApkVersion { + t.Errorf("expected APK version '%s', got '%s'", tt.wantApkVersion, paramApkVer) + } + if paramAlpineVer != tt.wantAlpineVersion { + t.Errorf("expected alpine version '%s', got '%s'", tt.wantAlpineVersion, paramAlpineVer) + } + }) + } +} + func TestSynoArch(t *testing.T) { tests := []struct { goarch string diff --git a/clientupdate/clientupdate_windows.go b/clientupdate/clientupdate_windows.go index 5faeda6dd70e3..70a3c509121ea 100644 --- a/clientupdate/clientupdate_windows.go +++ b/clientupdate/clientupdate_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Windows-specific stuff that can't go in clientupdate.go because it needs diff --git a/clientupdate/distsign/distsign.go b/clientupdate/distsign/distsign.go index 954403ae0c62c..c804b855cfc1d 100644 --- a/clientupdate/distsign/distsign.go +++ b/clientupdate/distsign/distsign.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package distsign implements signature and validation of arbitrary diff --git a/clientupdate/distsign/distsign_test.go b/clientupdate/distsign/distsign_test.go index 09a701f499198..0d454771fc9a4 100644 --- a/clientupdate/distsign/distsign_test.go +++ b/clientupdate/distsign/distsign_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package distsign diff --git a/clientupdate/distsign/roots.go b/clientupdate/distsign/roots.go index d5b47b7b62e92..2fab3aab90373 100644 --- a/clientupdate/distsign/roots.go +++ b/clientupdate/distsign/roots.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package distsign diff --git a/clientupdate/distsign/roots_test.go b/clientupdate/distsign/roots_test.go index 7a94529538ef1..562b06c1c29c1 100644 --- a/clientupdate/distsign/roots_test.go +++ b/clientupdate/distsign/roots_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package distsign diff --git a/cmd/addlicense/main.go b/cmd/addlicense/main.go index 1cd1b0f19354a..35d97b72f70b4 100644 --- a/cmd/addlicense/main.go +++ b/cmd/addlicense/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Program addlicense adds a license header to a file. @@ -67,7 +67,7 @@ func check(err error) { } var license = ` -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause `[1:] diff --git a/cmd/build-webclient/build-webclient.go b/cmd/build-webclient/build-webclient.go index f92c0858fae25..949d9ef349ef1 100644 --- a/cmd/build-webclient/build-webclient.go +++ b/cmd/build-webclient/build-webclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The build-webclient tool generates the static resources needed for the diff --git a/cmd/checkmetrics/checkmetrics.go b/cmd/checkmetrics/checkmetrics.go index fb9e8ab4c61ec..5612ffbf512f9 100644 --- a/cmd/checkmetrics/checkmetrics.go +++ b/cmd/checkmetrics/checkmetrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // checkmetrics validates that all metrics in the tailscale client-metrics diff --git a/cmd/cigocacher/cigocacher.go b/cmd/cigocacher/cigocacher.go index 872cb195355b5..74ed083679743 100644 --- a/cmd/cigocacher/cigocacher.go +++ b/cmd/cigocacher/cigocacher.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // cigocacher is an opinionated-to-Tailscale client for gocached. It connects @@ -12,10 +12,8 @@ package main import ( - "bytes" "context" jsonv1 "encoding/json" - "errors" "flag" "fmt" "io" @@ -103,17 +101,21 @@ func main() { if tk == "" { log.Fatal("--token is empty; cannot fetch stats") } - c := &gocachedClient{ - baseURL: *srvURL, - cl: httpClient(srvHost, *srvHostDial), - accessToken: tk, - verbose: *verbose, - } - stats, err := c.fetchStats() + stats, err := fetchStats(httpClient(srvHost, *srvHostDial), *srvURL, tk) if err != nil { - log.Fatalf("error fetching gocached stats: %v", err) + // Errors that are not due to misconfiguration are non-fatal so we + // don't fail builds if e.g. cigocached is down. + // + // Print error as JSON so it can still be piped through jq. + statsErr := map[string]any{ + "error": fmt.Sprintf("fetching gocached stats: %v", err), + } + b, _ := jsonv1.Marshal(statsErr) + fmt.Println(string(b)) + } else { + fmt.Println(stats) } - fmt.Println(stats) + return } @@ -140,11 +142,13 @@ func main() { if *verbose { log.Printf("Using cigocached at %s", *srvURL) } - c.gocached = &gocachedClient{ - baseURL: *srvURL, - cl: httpClient(srvHost, *srvHostDial), - accessToken: *token, - verbose: *verbose, + c.remote = &cachers.HTTPClient{ + BaseURL: *srvURL, + Disk: c.disk, + HTTPClient: httpClient(srvHost, *srvHostDial), + AccessToken: *token, + Verbose: *verbose, + BestEffortHTTP: true, } } var p *cacheproc.Process @@ -186,9 +190,9 @@ func httpClient(srvHost, srvHostDial string) *http.Client { } type cigocacher struct { - disk *cachers.DiskCache - gocached *gocachedClient - verbose bool + disk *cachers.DiskCache + remote *cachers.HTTPClient // nil if no remote server + verbose bool getNanos atomic.Int64 // total nanoseconds spent in gets putNanos atomic.Int64 // total nanoseconds spent in puts @@ -209,39 +213,33 @@ func (c *cigocacher) get(ctx context.Context, actionID string) (outputID, diskPa defer func() { c.getNanos.Add(time.Since(t0).Nanoseconds()) }() - if c.gocached == nil { - return c.disk.Get(ctx, actionID) - } outputID, diskPath, err = c.disk.Get(ctx, actionID) - if err == nil && outputID != "" { - return outputID, diskPath, nil + if c.remote == nil || (err == nil && outputID != "") { + return outputID, diskPath, err } + // Disk miss; try remote. HTTPClient.Get handles the HTTP fetch + // (including lz4 decompression) and writes to disk for us. c.getHTTP.Add(1) t0HTTP := time.Now() defer func() { c.getHTTPNanos.Add(time.Since(t0HTTP).Nanoseconds()) }() - outputID, res, err := c.gocached.get(ctx, actionID) + outputID, diskPath, err = c.remote.Get(ctx, actionID) if err != nil { c.getHTTPErrors.Add(1) return "", "", nil } - if outputID == "" || res == nil { + if outputID == "" { c.getHTTPMisses.Add(1) return "", "", nil } - defer res.Body.Close() - - diskPath, err = put(c.disk, actionID, outputID, res.ContentLength, res.Body) - if err != nil { - return "", "", fmt.Errorf("error filling disk cache from HTTP: %w", err) - } - c.getHTTPHits.Add(1) - c.getHTTPBytes.Add(res.ContentLength) + if fi, err := os.Stat(diskPath); err == nil { + c.getHTTPBytes.Add(fi.Size()) + } return outputID, diskPath, nil } @@ -250,56 +248,25 @@ func (c *cigocacher) put(ctx context.Context, actionID, outputID string, size in defer func() { c.putNanos.Add(time.Since(t0).Nanoseconds()) }() - if c.gocached == nil { - return put(c.disk, actionID, outputID, size, r) - } - c.putHTTP.Add(1) - var diskReader, httpReader io.Reader - tee := &bestEffortTeeReader{r: r} - if size == 0 { - // Special case the empty file so NewRequest sets "Content-Length: 0", - // as opposed to thinking we didn't set it and not being able to sniff its size - // from the type. - diskReader, httpReader = bytes.NewReader(nil), bytes.NewReader(nil) - } else { - pr, pw := io.Pipe() - defer pw.Close() - // The diskReader is in the driving seat. We will try to forward data - // to httpReader as well, but only best-effort. - diskReader = tee - tee.w = pw - httpReader = pr + if c.remote == nil { + return c.disk.Put(ctx, actionID, outputID, size, r) } - httpErrCh := make(chan error) - go func() { - t0HTTP := time.Now() - defer func() { - c.putHTTPNanos.Add(time.Since(t0HTTP).Nanoseconds()) - }() - httpErrCh <- c.gocached.put(ctx, actionID, outputID, size, httpReader) - }() - diskPath, err = put(c.disk, actionID, outputID, size, diskReader) + c.putHTTP.Add(1) + diskPath, err = c.remote.Put(ctx, actionID, outputID, size, r) + c.putHTTPNanos.Add(time.Since(t0).Nanoseconds()) if err != nil { - return "", fmt.Errorf("error writing to disk cache: %w", errors.Join(err, tee.err)) - } - - select { - case err := <-httpErrCh: - if err != nil { - c.putHTTPErrors.Add(1) - } else { - c.putHTTPBytes.Add(size) - } - case <-ctx.Done(): + c.putHTTPErrors.Add(1) + } else { + c.putHTTPBytes.Add(size) } - return diskPath, nil + return diskPath, err } func (c *cigocacher) close() error { - if !c.verbose || c.gocached == nil { + if !c.verbose || c.remote == nil { return nil } @@ -307,7 +274,7 @@ func (c *cigocacher) close() error { c.getHTTP.Load(), float64(c.getHTTPBytes.Load())/float64(1<<20), float64(c.getHTTPNanos.Load())/float64(time.Second), c.getHTTPHits.Load(), c.getHTTPMisses.Load(), c.getHTTPErrors.Load(), c.putHTTP.Load(), float64(c.putHTTPBytes.Load())/float64(1<<20), float64(c.putHTTPNanos.Load())/float64(time.Second), c.putHTTPErrors.Load()) - stats, err := c.gocached.fetchStats() + stats, err := fetchStats(c.remote.HTTPClient, c.remote.BaseURL, c.remote.AccessToken) if err != nil { log.Printf("error fetching gocached stats: %v", err) } else { @@ -354,19 +321,20 @@ func fetchAccessToken(cl *http.Client, idTokenURL, idTokenRequestToken, gocached return accessToken.AccessToken, nil } -type bestEffortTeeReader struct { - r io.Reader - w io.WriteCloser - err error -} - -func (t *bestEffortTeeReader) Read(p []byte) (int, error) { - n, err := t.r.Read(p) - if n > 0 && t.w != nil { - if _, err := t.w.Write(p[:n]); err != nil { - t.err = errors.Join(err, t.w.Close()) - t.w = nil - } +func fetchStats(cl *http.Client, baseURL, accessToken string) (string, error) { + req, _ := http.NewRequest("GET", baseURL+"/session/stats", nil) + req.Header.Set("Authorization", "Bearer "+accessToken) + resp, err := cl.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("fetching stats: %s", resp.Status) + } + b, err := io.ReadAll(resp.Body) + if err != nil { + return "", err } - return n, err + return string(b), nil } diff --git a/cmd/cigocacher/disk.go b/cmd/cigocacher/disk.go deleted file mode 100644 index 57a9b80d5609e..0000000000000 --- a/cmd/cigocacher/disk.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "log" - "os" - "path/filepath" - "time" - - "github.com/bradfitz/go-tool-cache/cachers" -) - -// indexEntry is the metadata that DiskCache stores on disk for an ActionID. -type indexEntry struct { - Version int `json:"v"` - OutputID string `json:"o"` - Size int64 `json:"n"` - TimeNanos int64 `json:"t"` -} - -func validHex(x string) bool { - if len(x) < 4 || len(x) > 100 { - return false - } - for _, b := range x { - if b >= '0' && b <= '9' || b >= 'a' && b <= 'f' { - continue - } - return false - } - return true -} - -// put is like dc.Put but refactored to support safe concurrent writes on Windows. -// TODO(tomhjp): upstream these changes to go-tool-cache once they look stable. -func put(dc *cachers.DiskCache, actionID, outputID string, size int64, body io.Reader) (diskPath string, _ error) { - if len(actionID) < 4 || len(outputID) < 4 { - return "", fmt.Errorf("actionID and outputID must be at least 4 characters long") - } - if !validHex(actionID) { - log.Printf("diskcache: got invalid actionID %q", actionID) - return "", errors.New("actionID must be hex") - } - if !validHex(outputID) { - log.Printf("diskcache: got invalid outputID %q", outputID) - return "", errors.New("outputID must be hex") - } - - actionFile := dc.ActionFilename(actionID) - outputFile := dc.OutputFilename(outputID) - actionDir := filepath.Dir(actionFile) - outputDir := filepath.Dir(outputFile) - - if err := os.MkdirAll(actionDir, 0755); err != nil { - return "", fmt.Errorf("failed to create action directory: %w", err) - } - if err := os.MkdirAll(outputDir, 0755); err != nil { - return "", fmt.Errorf("failed to create output directory: %w", err) - } - - wrote, err := writeOutputFile(outputFile, body, size, outputID) - if err != nil { - return "", err - } - if wrote != size { - return "", fmt.Errorf("wrote %d bytes, expected %d", wrote, size) - } - - ij, err := json.Marshal(indexEntry{ - Version: 1, - OutputID: outputID, - Size: size, - TimeNanos: time.Now().UnixNano(), - }) - if err != nil { - return "", err - } - if err := writeActionFile(dc.ActionFilename(actionID), ij); err != nil { - return "", fmt.Errorf("atomic write failed: %w", err) - } - return outputFile, nil -} diff --git a/cmd/cigocacher/disk_notwindows.go b/cmd/cigocacher/disk_notwindows.go deleted file mode 100644 index 705ed92e3d8de..0000000000000 --- a/cmd/cigocacher/disk_notwindows.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !windows - -package main - -import ( - "bytes" - "io" - "os" - "path/filepath" -) - -func writeActionFile(dest string, b []byte) error { - _, err := writeAtomic(dest, bytes.NewReader(b)) - return err -} - -func writeOutputFile(dest string, r io.Reader, _ int64, _ string) (int64, error) { - return writeAtomic(dest, r) -} - -func writeAtomic(dest string, r io.Reader) (int64, error) { - tf, err := os.CreateTemp(filepath.Dir(dest), filepath.Base(dest)+".*") - if err != nil { - return 0, err - } - size, err := io.Copy(tf, r) - if err != nil { - tf.Close() - os.Remove(tf.Name()) - return 0, err - } - if err := tf.Close(); err != nil { - os.Remove(tf.Name()) - return 0, err - } - if err := os.Rename(tf.Name(), dest); err != nil { - os.Remove(tf.Name()) - return 0, err - } - return size, nil -} diff --git a/cmd/cigocacher/disk_windows.go b/cmd/cigocacher/disk_windows.go deleted file mode 100644 index 9efae2c632087..0000000000000 --- a/cmd/cigocacher/disk_windows.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "crypto/sha256" - "errors" - "fmt" - "io" - "os" -) - -// The functions in this file are based on go's own cache in -// cmd/go/internal/cache/cache.go, particularly putIndexEntry and copyFile. - -// writeActionFile writes the indexEntry metadata for an ActionID to disk. It -// may be called for the same actionID concurrently from multiple processes, -// and the outputID for a specific actionID may change from time to time due -// to non-deterministic builds. It makes a best-effort to delete the file if -// anything goes wrong. -func writeActionFile(dest string, b []byte) (retErr error) { - f, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, 0o666) - if err != nil { - return err - } - defer func() { - cerr := f.Close() - if retErr != nil || cerr != nil { - retErr = errors.Join(retErr, cerr, os.Remove(dest)) - } - }() - - _, err = f.Write(b) - if err != nil { - return err - } - - // Truncate the file only *after* writing it. - // (This should be a no-op, but truncate just in case of previous corruption.) - // - // This differs from os.WriteFile, which truncates to 0 *before* writing - // via os.O_TRUNC. Truncating only after writing ensures that a second write - // of the same content to the same file is idempotent, and does not - even - // temporarily! - undo the effect of the first write. - return f.Truncate(int64(len(b))) -} - -// writeOutputFile writes content to be cached to disk. The outputID is the -// sha256 hash of the content, and each file should only be written ~once, -// assuming no sha256 hash collisions. It may be written multiple times if -// concurrent processes are both populating the same output. The file is opened -// with FILE_SHARE_READ|FILE_SHARE_WRITE, which means both processes can write -// the same contents concurrently without conflict. -// -// It makes a best effort to clean up if anything goes wrong, but the file may -// be left in an inconsistent state in the event of disk-related errors such as -// another process taking file locks, or power loss etc. -func writeOutputFile(dest string, r io.Reader, size int64, outputID string) (_ int64, retErr error) { - info, err := os.Stat(dest) - if err == nil && info.Size() == size { - // Already exists, check the hash. - if f, err := os.Open(dest); err == nil { - h := sha256.New() - io.Copy(h, f) - f.Close() - if fmt.Sprintf("%x", h.Sum(nil)) == outputID { - // Still drain the reader to ensure associated resources are released. - return io.Copy(io.Discard, r) - } - } - } - - // Didn't successfully find the pre-existing file, write it. - mode := os.O_WRONLY | os.O_CREATE - if err == nil && info.Size() > size { - mode |= os.O_TRUNC // Should never happen, but self-heal. - } - f, err := os.OpenFile(dest, mode, 0644) - if err != nil { - return 0, fmt.Errorf("failed to open output file %q: %w", dest, err) - } - defer func() { - cerr := f.Close() - if retErr != nil || cerr != nil { - retErr = errors.Join(retErr, cerr, os.Remove(dest)) - } - }() - - // Copy file to f, but also into h to double-check hash. - h := sha256.New() - w := io.MultiWriter(f, h) - n, err := io.Copy(w, r) - if err != nil { - return 0, err - } - if fmt.Sprintf("%x", h.Sum(nil)) != outputID { - return 0, errors.New("file content changed underfoot") - } - - return n, nil -} diff --git a/cmd/cigocacher/http.go b/cmd/cigocacher/http.go deleted file mode 100644 index 55735f089655e..0000000000000 --- a/cmd/cigocacher/http.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "context" - "fmt" - "io" - "log" - "net/http" -) - -type gocachedClient struct { - baseURL string // base URL of the cacher server, like "http://localhost:31364". - cl *http.Client // http.Client to use. - accessToken string // Bearer token to use in the Authorization header. - verbose bool -} - -// drainAndClose reads and throws away a small bounded amount of data. This is a -// best-effort attempt to allow connection reuse; Go's HTTP/1 Transport won't -// reuse a TCP connection unless you fully consume HTTP responses. -func drainAndClose(body io.ReadCloser) { - io.CopyN(io.Discard, body, 4<<10) - body.Close() -} - -func tryReadErrorMessage(res *http.Response) []byte { - msg, _ := io.ReadAll(io.LimitReader(res.Body, 4<<10)) - return msg -} - -func (c *gocachedClient) get(ctx context.Context, actionID string) (outputID string, resp *http.Response, err error) { - req, _ := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/action/"+actionID, nil) - req.Header.Set("Want-Object", "1") // opt in to single roundtrip protocol - if c.accessToken != "" { - req.Header.Set("Authorization", "Bearer "+c.accessToken) - } - - res, err := c.cl.Do(req) - if err != nil { - return "", nil, err - } - defer func() { - if resp == nil { - drainAndClose(res.Body) - } - }() - if res.StatusCode == http.StatusNotFound { - return "", nil, nil - } - if res.StatusCode != http.StatusOK { - msg := tryReadErrorMessage(res) - if c.verbose { - log.Printf("error GET /action/%s: %v, %s", actionID, res.Status, msg) - } - return "", nil, fmt.Errorf("unexpected GET /action/%s status %v", actionID, res.Status) - } - - outputID = res.Header.Get("Go-Output-Id") - if outputID == "" { - return "", nil, fmt.Errorf("missing Go-Output-Id header in response") - } - if res.ContentLength == -1 { - return "", nil, fmt.Errorf("no Content-Length from server") - } - return outputID, res, nil -} - -func (c *gocachedClient) put(ctx context.Context, actionID, outputID string, size int64, body io.Reader) error { - req, _ := http.NewRequestWithContext(ctx, "PUT", c.baseURL+"/"+actionID+"/"+outputID, body) - req.ContentLength = size - if c.accessToken != "" { - req.Header.Set("Authorization", "Bearer "+c.accessToken) - } - res, err := c.cl.Do(req) - if err != nil { - if c.verbose { - log.Printf("error PUT /%s/%s: %v", actionID, outputID, err) - } - return err - } - defer res.Body.Close() - if res.StatusCode != http.StatusNoContent { - msg := tryReadErrorMessage(res) - if c.verbose { - log.Printf("error PUT /%s/%s: %v, %s", actionID, outputID, res.Status, msg) - } - return fmt.Errorf("unexpected PUT /%s/%s status %v", actionID, outputID, res.Status) - } - - return nil -} - -func (c *gocachedClient) fetchStats() (string, error) { - req, _ := http.NewRequest("GET", c.baseURL+"/session/stats", nil) - req.Header.Set("Authorization", "Bearer "+c.accessToken) - resp, err := c.cl.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - return string(b), nil -} diff --git a/cmd/cloner/cloner.go b/cmd/cloner/cloner.go index a81bd10bd5401..a3f0684faa589 100644 --- a/cmd/cloner/cloner.go +++ b/cmd/cloner/cloner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Cloner is a tool to automate the creation of a Clone method. diff --git a/cmd/cloner/cloner_test.go b/cmd/cloner/cloner_test.go index 754a4ac49a220..b06f5c4fa5610 100644 --- a/cmd/cloner/cloner_test.go +++ b/cmd/cloner/cloner_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/cloner/clonerex/clonerex.go b/cmd/cloner/clonerex/clonerex.go index b9f6d60dedb35..1007d0c6b646d 100644 --- a/cmd/cloner/clonerex/clonerex.go +++ b/cmd/cloner/clonerex/clonerex.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type SliceContainer,InterfaceContainer,MapWithPointers,DeeplyNestedMap diff --git a/cmd/cloner/clonerex/clonerex_clone.go b/cmd/cloner/clonerex/clonerex_clone.go index 13e1276c4e4b8..5c161239fc992 100644 --- a/cmd/cloner/clonerex/clonerex_clone.go +++ b/cmd/cloner/clonerex/clonerex_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/cmd/connector-gen/advertise-routes.go b/cmd/connector-gen/advertise-routes.go index 446f4906a4d65..57c101e27af7c 100644 --- a/cmd/connector-gen/advertise-routes.go +++ b/cmd/connector-gen/advertise-routes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/connector-gen/aws.go b/cmd/connector-gen/aws.go index bd2632ae27960..b0d6566b915f6 100644 --- a/cmd/connector-gen/aws.go +++ b/cmd/connector-gen/aws.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/connector-gen/connector-gen.go b/cmd/connector-gen/connector-gen.go index 6947f6410a96f..8693a1bf0490f 100644 --- a/cmd/connector-gen/connector-gen.go +++ b/cmd/connector-gen/connector-gen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // connector-gen is a tool to generate app connector configuration and flags from service provider address data. diff --git a/cmd/connector-gen/github.go b/cmd/connector-gen/github.go index def40872d52c1..a0162aa06cae3 100644 --- a/cmd/connector-gen/github.go +++ b/cmd/connector-gen/github.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/containerboot/egressservices.go b/cmd/containerboot/egressservices.go index 21d9f0bcb9a2b..e60d65c047f95 100644 --- a/cmd/containerboot/egressservices.go +++ b/cmd/containerboot/egressservices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -478,7 +478,8 @@ func (ep *egressProxy) tailnetTargetIPsForSvc(svc egressservices.Config, n ipn.N } egressAddrs, err := resolveTailnetFQDN(n.NetMap, svc.TailnetTarget.FQDN) if err != nil { - return nil, fmt.Errorf("error fetching backend addresses for %q: %w", svc.TailnetTarget.FQDN, err) + log.Printf("error fetching backend addresses for %q: %v", svc.TailnetTarget.FQDN, err) + return addrs, nil } if len(egressAddrs) == 0 { log.Printf("tailnet target %q does not have any backend addresses, skipping", svc.TailnetTarget.FQDN) diff --git a/cmd/containerboot/egressservices_test.go b/cmd/containerboot/egressservices_test.go index 724626b072c2b..0d8504bdad7fd 100644 --- a/cmd/containerboot/egressservices_test.go +++ b/cmd/containerboot/egressservices_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/forwarding.go b/cmd/containerboot/forwarding.go index 04d34836c92d8..0ec9c36c0bd30 100644 --- a/cmd/containerboot/forwarding.go +++ b/cmd/containerboot/forwarding.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/ingressservices.go b/cmd/containerboot/ingressservices.go index 1a2da95675f4e..d76bf86e0b8ec 100644 --- a/cmd/containerboot/ingressservices.go +++ b/cmd/containerboot/ingressservices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/ingressservices_test.go b/cmd/containerboot/ingressservices_test.go index 228bbb159f463..46330103e343b 100644 --- a/cmd/containerboot/ingressservices_test.go +++ b/cmd/containerboot/ingressservices_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/kube.go b/cmd/containerboot/kube.go index e566fa483447c..73f5819b406db 100644 --- a/cmd/containerboot/kube.go +++ b/cmd/containerboot/kube.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -14,9 +14,12 @@ import ( "net/http" "net/netip" "os" + "path/filepath" "strings" "time" + "github.com/fsnotify/fsnotify" + "tailscale.com/client/local" "tailscale.com/ipn" "tailscale.com/kube/egressservices" "tailscale.com/kube/ingressservices" @@ -26,9 +29,11 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/util/backoff" - "tailscale.com/util/set" ) +const fieldManager = "tailscale-container" +const kubeletMountedConfigLn = "..data" + // kubeClient is a wrapper around Tailscale's internal kube client that knows how to talk to the kube API server. We use // this rather than any of the upstream Kubernetes client libaries to avoid extra imports. type kubeClient struct { @@ -46,7 +51,7 @@ func newKubeClient(root string, stateSecret string) (*kubeClient, error) { var err error kc, err := kubeclient.New("tailscale-container") if err != nil { - return nil, fmt.Errorf("Error creating kube client: %w", err) + return nil, fmt.Errorf("error creating kube client: %w", err) } if (root != "/") || os.Getenv("TS_KUBERNETES_READ_API_SERVER_ADDRESS_FROM_ENV") == "true" { // Derive the API server address from the environment variables @@ -63,7 +68,7 @@ func (kc *kubeClient) storeDeviceID(ctx context.Context, deviceID tailcfg.Stable kubetypes.KeyDeviceID: []byte(deviceID), }, } - return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, "tailscale-container") + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) } // storeDeviceEndpoints writes device's tailnet IPs and MagicDNS name to fields 'device_ips', 'device_fqdn' of client's @@ -84,7 +89,7 @@ func (kc *kubeClient) storeDeviceEndpoints(ctx context.Context, fqdn string, add kubetypes.KeyDeviceIPs: deviceIPs, }, } - return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, "tailscale-container") + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) } // storeHTTPSEndpoint writes an HTTPS endpoint exposed by this device via 'tailscale serve' to the client's state @@ -96,7 +101,7 @@ func (kc *kubeClient) storeHTTPSEndpoint(ctx context.Context, ep string) error { kubetypes.KeyHTTPSEndpoint: []byte(ep), }, } - return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, "tailscale-container") + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) } // deleteAuthKey deletes the 'authkey' field of the given kube @@ -122,7 +127,7 @@ func (kc *kubeClient) deleteAuthKey(ctx context.Context) error { // resetContainerbootState resets state from previous runs of containerboot to // ensure the operator doesn't use stale state when a Pod is first recreated. -func (kc *kubeClient) resetContainerbootState(ctx context.Context, podUID string) error { +func (kc *kubeClient) resetContainerbootState(ctx context.Context, podUID string, tailscaledConfigAuthkey string) error { existingSecret, err := kc.GetSecret(ctx, kc.stateSecret) switch { case kubeclient.IsNotFoundErr(err): @@ -131,32 +136,135 @@ func (kc *kubeClient) resetContainerbootState(ctx context.Context, podUID string case err != nil: return fmt.Errorf("failed to read state Secret %q to reset state: %w", kc.stateSecret, err) } + s := &kubeapi.Secret{ Data: map[string][]byte{ kubetypes.KeyCapVer: fmt.Appendf(nil, "%d", tailcfg.CurrentCapabilityVersion), + + // TODO(tomhjp): Perhaps shouldn't clear device ID and use a different signal, as this could leak tailnet devices. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, }, } if podUID != "" { s.Data[kubetypes.KeyPodUID] = []byte(podUID) } - toClear := set.SetOf([]string{ - kubetypes.KeyDeviceID, - kubetypes.KeyDeviceFQDN, - kubetypes.KeyDeviceIPs, - kubetypes.KeyHTTPSEndpoint, - egressservices.KeyEgressServices, - ingressservices.IngressConfigKey, - }) - for key := range existingSecret.Data { - if toClear.Contains(key) { - // It's fine to leave the key in place as a debugging breadcrumb, - // it should get a new value soon. - s.Data[key] = nil + // Only clear reissue_authkey if the operator has actioned it. + brokenAuthkey, ok := existingSecret.Data[kubetypes.KeyReissueAuthkey] + if ok && tailscaledConfigAuthkey != "" && string(brokenAuthkey) != tailscaledConfigAuthkey { + s.Data[kubetypes.KeyReissueAuthkey] = nil + } + + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) +} + +func (kc *kubeClient) setAndWaitForAuthKeyReissue(ctx context.Context, client *local.Client, cfg *settings, tailscaledConfigAuthKey string) error { + err := client.DisconnectControl(ctx) + if err != nil { + return fmt.Errorf("error disconnecting from control: %w", err) + } + + err = kc.setReissueAuthKey(ctx, tailscaledConfigAuthKey) + if err != nil { + return fmt.Errorf("failed to set reissue_authkey in Kubernetes Secret: %w", err) + } + + err = kc.waitForAuthKeyReissue(ctx, cfg.TailscaledConfigFilePath, tailscaledConfigAuthKey, 10*time.Minute) + if err != nil { + return fmt.Errorf("failed to receive new auth key: %w", err) + } + + return nil +} + +func (kc *kubeClient) setReissueAuthKey(ctx context.Context, authKey string) error { + s := &kubeapi.Secret{ + Data: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte(authKey), + }, + } + + log.Printf("Requesting a new auth key from operator") + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) +} + +func (kc *kubeClient) waitForAuthKeyReissue(ctx context.Context, configPath string, oldAuthKey string, maxWait time.Duration) error { + log.Printf("Waiting for operator to provide new auth key (max wait: %v)", maxWait) + + ctx, cancel := context.WithTimeout(ctx, maxWait) + defer cancel() + + tailscaledCfgDir := filepath.Dir(configPath) + toWatch := filepath.Join(tailscaledCfgDir, kubeletMountedConfigLn) + + var ( + pollTicker <-chan time.Time + eventChan <-chan fsnotify.Event + ) + + pollInterval := 5 * time.Second + + // Try to use fsnotify for faster notification + if w, err := fsnotify.NewWatcher(); err != nil { + log.Printf("auth key reissue: fsnotify unavailable, using polling: %v", err) + } else if err := w.Add(tailscaledCfgDir); err != nil { + w.Close() + log.Printf("auth key reissue: fsnotify watch failed, using polling: %v", err) + } else { + defer w.Close() + log.Printf("auth key reissue: watching for config changes via fsnotify") + eventChan = w.Events + } + + // still keep polling if using fsnotify, for logging and in case fsnotify fails + pt := time.NewTicker(pollInterval) + defer pt.Stop() + pollTicker = pt.C + + start := time.Now() + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("timeout waiting for auth key reissue after %v", maxWait) + case <-pollTicker: // Waits for polling tick, continues when received + case event := <-eventChan: + if event.Name != toWatch { + continue + } + } + + newAuthKey := authkeyFromTailscaledConfig(configPath) + if newAuthKey != "" && newAuthKey != oldAuthKey { + log.Printf("New auth key received from operator after %v", time.Since(start).Round(time.Second)) + + if err := kc.clearReissueAuthKeyRequest(ctx); err != nil { + log.Printf("Warning: failed to clear reissue request: %v", err) + } + + return nil + } + + if eventChan == nil && pollTicker != nil { + log.Printf("Waiting for new auth key from operator (%v elapsed)", time.Since(start).Round(time.Second)) } } +} - return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, "tailscale-container") +// clearReissueAuthKeyRequest removes the reissue_authkey marker from the Secret +// to signal to the operator that we've successfully received the new key. +func (kc *kubeClient) clearReissueAuthKeyRequest(ctx context.Context) error { + s := &kubeapi.Secret{ + Data: map[string][]byte{ + kubetypes.KeyReissueAuthkey: nil, + }, + } + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) } // waitForConsistentState waits for tailscaled to finish writing state if it diff --git a/cmd/containerboot/kube_test.go b/cmd/containerboot/kube_test.go index c33714ed12ace..6acaa60e1588e 100644 --- a/cmd/containerboot/kube_test.go +++ b/cmd/containerboot/kube_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -248,25 +248,42 @@ func TestResetContainerbootState(t *testing.T) { capver := fmt.Appendf(nil, "%d", tailcfg.CurrentCapabilityVersion) for name, tc := range map[string]struct { podUID string + authkey string initial map[string][]byte expected map[string][]byte }{ "empty_initial": { podUID: "1234", + authkey: "new-authkey", initial: map[string][]byte{}, expected: map[string][]byte{ kubetypes.KeyCapVer: capver, kubetypes.KeyPodUID: []byte("1234"), + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, }, }, "empty_initial_no_pod_uid": { initial: map[string][]byte{}, expected: map[string][]byte{ kubetypes.KeyCapVer: capver, + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, }, }, "only_relevant_keys_updated": { - podUID: "1234", + podUID: "1234", + authkey: "new-authkey", initial: map[string][]byte{ kubetypes.KeyCapVer: []byte("1"), kubetypes.KeyPodUID: []byte("5678"), @@ -295,6 +312,57 @@ func TestResetContainerbootState(t *testing.T) { // Tailscaled keys not included in patch. }, }, + "new_authkey_issued": { + initial: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte("old-authkey"), + }, + authkey: "new-authkey", + expected: map[string][]byte{ + kubetypes.KeyCapVer: capver, + kubetypes.KeyReissueAuthkey: nil, + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, + }, + }, + "authkey_not_yet_updated": { + initial: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte("old-authkey"), + }, + authkey: "old-authkey", + expected: map[string][]byte{ + kubetypes.KeyCapVer: capver, + // reissue_authkey not cleared. + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, + }, + }, + "authkey_deleted_from_config": { + initial: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte("old-authkey"), + }, + authkey: "", + expected: map[string][]byte{ + kubetypes.KeyCapVer: capver, + // reissue_authkey not cleared. + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, + }, + }, } { t.Run(name, func(t *testing.T) { var actual map[string][]byte @@ -309,7 +377,7 @@ func TestResetContainerbootState(t *testing.T) { return nil }, }} - if err := kc.resetContainerbootState(context.Background(), tc.podUID); err != nil { + if err := kc.resetContainerbootState(context.Background(), tc.podUID, tc.authkey); err != nil { t.Fatalf("resetContainerbootState() error = %v", err) } if diff := cmp.Diff(tc.expected, actual); diff != "" { diff --git a/cmd/containerboot/main.go b/cmd/containerboot/main.go index a520b5756ade5..c020ab0a94402 100644 --- a/cmd/containerboot/main.go +++ b/cmd/containerboot/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -101,6 +101,10 @@ // cluster using the same hostname (in this case, the MagicDNS name of the ingress proxy) // as a non-cluster workload on tailnet. // This is only meant to be configured by the Kubernetes operator. +// - TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT: If set to true and if this +// containerboot instance is not running in Kubernetes, autoadvertise any services +// defined in the devices serve config, and unadvertise on shutdown. Defaults +// to `true`, but can be disabled to allow user specific advertisement configuration. // // When running on Kubernetes, containerboot defaults to storing state in the // "tailscale" kube secret. To store state on local disk instead, set @@ -133,10 +137,13 @@ import ( "golang.org/x/sys/unix" "tailscale.com/client/tailscale" + "tailscale.com/health" "tailscale.com/ipn" + "tailscale.com/ipn/conffile" kubeutils "tailscale.com/k8s-operator" healthz "tailscale.com/kube/health" "tailscale.com/kube/kubetypes" + klc "tailscale.com/kube/localclient" "tailscale.com/kube/metrics" "tailscale.com/kube/services" "tailscale.com/tailcfg" @@ -155,6 +162,10 @@ func newNetfilterRunner(logf logger.Logf) (linuxfw.NetfilterRunner, error) { return linuxfw.New(logf, "") } +func getAutoAdvertiseBool() bool { + return defaultBool("TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", true) +} + func main() { if err := run(); err != nil && !errors.Is(err, context.Canceled) { log.Fatal(err) @@ -198,8 +209,13 @@ func run() error { bootCtx, cancel := context.WithTimeout(ctx, 60*time.Second) defer cancel() + var tailscaledConfigAuthkey string + if isOneStepConfig(cfg) { + tailscaledConfigAuthkey = authkeyFromTailscaledConfig(cfg.TailscaledConfigFilePath) + } + var kc *kubeClient - if cfg.InKubernetes { + if cfg.KubeSecret != "" { kc, err = newKubeClient(cfg.Root, cfg.KubeSecret) if err != nil { return fmt.Errorf("error initializing kube client: %w", err) @@ -211,7 +227,7 @@ func run() error { // hasKubeStateStore because although we know we're in kube, that // doesn't guarantee the state store is properly configured. if hasKubeStateStore(cfg) { - if err := kc.resetContainerbootState(bootCtx, cfg.PodUID); err != nil { + if err := kc.resetContainerbootState(bootCtx, cfg.PodUID, tailscaledConfigAuthkey); err != nil { return fmt.Errorf("error clearing previous state from Secret: %w", err) } } @@ -229,6 +245,7 @@ func run() error { ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) defer cancel() + // we are shutting down, we always want to unadvertise here if err := services.EnsureServicesNotAdvertised(ctx, client, log.Printf); err != nil { log.Printf("Error ensuring services are not advertised: %v", err) } @@ -290,7 +307,7 @@ func run() error { } } - w, err := client.WatchIPNBus(bootCtx, ipn.NotifyInitialNetMap|ipn.NotifyInitialPrefs|ipn.NotifyInitialState) + w, err := client.WatchIPNBus(bootCtx, ipn.NotifyInitialNetMap|ipn.NotifyInitialPrefs|ipn.NotifyInitialState|ipn.NotifyInitialHealthState) if err != nil { return fmt.Errorf("failed to watch tailscaled for updates: %w", err) } @@ -356,8 +373,23 @@ authLoop: if isOneStepConfig(cfg) { // This could happen if this is the first time tailscaled was run for this // device and the auth key was not passed via the configfile. - return fmt.Errorf("invalid state: tailscaled daemon started with a config file, but tailscale is not logged in: ensure you pass a valid auth key in the config file.") + if hasKubeStateStore(cfg) { + log.Printf("Auth key missing or invalid (NeedsLogin state), disconnecting from control and requesting new key from operator") + + err := kc.setAndWaitForAuthKeyReissue(bootCtx, client, cfg, tailscaledConfigAuthkey) + if err != nil { + return fmt.Errorf("failed to get a reissued authkey: %w", err) + } + + log.Printf("Successfully received new auth key, restarting to apply configuration") + + // we don't return an error here since we have handled the reissue gracefully. + return nil + } + + return errors.New("invalid state: tailscaled daemon started with a config file, but tailscale is not logged in: ensure you pass a valid auth key in the config file") } + if err := authTailscale(); err != nil { return fmt.Errorf("failed to auth tailscale: %w", err) } @@ -375,6 +407,27 @@ authLoop: log.Printf("tailscaled in state %q, waiting", *n.State) } } + + if n.Health != nil { + // This can happen if the config has an auth key but it's invalid, + // for example if it was single-use and already got used, but the + // device state was lost. + if _, ok := n.Health.Warnings[health.LoginStateWarnable.Code]; ok { + if isOneStepConfig(cfg) && hasKubeStateStore(cfg) { + log.Printf("Auth key failed to authenticate (may be expired or single-use), disconnecting from control and requesting new key from operator") + + err := kc.setAndWaitForAuthKeyReissue(bootCtx, client, cfg, tailscaledConfigAuthkey) + if err != nil { + return fmt.Errorf("failed to get a reissued authkey: %w", err) + } + + // we don't return an error here since we have handled the reissue gracefully. + log.Printf("Successfully received new auth key, restarting to apply configuration") + + return nil + } + } + } } w.Close() @@ -400,9 +453,9 @@ authLoop: // We were told to only auth once, so any secret-bound // authkey is no longer needed. We don't strictly need to // wipe it, but it's good hygiene. - log.Printf("Deleting authkey from kube secret") + log.Printf("Deleting authkey from Kubernetes Secret") if err := kc.deleteAuthKey(ctx); err != nil { - return fmt.Errorf("deleting authkey from kube secret: %w", err) + return fmt.Errorf("deleting authkey from Kubernetes Secret: %w", err) } } @@ -413,8 +466,10 @@ authLoop: // If tailscaled config was read from a mounted file, watch the file for updates and reload. cfgWatchErrChan := make(chan error) + cfgWatchCtx, cfgWatchCancel := context.WithCancel(ctx) + defer cfgWatchCancel() if cfg.TailscaledConfigFilePath != "" { - go watchTailscaledConfigChanges(ctx, cfg.TailscaledConfigFilePath, client, cfgWatchErrChan) + go watchTailscaledConfigChanges(cfgWatchCtx, cfg.TailscaledConfigFilePath, client, cfgWatchErrChan) } var ( @@ -514,6 +569,7 @@ runLoop: case err := <-cfgWatchErrChan: return fmt.Errorf("failed to watch tailscaled config: %w", err) case n := <-notifyChan: + // TODO: (ChaosInTheCRD) Add node removed check when supported by ipn if n.State != nil && *n.State != ipn.Running { // Something's gone wrong and we've left the authenticated state. // Our container image never recovered gracefully from this, and the @@ -652,9 +708,22 @@ runLoop: healthCheck.Update(len(addrs) != 0) } + var prevServeConfig *ipn.ServeConfig + if getAutoAdvertiseBool() { + prevServeConfig, err = client.GetServeConfig(ctx) + if err != nil { + return fmt.Errorf("autoadvertisement: failed to get serve config: %w", err) + } + + err = refreshAdvertiseServices(ctx, prevServeConfig, klc.New(client)) + if err != nil { + return fmt.Errorf("autoadvertisement: failed to refresh advertise services: %w", err) + } + } + if cfg.ServeConfigPath != "" { triggerWatchServeConfigChanges.Do(func() { - go watchServeConfigChanges(ctx, certDomainChanged, certDomain, client, kc, cfg) + go watchServeConfigChanges(ctx, certDomainChanged, certDomain, client, kc, cfg, prevServeConfig) }) } @@ -957,3 +1026,11 @@ func serviceIPsFromNetMap(nm *netmap.NetworkMap, fqdn dnsname.FQDN) []netip.Pref return prefixes } + +func authkeyFromTailscaledConfig(path string) string { + if cfg, err := conffile.Load(path); err == nil && cfg.Parsed.AuthKey != nil { + return *cfg.Parsed.AuthKey + } + + return "" +} diff --git a/cmd/containerboot/main_test.go b/cmd/containerboot/main_test.go index 7007cc15202d9..f1d892a19d118 100644 --- a/cmd/containerboot/main_test.go +++ b/cmd/containerboot/main_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -31,6 +31,7 @@ import ( "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" + "tailscale.com/health" "tailscale.com/ipn" "tailscale.com/kube/egressservices" "tailscale.com/kube/kubeclient" @@ -41,6 +42,8 @@ import ( "tailscale.com/types/ptr" ) +const configFileAuthKey = "some-auth-key" + func TestContainerBoot(t *testing.T) { boot := filepath.Join(t.TempDir(), "containerboot") if err := exec.Command("go", "build", "-ldflags", "-X main.testSleepDuration=1ms", "-o", boot, "tailscale.com/cmd/containerboot").Run(); err != nil { @@ -77,6 +80,10 @@ func TestContainerBoot(t *testing.T) { // phase (simulates our fake tailscaled doing it). UpdateKubeSecret map[string]string + // Update files with these paths/contents at the beginning of the phase + // (simulates the operator updating mounted config files). + UpdateFiles map[string]string + // WantFiles files that should exist in the container and their // contents. WantFiles map[string]string @@ -781,6 +788,127 @@ func TestContainerBoot(t *testing.T) { }, } }, + "sets_reissue_authkey_if_needs_login": func(env *testEnv) testCase { + newAuthKey := "new-reissued-auth-key" + return testCase{ + Env: map[string]string{ + "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR": filepath.Join(env.d, "etc/tailscaled/"), + "KUBERNETES_SERVICE_HOST": env.kube.Host, + "KUBERNETES_SERVICE_PORT_HTTPS": env.kube.Port, + }, + Phases: []phase{ + { + UpdateFiles: map[string]string{ + "etc/tailscaled/..data": "", + }, + WantCmds: []string{ + "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking --config=/etc/tailscaled/cap-95.hujson", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + }, + }, { + Notify: &ipn.Notify{ + State: new(ipn.NeedsLogin), + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + kubetypes.KeyReissueAuthkey: configFileAuthKey, + }, + WantLog: "watching for config changes via fsnotify", + }, { + UpdateFiles: map[string]string{ + "etc/tailscaled/cap-95.hujson": fmt.Sprintf(`{"Version":"alpha0","AuthKey":"%s"}`, newAuthKey), + "etc/tailscaled/..data": "updated", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + }, + WantExitCode: new(0), + WantLog: "Successfully received new auth key, restarting to apply configuration", + }, + }, + } + }, + "sets_reissue_authkey_if_auth_fails": func(env *testEnv) testCase { + newAuthKey := "new-reissued-auth-key" + return testCase{ + Env: map[string]string{ + "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR": filepath.Join(env.d, "etc/tailscaled/"), + "KUBERNETES_SERVICE_HOST": env.kube.Host, + "KUBERNETES_SERVICE_PORT_HTTPS": env.kube.Port, + }, + Phases: []phase{ + { + UpdateFiles: map[string]string{ + "etc/tailscaled/..data": "", + }, + WantCmds: []string{ + "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking --config=/etc/tailscaled/cap-95.hujson", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + }, + }, { + Notify: &ipn.Notify{ + Health: &health.State{ + Warnings: map[health.WarnableCode]health.UnhealthyState{ + health.LoginStateWarnable.Code: {}, + }, + }, + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + kubetypes.KeyReissueAuthkey: configFileAuthKey, + }, + WantLog: "watching for config changes via fsnotify", + }, { + UpdateFiles: map[string]string{ + "etc/tailscaled/cap-95.hujson": fmt.Sprintf(`{"Version":"alpha0","AuthKey":"%s"}`, newAuthKey), + "etc/tailscaled/..data": "updated", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + }, + WantExitCode: new(0), + WantLog: "Successfully received new auth key, restarting to apply configuration", + }, + }, + } + }, + "clears_reissue_authkey_on_change": func(env *testEnv) testCase { + return testCase{ + Env: map[string]string{ + "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR": filepath.Join(env.d, "etc/tailscaled/"), + "KUBERNETES_SERVICE_HOST": env.kube.Host, + "KUBERNETES_SERVICE_PORT_HTTPS": env.kube.Port, + }, + KubeSecret: map[string]string{ + kubetypes.KeyReissueAuthkey: "some-older-authkey", + "foo": "bar", // Check not everything is cleared. + }, + Phases: []phase{ + { + WantCmds: []string{ + "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking --config=/etc/tailscaled/cap-95.hujson", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + "foo": "bar", + }, + }, { + Notify: runningNotify, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + "foo": "bar", + kubetypes.KeyDeviceFQDN: "test-node.test.ts.net.", + kubetypes.KeyDeviceID: "myID", + kubetypes.KeyDeviceIPs: `["100.64.0.1"]`, + }, + }, + }, + } + }, "metrics_enabled": func(env *testEnv) testCase { return testCase{ Env: map[string]string{ @@ -1009,6 +1137,25 @@ func TestContainerBoot(t *testing.T) { }, } }, + "serve_config_with_service_auto_advertisement": func(env *testEnv) testCase { + return testCase{ + Env: map[string]string{ + "TS_SERVE_CONFIG": filepath.Join(env.d, "etc/tailscaled/serve-config-with-services.json"), + "TS_AUTHKEY": "tskey-key", + }, + Phases: []phase{ + { + WantCmds: []string{ + "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking", + "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key", + }, + }, + { + Notify: runningNotify, + }, + }, + } + }, "kube_shutdown_during_state_write": func(env *testEnv) testCase { return testCase{ Env: map[string]string{ @@ -1115,19 +1262,22 @@ func TestContainerBoot(t *testing.T) { for k, v := range p.UpdateKubeSecret { env.kube.SetSecret(k, v) } + for path, content := range p.UpdateFiles { + fullPath := filepath.Join(env.d, path) + if err := os.WriteFile(fullPath, []byte(content), 0700); err != nil { + t.Fatalf("phase %d: updating file %q: %v", i, path, err) + } + // Explicitly update mtime to ensure fsnotify detects the change. + // Without this, file operations can be buffered and fsnotify events may not trigger. + now := time.Now() + if err := os.Chtimes(fullPath, now, now); err != nil { + t.Fatalf("phase %d: updating mtime for %q: %v", i, path, err) + } + } env.lapi.Notify(p.Notify) if p.Signal != nil { cmd.Process.Signal(*p.Signal) } - if p.WantLog != "" { - err := tstest.WaitFor(2*time.Second, func() error { - waitLogLine(t, time.Second, cbOut, p.WantLog) - return nil - }) - if err != nil { - t.Fatal(err) - } - } if p.WantExitCode != nil { state, err := cmd.Process.Wait() @@ -1137,14 +1287,19 @@ func TestContainerBoot(t *testing.T) { if state.ExitCode() != *p.WantExitCode { t.Fatalf("phase %d: want exit code %d, got %d", i, *p.WantExitCode, state.ExitCode()) } + } - // Early test return, we don't expect the successful startup log message. - return + if p.WantLog != "" { + err := tstest.WaitFor(5*time.Second, func() error { + waitLogLine(t, 5*time.Second, cbOut, p.WantLog) + return nil + }) + if err != nil { + t.Fatal(err) + } } - wantCmds = append(wantCmds, p.WantCmds...) - waitArgs(t, 2*time.Second, env.d, env.argFile, strings.Join(wantCmds, "\n")) - err := tstest.WaitFor(2*time.Second, func() error { + err := tstest.WaitFor(5*time.Second, func() error { if p.WantKubeSecret != nil { got := env.kube.Secret() if diff := cmp.Diff(got, p.WantKubeSecret); diff != "" { @@ -1159,8 +1314,18 @@ func TestContainerBoot(t *testing.T) { return nil }) if err != nil { - t.Fatalf("phase %d: %v", i, err) + t.Fatalf("test: %q phase %d: %v", name, i, err) + } + + // if we provide a wanted exit code, we expect that the process is finished, + // so should return from the test. + if p.WantExitCode != nil { + return } + + wantCmds = append(wantCmds, p.WantCmds...) + waitArgs(t, 2*time.Second, env.d, env.argFile, strings.Join(wantCmds, "\n")) + err = tstest.WaitFor(2*time.Second, func() error { for path, want := range p.WantFiles { gotBs, err := os.ReadFile(filepath.Join(env.d, path)) @@ -1340,10 +1505,16 @@ func (lc *localAPI) Notify(n *ipn.Notify) { func (lc *localAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/localapi/v0/serve-config": - if r.Method != "POST" { + switch r.Method { + case "GET": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&ipn.ServeConfig{}) + return + case "POST": + return + default: panic(fmt.Sprintf("unsupported method %q", r.Method)) } - return case "/localapi/v0/watch-ipn-bus": if r.Method != "GET" { panic(fmt.Sprintf("unsupported method %q", r.Method)) @@ -1355,9 +1526,25 @@ func (lc *localAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("fake metrics")) return case "/localapi/v0/prefs": - if r.Method != "GET" { + switch r.Method { + case "GET": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&ipn.Prefs{}) + return + case "PATCH": + // EditPrefs - just return empty prefs + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&ipn.Prefs{}) + return + default: panic(fmt.Sprintf("unsupported method %q", r.Method)) } + // In the localAPI ServeHTTP method + case "/localapi/v0/disconnect-control": + if r.Method != "POST" { + panic(fmt.Sprintf("unsupported method %q", r.Method)) + } + w.WriteHeader(http.StatusOK) return default: panic(fmt.Sprintf("unsupported path %q", r.URL.Path)) @@ -1559,7 +1746,11 @@ func (k *kubeServer) serveSecret(w http.ResponseWriter, r *http.Request) { panic(fmt.Sprintf("json decode failed: %v. Body:\n\n%s", err, string(bs))) } for key, val := range req.Data { - k.secret[key] = string(val) + if val == nil { + delete(k.secret, key) + } else { + k.secret[key] = string(val) + } } default: panic(fmt.Sprintf("unknown content type %q", r.Header.Get("Content-Type"))) @@ -1569,12 +1760,6 @@ func (k *kubeServer) serveSecret(w http.ResponseWriter, r *http.Request) { } } -func mustBase64(t *testing.T, v any) string { - b := mustJSON(t, v) - s := base64.StdEncoding.WithPadding('=').EncodeToString(b) - return s -} - func mustJSON(t *testing.T, v any) []byte { b, err := json.Marshal(v) if err != nil { @@ -1633,8 +1818,15 @@ func newTestEnv(t *testing.T) testEnv { kube.Start(t) t.Cleanup(kube.Close) - tailscaledConf := &ipn.ConfigVAlpha{AuthKey: ptr.To("foo"), Version: "alpha0"} + tailscaledConf := &ipn.ConfigVAlpha{AuthKey: new(configFileAuthKey), Version: "alpha0"} serveConf := ipn.ServeConfig{TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}} + serveConfWithServices := ipn.ServeConfig{ + TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:test-service-1": {}, + "svc:test-service-2": {}, + }, + } egressCfg := egressSvcConfig("foo", "foo.tailnetxyz.ts.net") dirs := []string{ @@ -1652,15 +1844,16 @@ func newTestEnv(t *testing.T) testEnv { } } files := map[string][]byte{ - "usr/bin/tailscaled": fakeTailscaled, - "usr/bin/tailscale": fakeTailscale, - "usr/bin/iptables": fakeTailscale, - "usr/bin/ip6tables": fakeTailscale, - "dev/net/tun": []byte(""), - "proc/sys/net/ipv4/ip_forward": []byte("0"), - "proc/sys/net/ipv6/conf/all/forwarding": []byte("0"), - "etc/tailscaled/cap-95.hujson": mustJSON(t, tailscaledConf), - "etc/tailscaled/serve-config.json": mustJSON(t, serveConf), + "usr/bin/tailscaled": fakeTailscaled, + "usr/bin/tailscale": fakeTailscale, + "usr/bin/iptables": fakeTailscale, + "usr/bin/ip6tables": fakeTailscale, + "dev/net/tun": []byte(""), + "proc/sys/net/ipv4/ip_forward": []byte("0"), + "proc/sys/net/ipv6/conf/all/forwarding": []byte("0"), + "etc/tailscaled/cap-95.hujson": mustJSON(t, tailscaledConf), + "etc/tailscaled/serve-config.json": mustJSON(t, serveConf), + "etc/tailscaled/serve-config-with-services.json": mustJSON(t, serveConfWithServices), filepath.Join("etc/tailscaled/", egressservices.KeyEgressServices): mustJSON(t, egressCfg), filepath.Join("etc/tailscaled/", egressservices.KeyHEPPings): []byte("4"), } diff --git a/cmd/containerboot/serve.go b/cmd/containerboot/serve.go index 5fa8e580d5828..f64d2d24f681f 100644 --- a/cmd/containerboot/serve.go +++ b/cmd/containerboot/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -9,6 +9,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "log" "os" "path/filepath" @@ -22,6 +23,7 @@ import ( "tailscale.com/kube/certs" "tailscale.com/kube/kubetypes" klc "tailscale.com/kube/localclient" + "tailscale.com/kube/services" "tailscale.com/types/netmap" ) @@ -29,8 +31,9 @@ import ( // the serve config from it, replacing ${TS_CERT_DOMAIN} with certDomain, and // applies it to lc. It exits when ctx is canceled. cdChanged is a channel that // is written to when the certDomain changes, causing the serve config to be -// re-read and applied. -func watchServeConfigChanges(ctx context.Context, cdChanged <-chan bool, certDomainAtomic *atomic.Pointer[string], lc *local.Client, kc *kubeClient, cfg *settings) { +// re-read and applied. prevServeConfig is the serve config that was fetched +// during startup. This will be refreshed by the goroutine when serve config changes. +func watchServeConfigChanges(ctx context.Context, cdChanged <-chan bool, certDomainAtomic *atomic.Pointer[string], lc *local.Client, kc *kubeClient, cfg *settings, prevServeConfig *ipn.ServeConfig) { if certDomainAtomic == nil { panic("certDomainAtomic must not be nil") } @@ -53,11 +56,18 @@ func watchServeConfigChanges(ctx context.Context, cdChanged <-chan bool, certDom } var certDomain string - var prevServeConfig *ipn.ServeConfig var cm *certs.CertManager if cfg.CertShareMode == "rw" { cm = certs.NewCertManager(klc.New(lc), log.Printf) } + + var err error + if prevServeConfig == nil { + prevServeConfig, err = lc.GetServeConfig(ctx) + if err != nil { + log.Fatalf("serve proxy: failed to get serve config: %v", err) + } + } for { select { case <-ctx.Done(): @@ -70,35 +80,68 @@ func watchServeConfigChanges(ctx context.Context, cdChanged <-chan bool, certDom // k8s handles these mounts. So just re-read the file and apply it // if it's changed. } - sc, err := readServeConfig(cfg.ServeConfigPath, certDomain) - if err != nil { - log.Fatalf("serve proxy: failed to read serve config: %v", err) - } - if sc == nil { - log.Printf("serve proxy: no serve config at %q, skipping", cfg.ServeConfigPath) - continue - } - if prevServeConfig != nil && reflect.DeepEqual(sc, prevServeConfig) { - continue - } - if err := updateServeConfig(ctx, sc, certDomain, lc); err != nil { - log.Fatalf("serve proxy: error updating serve config: %v", err) - } - if kc != nil && kc.canPatch { - if err := kc.storeHTTPSEndpoint(ctx, certDomain); err != nil { - log.Fatalf("serve proxy: error storing HTTPS endpoint: %v", err) + + var sc *ipn.ServeConfig + if cfg.ServeConfigPath != "" { + sc, err := readServeConfig(cfg.ServeConfigPath, certDomain) + if err != nil { + log.Fatalf("serve proxy: failed to read serve config: %v", err) } + if sc == nil { + log.Printf("serve proxy: no serve config at %q, skipping", cfg.ServeConfigPath) + continue + } + if prevServeConfig != nil && reflect.DeepEqual(sc, prevServeConfig) { + continue + } + if err := updateServeConfig(ctx, sc, certDomain, klc.New(lc)); err != nil { + log.Fatalf("serve proxy: error updating serve config: %v", err) + } + if kc != nil && kc.canPatch { + if err := kc.storeHTTPSEndpoint(ctx, certDomain); err != nil { + log.Fatalf("serve proxy: error storing HTTPS endpoint: %v", err) + } + } + prevServeConfig = sc + if cfg.CertShareMode != "rw" { + continue + } + if err := cm.EnsureCertLoops(ctx, sc); err != nil { + log.Fatalf("serve proxy: error ensuring cert loops: %v", err) + } + } else { + log.Printf("serve config path not provided.") + sc = prevServeConfig } - prevServeConfig = sc - if cfg.CertShareMode != "rw" { - continue - } - if err := cm.EnsureCertLoops(ctx, sc); err != nil { - log.Fatalf("serve proxy: error ensuring cert loops: %v", err) + + // if we are running in kubernetes, we want to leave advertisement to the operator + // to do (by updating the serve config) + if getAutoAdvertiseBool() { + if err := refreshAdvertiseServices(ctx, sc, klc.New(lc)); err != nil { + log.Fatalf("error refreshing advertised services: %v", err) + } } } } +func refreshAdvertiseServices(ctx context.Context, sc *ipn.ServeConfig, lc klc.LocalClient) error { + if sc == nil || len(sc.Services) == 0 { + return nil + } + + var svcs []string + for svc := range sc.Services { + svcs = append(svcs, svc.String()) + } + + err := services.EnsureServicesAdvertised(ctx, svcs, lc, log.Printf) + if err != nil { + return fmt.Errorf("failed to ensure services advertised: %w", err) + } + + return nil +} + func certDomainFromNetmap(nm *netmap.NetworkMap) string { if len(nm.DNS.CertDomains) == 0 { return "" @@ -106,13 +149,7 @@ func certDomainFromNetmap(nm *netmap.NetworkMap) string { return nm.DNS.CertDomains[0] } -// localClient is a subset of [local.Client] that can be mocked for testing. -type localClient interface { - SetServeConfig(context.Context, *ipn.ServeConfig) error - CertPair(context.Context, string) ([]byte, []byte, error) -} - -func updateServeConfig(ctx context.Context, sc *ipn.ServeConfig, certDomain string, lc localClient) error { +func updateServeConfig(ctx context.Context, sc *ipn.ServeConfig, certDomain string, lc klc.LocalClient) error { if !isValidHTTPSConfig(certDomain, sc) { return nil } diff --git a/cmd/containerboot/serve_test.go b/cmd/containerboot/serve_test.go index fc18f254dad05..5da5ef5f737c3 100644 --- a/cmd/containerboot/serve_test.go +++ b/cmd/containerboot/serve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -12,9 +12,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "tailscale.com/client/local" "tailscale.com/ipn" "tailscale.com/kube/kubetypes" + "tailscale.com/kube/localclient" + "tailscale.com/tailcfg" ) func TestUpdateServeConfig(t *testing.T) { @@ -65,13 +66,13 @@ func TestUpdateServeConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - fakeLC := &fakeLocalClient{} + fakeLC := &localclient.FakeLocalClient{} err := updateServeConfig(context.Background(), tt.sc, tt.certDomain, fakeLC) if err != nil { t.Errorf("updateServeConfig() error = %v", err) } - if fakeLC.setServeCalled != tt.wantCall { - t.Errorf("SetServeConfig() called = %v, want %v", fakeLC.setServeCalled, tt.wantCall) + if fakeLC.SetServeCalled != tt.wantCall { + t.Errorf("SetServeConfig() called = %v, want %v", fakeLC.SetServeCalled, tt.wantCall) } }) } @@ -196,18 +197,114 @@ func TestReadServeConfig(t *testing.T) { } } -type fakeLocalClient struct { - *local.Client - setServeCalled bool -} +func TestRefreshAdvertiseServices(t *testing.T) { + tests := []struct { + name string + sc *ipn.ServeConfig + wantServices []string + wantEditPrefsCalled bool + wantErr bool + }{ + { + name: "nil_serve_config", + sc: nil, + wantEditPrefsCalled: false, + }, + { + name: "empty_serve_config", + sc: &ipn.ServeConfig{}, + wantEditPrefsCalled: false, + }, + { + name: "no_services_defined", + sc: &ipn.ServeConfig{ + TCP: map[uint16]*ipn.TCPPortHandler{ + 80: {HTTP: true}, + }, + }, + wantEditPrefsCalled: false, + }, + { + name: "single_service", + sc: &ipn.ServeConfig{ + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:my-service": {}, + }, + }, + wantServices: []string{"svc:my-service"}, + wantEditPrefsCalled: true, + }, + { + name: "multiple_services", + sc: &ipn.ServeConfig{ + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:service-a": {}, + "svc:service-b": {}, + "svc:service-c": {}, + }, + }, + wantServices: []string{"svc:service-a", "svc:service-b", "svc:service-c"}, + wantEditPrefsCalled: true, + }, + { + name: "services_with_tcp_and_web", + sc: &ipn.ServeConfig{ + TCP: map[uint16]*ipn.TCPPortHandler{ + 80: {HTTP: true}, + }, + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "example.com:443": {}, + }, + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:frontend": {}, + "svc:backend": {}, + }, + }, + wantServices: []string{"svc:frontend", "svc:backend"}, + wantEditPrefsCalled: true, + }, + } -func (m *fakeLocalClient) SetServeConfig(ctx context.Context, cfg *ipn.ServeConfig) error { - m.setServeCalled = true - return nil -} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeLC := &localclient.FakeLocalClient{} + err := refreshAdvertiseServices(context.Background(), tt.sc, fakeLC) -func (m *fakeLocalClient) CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err error) { - return nil, nil, nil + if (err != nil) != tt.wantErr { + t.Errorf("refreshAdvertiseServices() error = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantEditPrefsCalled != (len(fakeLC.EditPrefsCalls) > 0) { + t.Errorf("EditPrefs called = %v, want %v", len(fakeLC.EditPrefsCalls) > 0, tt.wantEditPrefsCalled) + } + + if tt.wantEditPrefsCalled { + if len(fakeLC.EditPrefsCalls) != 1 { + t.Fatalf("expected 1 EditPrefs call, got %d", len(fakeLC.EditPrefsCalls)) + } + + mp := fakeLC.EditPrefsCalls[0] + if !mp.AdvertiseServicesSet { + t.Error("AdvertiseServicesSet should be true") + } + + if len(mp.AdvertiseServices) != len(tt.wantServices) { + t.Errorf("AdvertiseServices length = %d, want %d", len(mp.Prefs.AdvertiseServices), len(tt.wantServices)) + } + + advertised := make(map[string]bool) + for _, svc := range mp.AdvertiseServices { + advertised[svc] = true + } + + for _, want := range tt.wantServices { + if !advertised[want] { + t.Errorf("expected service %q to be advertised, but it wasn't", want) + } + } + } + }) + } } func TestHasHTTPSEndpoint(t *testing.T) { diff --git a/cmd/containerboot/settings.go b/cmd/containerboot/settings.go index aab2b86314e23..e6147717bb39a 100644 --- a/cmd/containerboot/settings.go +++ b/cmd/containerboot/settings.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -107,7 +107,12 @@ func configFromEnv() (*settings, error) { UserspaceMode: defaultBool("TS_USERSPACE", true), StateDir: defaultEnv("TS_STATE_DIR", ""), AcceptDNS: defaultEnvBoolPointer("TS_ACCEPT_DNS"), - KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"), + KubeSecret: func() string { + if os.Getenv("KUBERNETES_SERVICE_HOST") != "" { + return defaultEnv("TS_KUBE_SECRET", "tailscale") + } + return defaultEnv("TS_KUBE_SECRET", "") + }(), SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""), HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""), Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"), @@ -126,6 +131,7 @@ func configFromEnv() (*settings, error) { IngressProxiesCfgPath: defaultEnv("TS_INGRESS_PROXIES_CONFIG_PATH", ""), PodUID: defaultEnv("POD_UID", ""), } + podIPs, ok := os.LookupEnv("POD_IPS") if ok { ips := strings.Split(podIPs, ",") @@ -144,6 +150,7 @@ func configFromEnv() (*settings, error) { cfg.PodIPv6 = parsed.String() } } + // If cert share is enabled, set the replica as read or write. Only 0th // replica should be able to write. isInCertShareMode := defaultBool("TS_EXPERIMENTAL_CERT_SHARE", false) @@ -165,9 +172,19 @@ func configFromEnv() (*settings, error) { cfg.AcceptDNS = &acceptDNSNew } + // In Kubernetes clusters, people like to use the "$(POD_IP):PORT" combination to configure the TS_LOCAL_ADDR_PORT + // environment variable (we even do this by default in the operator when enabling metrics), leading to a v6 address + // and port combo we cannot parse, as netip.ParseAddrPort expects the host segment to be enclosed in square brackets. + // We perform a check here to see if TS_LOCAL_ADDR_PORT is using the pod's IPv6 address and is not using brackets, + // adding the brackets in if need be. + if cfg.PodIPv6 != "" && strings.Contains(cfg.LocalAddrPort, cfg.PodIPv6) && !strings.ContainsAny(cfg.LocalAddrPort, "[]") { + cfg.LocalAddrPort = strings.Replace(cfg.LocalAddrPort, cfg.PodIPv6, "["+cfg.PodIPv6+"]", 1) + } + if err := cfg.validate(); err != nil { return nil, fmt.Errorf("invalid configuration: %v", err) } + return cfg, nil } diff --git a/cmd/containerboot/settings_test.go b/cmd/containerboot/settings_test.go index 576ea7f3eef3e..eca50101b6c70 100644 --- a/cmd/containerboot/settings_test.go +++ b/cmd/containerboot/settings_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -6,6 +6,7 @@ package main import ( + "net/netip" "strings" "testing" ) @@ -226,3 +227,30 @@ func TestValidateAuthMethods(t *testing.T) { }) } } + +func TestHandlesKubeIPV6(t *testing.T) { + t.Setenv("TS_LOCAL_ADDR_PORT", "fd7a:115c:a1e0::6c34:352:9002") + t.Setenv("POD_IPS", "fd7a:115c:a1e0::6c34:352") + + cfg, err := configFromEnv() + if err != nil { + t.Fatal(err) + } + + if cfg.LocalAddrPort != "[fd7a:115c:a1e0::6c34:352]:9002" { + t.Errorf("LocalAddrPort is not set correctly") + } + + parsed, err := netip.ParseAddrPort(cfg.LocalAddrPort) + if err != nil { + t.Fatal(err) + } + + if !parsed.Addr().Is6() { + t.Errorf("expected v6 address but got %s", parsed) + } + + if parsed.Port() != 9002 { + t.Errorf("expected port 9002 but got %d", parsed.Port()) + } +} diff --git a/cmd/containerboot/tailscaled.go b/cmd/containerboot/tailscaled.go index e5b0b8b8ed1b1..6f4ed77e76d72 100644 --- a/cmd/containerboot/tailscaled.go +++ b/cmd/containerboot/tailscaled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -69,7 +69,7 @@ func startTailscaled(ctx context.Context, cfg *settings) (*local.Client, *os.Pro func tailscaledArgs(cfg *settings) []string { args := []string{"--socket=" + cfg.Socket} switch { - case cfg.InKubernetes && cfg.KubeSecret != "": + case cfg.KubeSecret != "": args = append(args, "--state=kube:"+cfg.KubeSecret) if cfg.StateDir == "" { cfg.StateDir = "/tmp" diff --git a/cmd/derper/ace.go b/cmd/derper/ace.go index 56fb68c336cd3..ae2d0cbebb413 100644 --- a/cmd/derper/ace.go +++ b/cmd/derper/ace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO: docs about all this diff --git a/cmd/derper/bootstrap_dns.go b/cmd/derper/bootstrap_dns.go index a58f040bae687..9abc95df56878 100644 --- a/cmd/derper/bootstrap_dns.go +++ b/cmd/derper/bootstrap_dns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/bootstrap_dns_test.go b/cmd/derper/bootstrap_dns_test.go index 9b99103abfe33..5b765f6d37b5f 100644 --- a/cmd/derper/bootstrap_dns_test.go +++ b/cmd/derper/bootstrap_dns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/cert.go b/cmd/derper/cert.go index dfd7769905132..979c0d671517f 100644 --- a/cmd/derper/cert.go +++ b/cmd/derper/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/cert_test.go b/cmd/derper/cert_test.go index b4e18f6951ae0..e111ed76b7a97 100644 --- a/cmd/derper/cert_test.go +++ b/cmd/derper/cert_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt index 7695cf598b694..a0eb4a29e259c 100644 --- a/cmd/derper/depaware.txt +++ b/cmd/derper/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depaware) + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 github.com/axiomhq/hyperloglog from tailscale.com/derp/derpserver @@ -45,7 +46,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa github.com/tailscale/setec/types/api from github.com/tailscale/setec/client/setec github.com/x448/float16 from github.com/fxamacker/cbor/v2 đŸ’Ŗ go4.org/mem from tailscale.com/client/local+ - go4.org/netipx from tailscale.com/net/tsaddr + go4.org/netipx from tailscale.com/net/tsaddr+ W đŸ’Ŗ golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/netmon+ google.golang.org/protobuf/encoding/protodelim from github.com/prometheus/common/expfmt google.golang.org/protobuf/encoding/prototext from github.com/prometheus/common/expfmt+ @@ -203,7 +204,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/cmd/derper+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -226,7 +227,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -234,13 +235,14 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -255,7 +257,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -269,19 +271,21 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from golang.org/x/crypto/acme+ @@ -322,9 +326,8 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -337,14 +340,17 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from encoding/asn1 internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -387,7 +393,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa W os/user from tailscale.com/util/winutil path from github.com/prometheus/client_golang/prometheus/internal+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from encoding/asn1+ regexp from github.com/prometheus/client_golang/prometheus/internal+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go index ddf45747ac9fe..87f9a0bc084e4 100644 --- a/cmd/derper/derper.go +++ b/cmd/derper/derper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The derper binary is a simple DERP server. diff --git a/cmd/derper/derper_test.go b/cmd/derper/derper_test.go index d27f8cb20144d..0a2fd8787d61d 100644 --- a/cmd/derper/derper_test.go +++ b/cmd/derper/derper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/mesh.go b/cmd/derper/mesh.go index 909b5f2ca18c4..34ea7da856220 100644 --- a/cmd/derper/mesh.go +++ b/cmd/derper/mesh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/websocket.go b/cmd/derper/websocket.go index 82fd30bed165a..1929f16906659 100644 --- a/cmd/derper/websocket.go +++ b/cmd/derper/websocket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derpprobe/derpprobe.go b/cmd/derpprobe/derpprobe.go index 5d2179b512c23..549364e5e8f6a 100644 --- a/cmd/derpprobe/derpprobe.go +++ b/cmd/derpprobe/derpprobe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The derpprobe binary probes derpers. diff --git a/cmd/dist/dist.go b/cmd/dist/dist.go index c7406298d8188..88b9e6fba9133 100644 --- a/cmd/dist/dist.go +++ b/cmd/dist/dist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The dist command builds Tailscale release packages for distribution. diff --git a/cmd/distsign/distsign.go b/cmd/distsign/distsign.go index 051afabcd0b71..e0dba27206be9 100644 --- a/cmd/distsign/distsign.go +++ b/cmd/distsign/distsign.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command distsign tests downloads and signature validating for packages diff --git a/cmd/featuretags/featuretags.go b/cmd/featuretags/featuretags.go index 8c8a2ceaf54ff..f3aae68cc8b17 100644 --- a/cmd/featuretags/featuretags.go +++ b/cmd/featuretags/featuretags.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The featuretags command helps other build tools select Tailscale's Go build diff --git a/cmd/get-authkey/main.go b/cmd/get-authkey/main.go index ec7ab5d2c6158..da98decda6ae5 100644 --- a/cmd/get-authkey/main.go +++ b/cmd/get-authkey/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // get-authkey allocates an authkey using an OAuth API client diff --git a/cmd/gitops-pusher/cache.go b/cmd/gitops-pusher/cache.go index 6792e5e63e9cc..af5c4606c0d50 100644 --- a/cmd/gitops-pusher/cache.go +++ b/cmd/gitops-pusher/cache.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/gitops-pusher/gitops-pusher.go b/cmd/gitops-pusher/gitops-pusher.go index 0cbbda88a18b9..11448e30da1aa 100644 --- a/cmd/gitops-pusher/gitops-pusher.go +++ b/cmd/gitops-pusher/gitops-pusher.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command gitops-pusher allows users to use a GitOps flow for managing Tailscale ACLs. @@ -252,7 +252,7 @@ func getCredentials() (*http.Client, string) { TokenURL: fmt.Sprintf("https://%s/api/v2/oauth/token", *apiServer), } client = oauthConfig.Client(context.Background()) - } else if idok { + } else if idok && idToken != "" && oiok && oauthId != "" { if exchangeJWTForToken, ok := tailscale.HookExchangeJWTForTokenViaWIF.GetOk(); ok { var err error apiKeyEnv, err = exchangeJWTForToken(context.Background(), fmt.Sprintf("https://%s", *apiServer), oauthId, idToken) diff --git a/cmd/gitops-pusher/gitops-pusher_test.go b/cmd/gitops-pusher/gitops-pusher_test.go index e08b06c9cd194..bc339ae6a0b84 100644 --- a/cmd/gitops-pusher/gitops-pusher_test.go +++ b/cmd/gitops-pusher/gitops-pusher_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go index fa116b28b15ab..710de49cd67a8 100644 --- a/cmd/hello/hello.go +++ b/cmd/hello/hello.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The hello binary runs hello.ts.net. diff --git a/cmd/jsonimports/format.go b/cmd/jsonimports/format.go index 6dbd175583a4d..e990d0e6745c3 100644 --- a/cmd/jsonimports/format.go +++ b/cmd/jsonimports/format.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/jsonimports/format_test.go b/cmd/jsonimports/format_test.go index 28654eb4550ee..fb3d6fa09698d 100644 --- a/cmd/jsonimports/format_test.go +++ b/cmd/jsonimports/format_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/jsonimports/jsonimports.go b/cmd/jsonimports/jsonimports.go index 4be2e10cbe091..6903844e121ca 100644 --- a/cmd/jsonimports/jsonimports.go +++ b/cmd/jsonimports/jsonimports.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The jsonimports tool formats all Go source files in the repository diff --git a/cmd/k8s-nameserver/main.go b/cmd/k8s-nameserver/main.go index 84e65452d2334..1b219fb1ab924 100644 --- a/cmd/k8s-nameserver/main.go +++ b/cmd/k8s-nameserver/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-nameserver/main_test.go b/cmd/k8s-nameserver/main_test.go index bca010048664a..0624800836675 100644 --- a/cmd/k8s-nameserver/main_test.go +++ b/cmd/k8s-nameserver/main_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/api-server-proxy-pg.go b/cmd/k8s-operator/api-server-proxy-pg.go index 1a81e4967e5d8..0900fd0aaa264 100644 --- a/cmd/k8s-operator/api-server-proxy-pg.go +++ b/cmd/k8s-operator/api-server-proxy-pg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -23,6 +23,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "tailscale.com/internal/client/tailscale" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" @@ -52,7 +53,6 @@ type KubeAPIServerTSServiceReconciler struct { logger *zap.SugaredLogger tsClient tsClient tsNamespace string - lc localClient defaultTags []string operatorID string // stableID of the operator's Tailscale device @@ -78,9 +78,14 @@ func (r *KubeAPIServerTSServiceReconciler) Reconcile(ctx context.Context, req re serviceName := serviceNameForAPIServerProxy(pg) logger = logger.With("Tailscale Service", serviceName) + tailscaleClient, err := r.getClient(ctx, pg.Spec.Tailnet) + if err != nil { + return res, fmt.Errorf("failed to get tailscale client: %w", err) + } + if markedForDeletion(pg) { logger.Debugf("ProxyGroup is being deleted, ensuring any created resources are cleaned up") - if err = r.maybeCleanup(ctx, serviceName, pg, logger); err != nil && strings.Contains(err.Error(), optimisticLockErrorMsg) { + if err = r.maybeCleanup(ctx, serviceName, pg, logger, tailscaleClient); err != nil && strings.Contains(err.Error(), optimisticLockErrorMsg) { logger.Infof("optimistic lock error, retrying: %s", err) return res, nil } @@ -88,7 +93,7 @@ func (r *KubeAPIServerTSServiceReconciler) Reconcile(ctx context.Context, req re return res, err } - err = r.maybeProvision(ctx, serviceName, pg, logger) + err = r.maybeProvision(ctx, serviceName, pg, logger, tailscaleClient) if err != nil { if strings.Contains(err.Error(), optimisticLockErrorMsg) { logger.Infof("optimistic lock error, retrying: %s", err) @@ -100,11 +105,27 @@ func (r *KubeAPIServerTSServiceReconciler) Reconcile(ctx context.Context, req re return reconcile.Result{}, nil } +// getClient returns the appropriate Tailscale client for the given tailnet. +// If no tailnet is specified, returns the default client. +func (r *KubeAPIServerTSServiceReconciler) getClient(ctx context.Context, tailnetName string) (tsClient, + error) { + if tailnetName == "" { + return r.tsClient, nil + } + + tc, _, err := clientForTailnet(ctx, r.Client, r.tsNamespace, tailnetName) + if err != nil { + return nil, err + } + + return tc, nil +} + // maybeProvision ensures that a Tailscale Service for this ProxyGroup exists // and is up to date. // // Returns true if the operation resulted in a Tailscale Service update. -func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (err error) { +func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger, tsClient tsClient) (err error) { var dnsName string oldPGStatus := pg.Status.DeepCopy() defer func() { @@ -156,7 +177,7 @@ func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, s // 1. Check there isn't a Tailscale Service with the same hostname // already created and not owned by this ProxyGroup. - existingTSSvc, err := r.tsClient.GetVIPService(ctx, serviceName) + existingTSSvc, err := tsClient.GetVIPService(ctx, serviceName) if err != nil && !isErrorTailscaleServiceNotFound(err) { return fmt.Errorf("error getting Tailscale Service %q: %w", serviceName, err) } @@ -198,17 +219,17 @@ func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, s !ownersAreSetAndEqual(tsSvc, existingTSSvc) || !slices.Equal(tsSvc.Ports, existingTSSvc.Ports) { logger.Infof("Ensuring Tailscale Service exists and is up to date") - if err := r.tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { + if err = tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { return fmt.Errorf("error creating Tailscale Service: %w", err) } } // 3. Ensure that TLS Secret and RBAC exists. - tcd, err := tailnetCertDomain(ctx, r.lc) + dnsName, err = dnsNameForService(ctx, r.Client, serviceName, pg, r.tsNamespace) if err != nil { - return fmt.Errorf("error determining DNS name base: %w", err) + return fmt.Errorf("error determining service DNS name: %w", err) } - dnsName = serviceName.WithoutPrefix() + "." + tcd + if err = r.ensureCertResources(ctx, pg, dnsName); err != nil { return fmt.Errorf("error ensuring cert resources: %w", err) } @@ -219,7 +240,7 @@ func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, s } // 5. Clean up any stale Tailscale Services from previous resource versions. - if err = r.maybeDeleteStaleServices(ctx, pg, logger); err != nil { + if err = r.maybeDeleteStaleServices(ctx, pg, logger, tsClient); err != nil { return fmt.Errorf("failed to delete stale Tailscale Services: %w", err) } @@ -230,7 +251,7 @@ func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, s // Service is being deleted or is unexposed. The cleanup is safe for a multi-cluster setup- the Tailscale Service is only // deleted if it does not contain any other owner references. If it does, the cleanup only removes the owner reference // corresponding to this Service. -func (r *KubeAPIServerTSServiceReconciler) maybeCleanup(ctx context.Context, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (err error) { +func (r *KubeAPIServerTSServiceReconciler) maybeCleanup(ctx context.Context, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger, tsClient tsClient) (err error) { ix := slices.Index(pg.Finalizers, proxyPGFinalizerName) if ix < 0 { logger.Debugf("no finalizer, nothing to do") @@ -244,11 +265,11 @@ func (r *KubeAPIServerTSServiceReconciler) maybeCleanup(ctx context.Context, ser } }() - if _, err = cleanupTailscaleService(ctx, r.tsClient, serviceName, r.operatorID, logger); err != nil { + if _, err = cleanupTailscaleService(ctx, tsClient, serviceName, r.operatorID, logger); err != nil { return fmt.Errorf("error deleting Tailscale Service: %w", err) } - if err = cleanupCertResources(ctx, r.Client, r.lc, r.tsNamespace, pg.Name, serviceName); err != nil { + if err = cleanupCertResources(ctx, r.Client, r.tsNamespace, serviceName, pg); err != nil { return fmt.Errorf("failed to clean up cert resources: %w", err) } @@ -257,10 +278,10 @@ func (r *KubeAPIServerTSServiceReconciler) maybeCleanup(ctx context.Context, ser // maybeDeleteStaleServices deletes Services that have previously been created for // this ProxyGroup but are no longer needed. -func (r *KubeAPIServerTSServiceReconciler) maybeDeleteStaleServices(ctx context.Context, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) error { +func (r *KubeAPIServerTSServiceReconciler) maybeDeleteStaleServices(ctx context.Context, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger, tsClient tsClient) error { serviceName := serviceNameForAPIServerProxy(pg) - svcs, err := r.tsClient.ListVIPServices(ctx) + svcs, err := tsClient.ListVIPServices(ctx) if err != nil { return fmt.Errorf("error listing Tailscale Services: %w", err) } @@ -285,11 +306,11 @@ func (r *KubeAPIServerTSServiceReconciler) maybeDeleteStaleServices(ctx context. } logger.Infof("Deleting Tailscale Service %s", svc.Name) - if err := r.tsClient.DeleteVIPService(ctx, svc.Name); err != nil && !isErrorTailscaleServiceNotFound(err) { + if err = tsClient.DeleteVIPService(ctx, svc.Name); err != nil && !isErrorTailscaleServiceNotFound(err) { return fmt.Errorf("error deleting Tailscale Service %s: %w", svc.Name, err) } - if err = cleanupCertResources(ctx, r.Client, r.lc, r.tsNamespace, pg.Name, svc.Name); err != nil { + if err = cleanupCertResources(ctx, r.Client, r.tsNamespace, svc.Name, pg); err != nil { return fmt.Errorf("failed to clean up cert resources: %w", err) } } @@ -343,7 +364,7 @@ func (r *KubeAPIServerTSServiceReconciler) maybeAdvertiseServices(ctx context.Co // Only advertise a Tailscale Service once the TLS certs required for // serving it are available. - shouldBeAdvertised, err := hasCerts(ctx, r.Client, r.lc, r.tsNamespace, serviceName) + shouldBeAdvertised, err := hasCerts(ctx, r.Client, r.tsNamespace, serviceName, pg) if err != nil { return fmt.Errorf("error checking TLS credentials provisioned for Tailscale Service %q: %w", serviceName, err) } diff --git a/cmd/k8s-operator/api-server-proxy-pg_test.go b/cmd/k8s-operator/api-server-proxy-pg_test.go index dee5057236675..f7277c70d5717 100644 --- a/cmd/k8s-operator/api-server-proxy-pg_test.go +++ b/cmd/k8s-operator/api-server-proxy-pg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main @@ -16,8 +16,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "tailscale.com/internal/client/tailscale" - "tailscale.com/ipn/ipnstate" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/k8s-proxy/conf" @@ -108,14 +108,6 @@ func TestAPIServerProxyReconciler(t *testing.T) { } ft.CreateOrUpdateVIPService(t.Context(), ingressTSSvc) - lc := &fakeLocalClient{ - status: &ipnstate.Status{ - CurrentTailnet: &ipnstate.TailnetStatus{ - MagicDNSSuffix: "ts.net", - }, - }, - } - r := &KubeAPIServerTSServiceReconciler{ Client: fc, tsClient: ft, @@ -123,7 +115,6 @@ func TestAPIServerProxyReconciler(t *testing.T) { tsNamespace: ns, logger: zap.Must(zap.NewDevelopment()).Sugar(), recorder: record.NewFakeRecorder(10), - lc: lc, clock: tstest.NewClock(tstest.ClockOpts{}), operatorID: "self-id", } @@ -148,6 +139,20 @@ func TestAPIServerProxyReconciler(t *testing.T) { if err := ft.DeleteVIPService(t.Context(), "svc:"+pgName); err != nil { t.Fatalf("deleting initial Tailscale Service: %v", err) } + + // Create the state secret for the ProxyGroup without services being advertised. + mustCreate(t, fc, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pg-0", + Namespace: ns, + Labels: pgSecretLabels(pgName, kubetypes.LabelSecretTypeState), + }, + Data: map[string][]byte{ + "_current-profile": []byte("test"), + "test": []byte(`{"Config":{"NodeID":"node-foo", "UserProfile": {"LoginName": "test-pg.ts.net" }}}`), + }, + }) + expectReconciled(t, r, "", pgName) tsSvc, err := ft.GetVIPService(t.Context(), "svc:"+pgName) @@ -191,17 +196,19 @@ func TestAPIServerProxyReconciler(t *testing.T) { expectEqual(t, fc, pg, omitPGStatusConditionMessages) // Unchanged status. // Simulate Pod prefs updated with advertised services; should see Configured condition updated to true. - mustCreate(t, fc, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pg-0", - Namespace: ns, - Labels: pgSecretLabels(pgName, kubetypes.LabelSecretTypeState), - }, - Data: map[string][]byte{ - "_current-profile": []byte("profile-foo"), - "profile-foo": []byte(`{"AdvertiseServices":["svc:test-pg"],"Config":{"NodeID":"node-foo"}}`), - }, + mustUpdate(t, fc, ns, "test-pg-0", func(o *corev1.Secret) { + var p prefs + if err = json.Unmarshal(o.Data["test"], &p); err != nil { + t.Errorf("failed to unmarshal preferences: %v", err) + } + + p.AdvertiseServices = []string{"svc:test-pg"} + o.Data["test"], err = json.Marshal(p) + if err != nil { + t.Errorf("failed to marshal preferences: %v", err) + } }) + expectReconciled(t, r, "", pgName) tsoperator.SetProxyGroupCondition(pg, tsapi.KubeAPIServerProxyConfigured, metav1.ConditionTrue, reasonKubeAPIServerProxyConfigured, "", 1, r.clock, r.logger) pg.Status.URL = "https://" + defaultDomain diff --git a/cmd/k8s-operator/api-server-proxy.go b/cmd/k8s-operator/api-server-proxy.go index 70333d2c48d41..492590c9fecd6 100644 --- a/cmd/k8s-operator/api-server-proxy.go +++ b/cmd/k8s-operator/api-server-proxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/connector.go b/cmd/k8s-operator/connector.go index 7fa311532238b..0c2d32482e78b 100644 --- a/cmd/k8s-operator/connector.go +++ b/cmd/k8s-operator/connector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" @@ -207,6 +208,7 @@ func (a *ConnectorReconciler) maybeProvisionConnector(ctx context.Context, logge ProxyClassName: proxyClass, proxyType: proxyTypeConnector, LoginServer: a.ssr.loginServer, + Tailnet: cn.Spec.Tailnet, } if cn.Spec.SubnetRouter != nil && len(cn.Spec.SubnetRouter.AdvertiseRoutes) > 0 { @@ -276,7 +278,7 @@ func (a *ConnectorReconciler) maybeProvisionConnector(ctx context.Context, logge } func (a *ConnectorReconciler) maybeCleanupConnector(ctx context.Context, logger *zap.SugaredLogger, cn *tsapi.Connector) (bool, error) { - if done, err := a.ssr.Cleanup(ctx, logger, childResourceLabels(cn.Name, a.tsnamespace, "connector"), proxyTypeConnector); err != nil { + if done, err := a.ssr.Cleanup(ctx, cn.Spec.Tailnet, logger, childResourceLabels(cn.Name, a.tsnamespace, "connector"), proxyTypeConnector); err != nil { return false, fmt.Errorf("failed to cleanup Connector resources: %w", err) } else if !done { logger.Debugf("Connector cleanup not done yet, waiting for next reconcile") diff --git a/cmd/k8s-operator/connector_test.go b/cmd/k8s-operator/connector_test.go index afc7d2d6e3975..7866f3e002921 100644 --- a/cmd/k8s-operator/connector_test.go +++ b/cmd/k8s-operator/connector_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index d6993465304fd..8718127b6e75f 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/depaware) + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 W đŸ’Ŗ github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ @@ -99,8 +100,13 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ github.com/fsnotify/fsnotify/internal from github.com/fsnotify/fsnotify github.com/fxamacker/cbor/v2 from tailscale.com/tka+ github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+ @@ -118,7 +124,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ đŸ’Ŗ github.com/gogo/protobuf/proto from k8s.io/api/admission/v1+ github.com/gogo/protobuf/sortkeys from k8s.io/api/admission/v1+ 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/transport/tcp github.com/google/gnostic-models/compiler from github.com/google/gnostic-models/openapiv2+ github.com/google/gnostic-models/extensions from github.com/google/gnostic-models/compiler github.com/google/gnostic-models/jsonschema from github.com/google/gnostic-models/compiler @@ -155,7 +161,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ đŸ’Ŗ github.com/modern-go/reflect2 from github.com/json-iterator/go github.com/munnerz/goautoneg from k8s.io/kube-openapi/pkg/handler3+ github.com/opencontainers/go-digest from github.com/distribution/reference - github.com/pires/go-proxyproto from tailscale.com/ipn/ipnlocal + github.com/pires/go-proxyproto from tailscale.com/ipn/ipnlocal+ github.com/pkg/errors from github.com/evanphx/json-patch/v5+ github.com/pmezard/go-difflib/difflib from k8s.io/apimachinery/pkg/util/diff D github.com/prometheus-community/pro-bing from tailscale.com/wgengine/netstack @@ -266,7 +272,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ đŸ’Ŗ gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+ gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state đŸ’Ŗ gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+ - đŸ’Ŗ gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack + đŸ’Ŗ gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack+ gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack đŸ’Ŗ gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+ @@ -725,7 +731,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ k8s.io/utils/net from k8s.io/apimachinery/pkg/util/net+ k8s.io/utils/ptr from k8s.io/client-go/tools/cache+ k8s.io/utils/trace from k8s.io/client-go/tools/cache - sigs.k8s.io/controller-runtime/pkg/builder from tailscale.com/cmd/k8s-operator + sigs.k8s.io/controller-runtime/pkg/builder from tailscale.com/cmd/k8s-operator+ sigs.k8s.io/controller-runtime/pkg/cache from sigs.k8s.io/controller-runtime/pkg/cluster+ sigs.k8s.io/controller-runtime/pkg/cache/internal from sigs.k8s.io/controller-runtime/pkg/cache sigs.k8s.io/controller-runtime/pkg/certwatcher from sigs.k8s.io/controller-runtime/pkg/metrics/server+ @@ -808,7 +814,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ tailscale.com/feature/syspolicy from tailscale.com/logpolicy tailscale.com/feature/useproxy from tailscale.com/feature/condregister/useproxy tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/internal/client/tailscale from tailscale.com/cmd/k8s-operator+ tailscale.com/ipn from tailscale.com/client/local+ @@ -816,15 +822,19 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ đŸ’Ŗ tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnstate from tailscale.com/client/local+ tailscale.com/ipn/localapi from tailscale.com/tsnet tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn/store/kubestore from tailscale.com/cmd/k8s-operator tailscale.com/ipn/store/mem from tailscale.com/ipn/ipnlocal+ - tailscale.com/k8s-operator from tailscale.com/cmd/k8s-operator + tailscale.com/k8s-operator from tailscale.com/cmd/k8s-operator+ tailscale.com/k8s-operator/api-proxy from tailscale.com/cmd/k8s-operator tailscale.com/k8s-operator/apis from tailscale.com/k8s-operator/apis/v1alpha1 tailscale.com/k8s-operator/apis/v1alpha1 from tailscale.com/cmd/k8s-operator+ + tailscale.com/k8s-operator/reconciler from tailscale.com/k8s-operator/reconciler/tailnet + tailscale.com/k8s-operator/reconciler/proxygrouppolicy from tailscale.com/cmd/k8s-operator + tailscale.com/k8s-operator/reconciler/tailnet from tailscale.com/cmd/k8s-operator tailscale.com/k8s-operator/sessionrecording from tailscale.com/k8s-operator/api-proxy tailscale.com/k8s-operator/sessionrecording/spdy from tailscale.com/k8s-operator/sessionrecording tailscale.com/k8s-operator/sessionrecording/tsrecorder from tailscale.com/k8s-operator/sessionrecording+ @@ -1041,7 +1051,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -1058,7 +1068,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ vendor/golang.org/x/text/unicode/norm from vendor/golang.org/x/net/idna bufio from compress/flate+ bytes from bufio+ - cmp from github.com/gaissmai/bart+ + cmp from encoding/json+ compress/flate from compress/gzip+ compress/gzip from github.com/emicklei/go-restful/v3+ compress/zlib from github.com/emicklei/go-restful/v3+ @@ -1066,7 +1076,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509+ @@ -1075,12 +1085,13 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ crypto/fips140 from crypto/tls/internal/fips140tls+ - crypto/hkdf from crypto/internal/hpke+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -1095,7 +1106,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls+ + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -1109,20 +1120,21 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ - LD crypto/mlkem from golang.org/x/crypto/ssh + crypto/mlkem from golang.org/x/crypto/ssh+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls+ crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from github.com/prometheus-community/pro-bing+ @@ -1153,6 +1165,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ go/build/constraint from go/parser go/doc from k8s.io/apimachinery/pkg/runtime go/doc/comment from go/doc + go/internal/scannerhooks from go/parser+ go/parser from k8s.io/apimachinery/pkg/runtime go/scanner from go/ast+ go/token from go/ast+ @@ -1176,9 +1189,8 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/lazyregexp from go/doc internal/msan from internal/runtime/maps+ internal/nettrace from net+ @@ -1192,14 +1204,17 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -1246,7 +1261,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ os/user from github.com/godbus/dbus/v5+ path from debug/dwarf+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from database/sql+ regexp from github.com/davecgh/go-spew/spew+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/cmd/k8s-operator/deploy/chart/Chart.yaml b/cmd/k8s-operator/deploy/chart/Chart.yaml index 9db6389d1d944..b16fc4c37fb8a 100644 --- a/cmd/k8s-operator/deploy/chart/Chart.yaml +++ b/cmd/k8s-operator/deploy/chart/Chart.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v2 diff --git a/cmd/k8s-operator/deploy/chart/templates/.gitignore b/cmd/k8s-operator/deploy/chart/templates/.gitignore index ae7c682d9fd15..185ea9e2be316 100644 --- a/cmd/k8s-operator/deploy/chart/templates/.gitignore +++ b/cmd/k8s-operator/deploy/chart/templates/.gitignore @@ -8,3 +8,5 @@ /proxyclass.yaml /proxygroup.yaml /recorder.yaml +/tailnet.yaml +/proxygrouppolicy.yaml diff --git a/cmd/k8s-operator/deploy/chart/templates/NOTES.txt b/cmd/k8s-operator/deploy/chart/templates/NOTES.txt index 1bee6704616e6..a1a351c5e526a 100644 --- a/cmd/k8s-operator/deploy/chart/templates/NOTES.txt +++ b/cmd/k8s-operator/deploy/chart/templates/NOTES.txt @@ -1,3 +1,13 @@ +{{/* +Fail on presence of removed TS_EXPERIMENTAL_KUBE_API_EVENTS extraEnv var. +*/}} +{{- $removed := "TS_EXPERIMENTAL_KUBE_API_EVENTS" -}} +{{- range .Values.operatorConfig.extraEnv }} + {{- if and .name (eq .name $removed) (eq .value "true") -}} + {{- fail (printf "ERROR: operatorConfig.extraEnv.%s has been removed. Use ACLs instead." $removed) -}} + {{- end -}} +{{- end -}} + You have successfully installed the Tailscale Kubernetes Operator! Once connected, the operator should appear as a device within the Tailscale admin console: diff --git a/cmd/k8s-operator/deploy/chart/templates/apiserverproxy-rbac.yaml b/cmd/k8s-operator/deploy/chart/templates/apiserverproxy-rbac.yaml index d6e9d1bf48ef8..2ca4f398ad2da 100644 --- a/cmd/k8s-operator/deploy/chart/templates/apiserverproxy-rbac.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/apiserverproxy-rbac.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # If old setting used, enable both old (operator) and new (ProxyGroup) workflows. diff --git a/cmd/k8s-operator/deploy/chart/templates/deployment.yaml b/cmd/k8s-operator/deploy/chart/templates/deployment.yaml index df9cb8ce1bcb0..0c0cb64cbb4ed 100644 --- a/cmd/k8s-operator/deploy/chart/templates/deployment.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/deployment.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: apps/v1 diff --git a/cmd/k8s-operator/deploy/chart/templates/oauth-secret.yaml b/cmd/k8s-operator/deploy/chart/templates/oauth-secret.yaml index 759ba341a8f21..34928d6dcd6c8 100644 --- a/cmd/k8s-operator/deploy/chart/templates/oauth-secret.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/oauth-secret.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause {{ if and .Values.oauth .Values.oauth.clientId (not .Values.oauth.audience) -}} diff --git a/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml b/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml index 5eb920a6f41c4..4d59b4aad077d 100644 --- a/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 @@ -37,6 +37,12 @@ rules: - apiGroups: ["tailscale.com"] resources: ["dnsconfigs", "dnsconfigs/status"] verbs: ["get", "list", "watch", "update"] +- apiGroups: ["tailscale.com"] + resources: ["tailnets", "tailnets/status"] + verbs: ["get", "list", "watch", "update"] +- apiGroups: ["tailscale.com"] + resources: ["proxygrouppolicies", "proxygrouppolicies/status"] + verbs: ["get", "list", "watch", "update"] - apiGroups: ["tailscale.com"] resources: ["recorders", "recorders/status"] verbs: ["get", "list", "watch", "update"] @@ -44,6 +50,9 @@ rules: resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] resourceNames: ["servicemonitors.monitoring.coreos.com"] +- apiGroups: ["admissionregistration.k8s.io"] + resources: ["validatingadmissionpolicies", "validatingadmissionpolicybindings"] + verbs: ["list", "create", "delete", "update", "get", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/cmd/k8s-operator/deploy/chart/templates/proxy-rbac.yaml b/cmd/k8s-operator/deploy/chart/templates/proxy-rbac.yaml index fa552a7c7e39a..89d6736b790a1 100644 --- a/cmd/k8s-operator/deploy/chart/templates/proxy-rbac.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/proxy-rbac.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 diff --git a/cmd/k8s-operator/deploy/chart/values.yaml b/cmd/k8s-operator/deploy/chart/values.yaml index cdcb2c1c50b7a..3606f1af3f3d2 100644 --- a/cmd/k8s-operator/deploy/chart/values.yaml +++ b/cmd/k8s-operator/deploy/chart/values.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # Operator oauth credentials. If unset a Secret named operator-oauth must be diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml index 74d32d53d2199..03c51c7553bf9 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml @@ -181,6 +181,14 @@ spec: items: type: string pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ + tailnet: + description: |- + Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - rule: self == oldSelf + message: Connector tailnet is immutable x-kubernetes-validations: - rule: has(self.subnetRouter) || (has(self.exitNode) && self.exitNode == true) || has(self.appConnector) message: A Connector needs to have at least one of exit node, subnet router or app connector configured. diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml new file mode 100644 index 0000000000000..d1425fba80165 --- /dev/null +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml @@ -0,0 +1,135 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: proxygrouppolicies.tailscale.com +spec: + group: tailscale.com + names: + kind: ProxyGroupPolicy + listKind: ProxyGroupPolicyList + plural: proxygrouppolicies + shortNames: + - pgp + singular: proxygrouppolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec describes the desired state of the ProxyGroupPolicy. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + egress: + description: |- + Names of ProxyGroup resources that can be used by Service resources within this namespace. An empty list + denotes that no egress via ProxyGroups is allowed within this namespace. + type: array + items: + type: string + ingress: + description: |- + Names of ProxyGroup resources that can be used by Ingress resources within this namespace. An empty list + denotes that no ingress via ProxyGroups is allowed within this namespace. + type: array + items: + type: string + status: + description: |- + Status describes the status of the ProxyGroupPolicy. This is set + and managed by the Tailscale operator. + type: object + properties: + conditions: + type: array + items: + description: Condition contains details for one aspect of the current state of this API Resource. + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + type: string + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + served: true + storage: true + subresources: + status: {} diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml index 98ca1c378ab8d..0254f01b8f0bf 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml @@ -139,6 +139,14 @@ spec: items: type: string pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ + tailnet: + description: |- + Tailnet specifies the tailnet this ProxyGroup should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - rule: self == oldSelf + message: ProxyGroup tailnet is immutable type: description: |- Type of the ProxyGroup proxies. Supported types are egress, ingress, and kube-apiserver. diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml index 3d80c55e10a73..ca43a72a557f2 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml @@ -72,6 +72,7 @@ spec: description: Replicas specifies how many instances of tsrecorder to run. Defaults to 1. type: integer format: int32 + default: 1 minimum: 0 statefulSet: description: |- @@ -1680,6 +1681,14 @@ spec: items: type: string pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ + tailnet: + description: |- + Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - rule: self == oldSelf + message: Recorder tailnet is immutable x-kubernetes-validations: - rule: '!(self.replicas > 1 && (!has(self.storage) || !has(self.storage.s3)))' message: S3 storage must be used when deploying multiple Recorder replicas diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_tailnets.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_tailnets.yaml new file mode 100644 index 0000000000000..200d839431573 --- /dev/null +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_tailnets.yaml @@ -0,0 +1,141 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: tailnets.tailscale.com +spec: + group: tailscale.com + names: + kind: Tailnet + listKind: TailnetList + plural: tailnets + shortNames: + - tn + singular: tailnet + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Status of the deployed Tailnet resources. + jsonPath: .status.conditions[?(@.type == "TailnetReady")].reason + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec describes the desired state of the Tailnet. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - credentials + properties: + credentials: + description: Denotes the location of the OAuth credentials to use for authenticating with this Tailnet. + type: object + required: + - secretName + properties: + secretName: + description: |- + The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and + "client_secret". + type: string + loginUrl: + description: URL of the control plane to be used by all resources managed by the operator using this Tailnet. + type: string + status: + description: |- + Status describes the status of the Tailnet. This is set + and managed by the Tailscale operator. + type: object + properties: + conditions: + type: array + items: + description: Condition contains details for one aspect of the current state of this API Resource. + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + type: string + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + served: true + storage: true + subresources: + status: {} diff --git a/cmd/k8s-operator/deploy/examples/connector.yaml b/cmd/k8s-operator/deploy/examples/connector.yaml index f5447400e8722..a025eef98cd26 100644 --- a/cmd/k8s-operator/deploy/examples/connector.yaml +++ b/cmd/k8s-operator/deploy/examples/connector.yaml @@ -1,9 +1,10 @@ # Before applying ensure that the operator owns tag:prod. # https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator. -# To set up autoapproval set tag:prod as approver for 10.40.0.0/14 route and exit node. +# To set up autoapproval set tag:prod as approver for 10.40.0.0/14 route. # Otherwise approve it manually in Machines panel once the # ts-prod Tailscale node has been created. # See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes +# For an exit node example, see exitnode.yaml apiVersion: tailscale.com/v1alpha1 kind: Connector metadata: @@ -17,4 +18,3 @@ spec: advertiseRoutes: - "10.40.0.0/14" - "192.168.0.0/14" - exitNode: true diff --git a/cmd/k8s-operator/deploy/examples/exitnode.yaml b/cmd/k8s-operator/deploy/examples/exitnode.yaml new file mode 100644 index 0000000000000..b2ce516cd98bf --- /dev/null +++ b/cmd/k8s-operator/deploy/examples/exitnode.yaml @@ -0,0 +1,26 @@ +# Before applying ensure that the operator owns tag:k8s-operator +# To use both subnet routing and exit node on the same cluster, deploy a separate +# Connector resource for each. +# See connector.yaml for a subnet router example. +# See: https://tailscale.com/kb/1441/kubernetes-operator-connector +--- +apiVersion: tailscale.com/v1alpha1 +kind: Connector +metadata: + name: exit-node +spec: + # Exit node configuration - allows Tailscale clients to route all internet traffic through this Connector + exitNode: true + + # High availability: 2 replicas for redundancy + # Note: Must use hostnamePrefix (not hostname) when replicas > 1 + replicas: 2 + + # Hostname prefix for the exit node devices + # Devices will be named: exit-node-0, exit-node-1 + hostnamePrefix: exit-node + + # Tailscale tags for ACL policy management + tags: + - tag:k8s-operator + diff --git a/cmd/k8s-operator/deploy/manifests/authproxy-rbac.yaml b/cmd/k8s-operator/deploy/manifests/authproxy-rbac.yaml index 5818fa69fff7d..2dc9cad228ffa 100644 --- a/cmd/k8s-operator/deploy/manifests/authproxy-rbac.yaml +++ b/cmd/k8s-operator/deploy/manifests/authproxy-rbac.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml index c53f5049261e8..597641bdefecf 100644 --- a/cmd/k8s-operator/deploy/manifests/operator.yaml +++ b/cmd/k8s-operator/deploy/manifests/operator.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 @@ -206,6 +206,14 @@ spec: pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ type: string type: array + tailnet: + description: |- + Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - message: Connector tailnet is immutable + rule: self == oldSelf type: object x-kubernetes-validations: - message: A Connector needs to have at least one of exit node, subnet router or app connector configured. @@ -3135,6 +3143,14 @@ spec: pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ type: string type: array + tailnet: + description: |- + Tailnet specifies the tailnet this ProxyGroup should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - message: ProxyGroup tailnet is immutable + rule: self == oldSelf type: description: |- Type of the ProxyGroup proxies. Supported types are egress, ingress, and kube-apiserver. @@ -3274,6 +3290,142 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: proxygrouppolicies.tailscale.com +spec: + group: tailscale.com + names: + kind: ProxyGroupPolicy + listKind: ProxyGroupPolicyList + plural: proxygrouppolicies + shortNames: + - pgp + singular: proxygrouppolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec describes the desired state of the ProxyGroupPolicy. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + egress: + description: |- + Names of ProxyGroup resources that can be used by Service resources within this namespace. An empty list + denotes that no egress via ProxyGroups is allowed within this namespace. + items: + type: string + type: array + ingress: + description: |- + Names of ProxyGroup resources that can be used by Ingress resources within this namespace. An empty list + denotes that no ingress via ProxyGroups is allowed within this namespace. + items: + type: string + type: array + type: object + status: + description: |- + Status describes the status of the ProxyGroupPolicy. This is set + and managed by the Tailscale operator. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.0 @@ -3339,6 +3491,7 @@ spec: Required if S3 storage is not set up, to ensure that recordings are accessible. type: boolean replicas: + default: 1 description: Replicas specifies how many instances of tsrecorder to run. Defaults to 1. format: int32 minimum: 0 @@ -4950,6 +5103,14 @@ spec: pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ type: string type: array + tailnet: + description: |- + Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - message: Recorder tailnet is immutable + rule: self == oldSelf type: object x-kubernetes-validations: - message: S3 storage must be used when deploying multiple Recorder replicas @@ -5059,6 +5220,148 @@ spec: subresources: status: {} --- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: tailnets.tailscale.com +spec: + group: tailscale.com + names: + kind: Tailnet + listKind: TailnetList + plural: tailnets + shortNames: + - tn + singular: tailnet + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Status of the deployed Tailnet resources. + jsonPath: .status.conditions[?(@.type == "TailnetReady")].reason + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec describes the desired state of the Tailnet. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + credentials: + description: Denotes the location of the OAuth credentials to use for authenticating with this Tailnet. + properties: + secretName: + description: |- + The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and + "client_secret". + type: string + required: + - secretName + type: object + loginUrl: + description: URL of the control plane to be used by all resources managed by the operator using this Tailnet. + type: string + required: + - credentials + type: object + status: + description: |- + Status describes the status of the Tailnet. This is set + and managed by the Tailscale operator. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -5141,6 +5444,26 @@ rules: - list - watch - update + - apiGroups: + - tailscale.com + resources: + - tailnets + - tailnets/status + verbs: + - get + - list + - watch + - update + - apiGroups: + - tailscale.com + resources: + - proxygrouppolicies + - proxygrouppolicies/status + verbs: + - get + - list + - watch + - update - apiGroups: - tailscale.com resources: @@ -5161,6 +5484,18 @@ rules: - get - list - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingadmissionpolicies + - validatingadmissionpolicybindings + verbs: + - list + - create + - delete + - update + - get + - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/cmd/k8s-operator/deploy/manifests/templates/01-header.yaml b/cmd/k8s-operator/deploy/manifests/templates/01-header.yaml index a96d4c37ee421..800025e90003b 100644 --- a/cmd/k8s-operator/deploy/manifests/templates/01-header.yaml +++ b/cmd/k8s-operator/deploy/manifests/templates/01-header.yaml @@ -1,3 +1,3 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/cmd/k8s-operator/dnsrecords.go b/cmd/k8s-operator/dnsrecords.go index 1a9395aa00aa9..e75bcd4c2e1da 100644 --- a/cmd/k8s-operator/dnsrecords.go +++ b/cmd/k8s-operator/dnsrecords.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/dnsrecords_test.go b/cmd/k8s-operator/dnsrecords_test.go index 13898078fd4ba..0d89c4a863e4d 100644 --- a/cmd/k8s-operator/dnsrecords_test.go +++ b/cmd/k8s-operator/dnsrecords_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/e2e/doc.go b/cmd/k8s-operator/e2e/doc.go index 40fa1f36a1d82..27d10e637c8c2 100644 --- a/cmd/k8s-operator/e2e/doc.go +++ b/cmd/k8s-operator/e2e/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package e2e runs end-to-end tests for the Tailscale Kubernetes operator. @@ -24,5 +24,5 @@ // // * go // * container runtime with the docker daemon API available -// * devcontrol: ./tool/go run ./cmd/devcontrol --generate-test-devices=k8s-operator-e2e --scenario-output-dir=/tmp/k8s-operator-e2e --test-dns=http://localhost:8055 +// * devcontrol: ./tool/go run --tags=tailscale_saas ./cmd/devcontrol --generate-test-devices=k8s-operator-e2e --scenario-output-dir=/tmp/k8s-operator-e2e --test-dns=http://localhost:8055 package e2e diff --git a/cmd/k8s-operator/e2e/ingress_test.go b/cmd/k8s-operator/e2e/ingress_test.go index c5b238e852b89..47a838414d449 100644 --- a/cmd/k8s-operator/e2e/ingress_test.go +++ b/cmd/k8s-operator/e2e/ingress_test.go @@ -1,10 +1,11 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e import ( "context" + "encoding/json" "fmt" "net/http" "testing" @@ -14,6 +15,11 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + "tailscale.com/cmd/testwrapper/flakytest" kube "tailscale.com/k8s-operator" "tailscale.com/tstest" "tailscale.com/types/ptr" @@ -22,6 +28,7 @@ import ( // See [TestMain] for test requirements. func TestIngress(t *testing.T) { + flakytest.Mark(t, "https://github.com/tailscale/corp/issues/37533") if tnClient == nil { t.Skip("TestIngress requires a working tailnet client") } @@ -84,8 +91,68 @@ func TestIngress(t *testing.T) { } createAndCleanup(t, kubeClient, svc) + // TODO(tomhjp): Delete once we've reproduced the flake with this extra info. + t0 := time.Now() + watcherCtx, cancelWatcher := context.WithCancel(t.Context()) + defer cancelWatcher() + go func() { + // client-go client for logs. + clientGoKubeClient, err := kubernetes.NewForConfig(restCfg) + if err != nil { + t.Logf("error creating client-go Kubernetes client: %v", err) + return + } + + for { + select { + case <-watcherCtx.Done(): + t.Logf("stopping watcher after %v", time.Since(t0)) + return + case <-time.After(time.Minute): + t.Logf("dumping info after %v elapsed", time.Since(t0)) + // Service itself. + svc := &corev1.Service{ObjectMeta: objectMeta("default", "test-ingress")} + err := get(watcherCtx, kubeClient, svc) + svcYaml, _ := yaml.Marshal(svc) + t.Logf("Service: %s, error: %v\n%s", svc.Name, err, string(svcYaml)) + + // Pods in tailscale namespace. + var pods corev1.PodList + if err := kubeClient.List(watcherCtx, &pods, client.InNamespace("tailscale")); err != nil { + t.Logf("error listing Pods in tailscale namespace: %v", err) + } else { + t.Logf("%d Pods", len(pods.Items)) + for _, pod := range pods.Items { + podYaml, _ := yaml.Marshal(pod) + t.Logf("Pod: %s\n%s", pod.Name, string(podYaml)) + logs := clientGoKubeClient.CoreV1().Pods("tailscale").GetLogs(pod.Name, &corev1.PodLogOptions{}).Do(watcherCtx) + logData, err := logs.Raw() + if err != nil { + t.Logf("error reading logs for Pod %s: %v", pod.Name, err) + continue + } + t.Logf("Logs for Pod %s:\n%s", pod.Name, string(logData)) + } + } + + // Tailscale status on the tailnet. + lc, err := tnClient.LocalClient() + if err != nil { + t.Logf("error getting tailnet local client: %v", err) + } else { + status, err := lc.Status(watcherCtx) + statusJSON, _ := json.MarshalIndent(status, "", " ") + t.Logf("Tailnet status: %s, error: %v", string(statusJSON), err) + } + } + } + }() + // TODO: instead of timing out only when test times out, cancel context after 60s or so. if err := wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { + if time.Since(t0) > time.Minute { + t.Logf("%v elapsed waiting for Service default/test-ingress to become Ready", time.Since(t0)) + } maybeReadySvc := &corev1.Service{ObjectMeta: objectMeta("default", "test-ingress")} if err := get(ctx, kubeClient, maybeReadySvc); err != nil { return false, err @@ -98,6 +165,7 @@ func TestIngress(t *testing.T) { }); err != nil { t.Fatalf("error waiting for the Service to become Ready: %v", err) } + cancelWatcher() var resp *http.Response if err := tstest.WaitFor(time.Minute, func() error { diff --git a/cmd/k8s-operator/e2e/main_test.go b/cmd/k8s-operator/e2e/main_test.go index 68f10dbb064cf..02f614014dbee 100644 --- a/cmd/k8s-operator/e2e/main_test.go +++ b/cmd/k8s-operator/e2e/main_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e @@ -54,12 +54,29 @@ func createAndCleanup(t *testing.T, cl client.Client, obj client.Object) { t.Cleanup(func() { // Use context.Background() for cleanup, as t.Context() is cancelled // just before cleanup functions are called. - if err := cl.Delete(context.Background(), obj); err != nil { + if err = cl.Delete(context.Background(), obj); err != nil { t.Errorf("error cleaning up %s %s/%s: %s", obj.GetObjectKind().GroupVersionKind(), obj.GetNamespace(), obj.GetName(), err) } }) } +func createAndCleanupErr(t *testing.T, cl client.Client, obj client.Object) error { + t.Helper() + + err := cl.Create(t.Context(), obj) + if err != nil { + return err + } + + t.Cleanup(func() { + if err = cl.Delete(context.Background(), obj); err != nil { + t.Errorf("error cleaning up %s %s/%s: %s", obj.GetObjectKind().GroupVersionKind(), obj.GetNamespace(), obj.GetName(), err) + } + }) + + return nil +} + func get(ctx context.Context, cl client.Client, obj client.Object) error { return cl.Get(ctx, client.ObjectKeyFromObject(obj), obj) } diff --git a/cmd/k8s-operator/e2e/pebble.go b/cmd/k8s-operator/e2e/pebble.go index a3175a4edc771..5fcb35e057c3d 100644 --- a/cmd/k8s-operator/e2e/pebble.go +++ b/cmd/k8s-operator/e2e/pebble.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e @@ -12,6 +12,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + "tailscale.com/types/ptr" ) diff --git a/cmd/k8s-operator/e2e/proxy_test.go b/cmd/k8s-operator/e2e/proxy_test.go index b61d6d5763810..2d4fa53cc2589 100644 --- a/cmd/k8s-operator/e2e/proxy_test.go +++ b/cmd/k8s-operator/e2e/proxy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e @@ -16,6 +16,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + "tailscale.com/ipn" "tailscale.com/tstest" ) diff --git a/cmd/k8s-operator/e2e/proxygrouppolicy_test.go b/cmd/k8s-operator/e2e/proxygrouppolicy_test.go new file mode 100644 index 0000000000000..f8126499b0db0 --- /dev/null +++ b/cmd/k8s-operator/e2e/proxygrouppolicy_test.go @@ -0,0 +1,161 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package e2e + +import ( + "strings" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/types/ptr" +) + +// See [TestMain] for test requirements. +func TestProxyGroupPolicy(t *testing.T) { + if tnClient == nil { + t.Skip("TestProxyGroupPolicy requires a working tailnet client") + } + + // Apply deny-all policy + denyAllPolicy := &tsapi.ProxyGroupPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{}, + Egress: []string{}, + }, + } + + createAndCleanup(t, kubeClient, denyAllPolicy) + <-time.After(time.Second * 2) + + // Attempt to create an egress Service within the default namespace, the above policy should + // reject it. + egressService := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "egress-to-proxy-group", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{ + "tailscale.com/tailnet-fqdn": "test.something.ts.net", + "tailscale.com/proxy-group": "test", + }, + }, + Spec: corev1.ServiceSpec{ + ExternalName: "placeholder", + Type: corev1.ServiceTypeExternalName, + Ports: []corev1.ServicePort{ + { + Port: 8080, + Protocol: corev1.ProtocolTCP, + Name: "http", + }, + }, + }, + } + + err := createAndCleanupErr(t, kubeClient, egressService) + switch { + case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): + case err != nil: + t.Fatalf("expected forbidden error, got: %v", err) + default: + t.Fatal("expected error when creating egress service") + } + + // Attempt to create an ingress Service within the default namespace, the above policy should + // reject it. + ingressService := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-to-proxy-group", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{ + "tailscale.com/proxy-group": "test", + }, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + LoadBalancerClass: ptr.To("tailscale"), + Ports: []corev1.ServicePort{ + { + Port: 8080, + Protocol: corev1.ProtocolTCP, + Name: "http", + }, + }, + }, + } + + err = createAndCleanupErr(t, kubeClient, ingressService) + switch { + case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): + case err != nil: + t.Fatalf("expected forbidden error, got: %v", err) + default: + t.Fatal("expected error when creating ingress service") + } + + // Attempt to create an Ingress within the default namespace, the above policy should reject it + ingress := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-to-proxy-group", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{ + "tailscale.com/proxy-group": "test", + }, + }, + Spec: networkingv1.IngressSpec{ + IngressClassName: ptr.To("tailscale"), + DefaultBackend: &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "nginx", + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"nginx"}, + }, + }, + }, + } + + err = createAndCleanupErr(t, kubeClient, ingress) + switch { + case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): + case err != nil: + t.Fatalf("expected forbidden error, got: %v", err) + default: + t.Fatal("expected error when creating ingress") + } + + // Add policy to allow ingress/egress using the "test" proxy-group. This should be merged with the deny-all + // policy so they do not conflict. + allowTestPolicy := &tsapi.ProxyGroupPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-test", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{"test"}, + Egress: []string{"test"}, + }, + } + + createAndCleanup(t, kubeClient, allowTestPolicy) + <-time.After(time.Second * 2) + + // With this policy in place, the above ingress/egress resources should be allowed to be created. + createAndCleanup(t, kubeClient, egressService) + createAndCleanup(t, kubeClient, ingressService) + createAndCleanup(t, kubeClient, ingress) +} diff --git a/cmd/k8s-operator/e2e/setup.go b/cmd/k8s-operator/e2e/setup.go index 00e75ddd5b3eb..c4fd45d3e4125 100644 --- a/cmd/k8s-operator/e2e/setup.go +++ b/cmd/k8s-operator/e2e/setup.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e @@ -52,6 +52,7 @@ import ( "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/cmd" + "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" "tailscale.com/ipn/store/mem" @@ -69,6 +70,7 @@ const ( var ( tsClient *tailscale.Client // For API calls to control. tnClient *tsnet.Server // For testing real tailnet traffic. + restCfg *rest.Config // For constructing a client-go client if necessary. kubeClient client.WithWatch // For k8s API calls. //go:embed certs/pebble.minica.crt @@ -140,7 +142,7 @@ func runTests(m *testing.M) (int, error) { } // Cluster client setup. - restCfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + restCfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return 0, fmt.Errorf("error loading kubeconfig: %w", err) } diff --git a/cmd/k8s-operator/e2e/ssh.go b/cmd/k8s-operator/e2e/ssh.go index 407e4e085b7a9..371c44f9d4544 100644 --- a/cmd/k8s-operator/e2e/ssh.go +++ b/cmd/k8s-operator/e2e/ssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + tailscaleroot "tailscale.com" "tailscale.com/types/ptr" ) diff --git a/cmd/k8s-operator/egress-eps.go b/cmd/k8s-operator/egress-eps.go index 88da9935320bf..5181edf49a26c 100644 --- a/cmd/k8s-operator/egress-eps.go +++ b/cmd/k8s-operator/egress-eps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-eps_test.go b/cmd/k8s-operator/egress-eps_test.go index bd80112aeb8a2..47acb64f27458 100644 --- a/cmd/k8s-operator/egress-eps_test.go +++ b/cmd/k8s-operator/egress-eps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-pod-readiness.go b/cmd/k8s-operator/egress-pod-readiness.go index ebab23ed06337..a8f306353d880 100644 --- a/cmd/k8s-operator/egress-pod-readiness.go +++ b/cmd/k8s-operator/egress-pod-readiness.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-pod-readiness_test.go b/cmd/k8s-operator/egress-pod-readiness_test.go index 3c35d9043ebe6..baa1442671907 100644 --- a/cmd/k8s-operator/egress-pod-readiness_test.go +++ b/cmd/k8s-operator/egress-pod-readiness_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-services-readiness.go b/cmd/k8s-operator/egress-services-readiness.go index 80f3c7d285141..965dc08f87f1d 100644 --- a/cmd/k8s-operator/egress-services-readiness.go +++ b/cmd/k8s-operator/egress-services-readiness.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-services-readiness_test.go b/cmd/k8s-operator/egress-services-readiness_test.go index fdff4fafa3240..ba89903df2f29 100644 --- a/cmd/k8s-operator/egress-services-readiness_test.go +++ b/cmd/k8s-operator/egress-services-readiness_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-services.go b/cmd/k8s-operator/egress-services.go index 05be8efed9402..90ab2c88270ee 100644 --- a/cmd/k8s-operator/egress-services.go +++ b/cmd/k8s-operator/egress-services.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-services_test.go b/cmd/k8s-operator/egress-services_test.go index 202804d3011fd..45861449191cb 100644 --- a/cmd/k8s-operator/egress-services_test.go +++ b/cmd/k8s-operator/egress-services_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/generate/main.go b/cmd/k8s-operator/generate/main.go index 08bdc350d500c..840812ea3b248 100644 --- a/cmd/k8s-operator/generate/main.go +++ b/cmd/k8s-operator/generate/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -20,18 +20,22 @@ import ( ) const ( - operatorDeploymentFilesPath = "cmd/k8s-operator/deploy" - connectorCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_connectors.yaml" - proxyClassCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxyclasses.yaml" - dnsConfigCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_dnsconfigs.yaml" - recorderCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_recorders.yaml" - proxyGroupCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygroups.yaml" - helmTemplatesPath = operatorDeploymentFilesPath + "/chart/templates" - connectorCRDHelmTemplatePath = helmTemplatesPath + "/connector.yaml" - proxyClassCRDHelmTemplatePath = helmTemplatesPath + "/proxyclass.yaml" - dnsConfigCRDHelmTemplatePath = helmTemplatesPath + "/dnsconfig.yaml" - recorderCRDHelmTemplatePath = helmTemplatesPath + "/recorder.yaml" - proxyGroupCRDHelmTemplatePath = helmTemplatesPath + "/proxygroup.yaml" + operatorDeploymentFilesPath = "cmd/k8s-operator/deploy" + connectorCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_connectors.yaml" + proxyClassCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxyclasses.yaml" + dnsConfigCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_dnsconfigs.yaml" + recorderCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_recorders.yaml" + proxyGroupCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygroups.yaml" + tailnetCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_tailnets.yaml" + proxyGroupPolicyCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygrouppolicies.yaml" + helmTemplatesPath = operatorDeploymentFilesPath + "/chart/templates" + connectorCRDHelmTemplatePath = helmTemplatesPath + "/connector.yaml" + proxyClassCRDHelmTemplatePath = helmTemplatesPath + "/proxyclass.yaml" + dnsConfigCRDHelmTemplatePath = helmTemplatesPath + "/dnsconfig.yaml" + recorderCRDHelmTemplatePath = helmTemplatesPath + "/recorder.yaml" + proxyGroupCRDHelmTemplatePath = helmTemplatesPath + "/proxygroup.yaml" + tailnetCRDHelmTemplatePath = helmTemplatesPath + "/tailnet.yaml" + proxyGroupPolicyCRDHelmTemplatePath = helmTemplatesPath + "/proxygrouppolicy.yaml" helmConditionalStart = "{{ if .Values.installCRDs -}}\n" helmConditionalEnd = "{{- end -}}" @@ -154,6 +158,8 @@ func generate(baseDir string) error { {dnsConfigCRDPath, dnsConfigCRDHelmTemplatePath}, {recorderCRDPath, recorderCRDHelmTemplatePath}, {proxyGroupCRDPath, proxyGroupCRDHelmTemplatePath}, + {tailnetCRDPath, tailnetCRDHelmTemplatePath}, + {proxyGroupPolicyCRDPath, proxyGroupPolicyCRDHelmTemplatePath}, } { if err := addCRDToHelm(crd.crdPath, crd.templatePath); err != nil { return fmt.Errorf("error adding %s CRD to Helm templates: %w", crd.crdPath, err) @@ -170,6 +176,8 @@ func cleanup(baseDir string) error { dnsConfigCRDHelmTemplatePath, recorderCRDHelmTemplatePath, proxyGroupCRDHelmTemplatePath, + tailnetCRDHelmTemplatePath, + proxyGroupPolicyCRDHelmTemplatePath, } { if err := os.Remove(filepath.Join(baseDir, path)); err != nil && !os.IsNotExist(err) { return fmt.Errorf("error cleaning up %s: %w", path, err) diff --git a/cmd/k8s-operator/generate/main_test.go b/cmd/k8s-operator/generate/main_test.go index 5ea7fec80971a..775d16ba1e827 100644 --- a/cmd/k8s-operator/generate/main_test.go +++ b/cmd/k8s-operator/generate/main_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 && !windows diff --git a/cmd/k8s-operator/ingress-for-pg.go b/cmd/k8s-operator/ingress-for-pg.go index 1b35d853688cd..60196ce1505ff 100644 --- a/cmd/k8s-operator/ingress-for-pg.go +++ b/cmd/k8s-operator/ingress-for-pg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -32,7 +32,6 @@ import ( "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" - "tailscale.com/ipn/ipnstate" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" @@ -44,22 +43,15 @@ import ( ) const ( - serveConfigKey = "serve-config.json" - TailscaleSvcOwnerRef = "tailscale.com/k8s-operator:owned-by:%s" + serveConfigKey = "serve-config.json" // FinalizerNamePG is the finalizer used by the IngressPGReconciler - FinalizerNamePG = "tailscale.com/ingress-pg-finalizer" - + FinalizerNamePG = "tailscale.com/ingress-pg-finalizer" indexIngressProxyGroup = ".metadata.annotations.ingress-proxy-group" // annotationHTTPEndpoint can be used to configure the Ingress to expose an HTTP endpoint to tailnet (as // well as the default HTTPS endpoint). - annotationHTTPEndpoint = "tailscale.com/http-endpoint" - - labelDomain = "tailscale.com/domain" - msgFeatureFlagNotEnabled = "Tailscale Service feature flag is not enabled for this tailnet, skipping provisioning. " + - "Please contact Tailscale support through https://tailscale.com/contact/support to enable the feature flag, then recreate the operator's Pod." - - warningTailscaleServiceFeatureFlagNotEnabled = "TailscaleServiceFeatureFlagNotEnabled" - managedTSServiceComment = "This Tailscale Service is managed by the Tailscale Kubernetes Operator, do not modify" + annotationHTTPEndpoint = "tailscale.com/http-endpoint" + labelDomain = "tailscale.com/domain" + managedTSServiceComment = "This Tailscale Service is managed by the Tailscale Kubernetes Operator, do not modify" ) var gaugePGIngressResources = clientmetric.NewGauge(kubetypes.MetricIngressPGResourceCount) @@ -74,7 +66,6 @@ type HAIngressReconciler struct { tsClient tsClient tsnetServer tsnetServer tsNamespace string - lc localClient defaultTags []string operatorID string // stableID of the operator's Tailscale device ingressClassName string @@ -108,11 +99,12 @@ func (r *HAIngressReconciler) Reconcile(ctx context.Context, req reconcile.Reque ing := new(networkingv1.Ingress) err = r.Get(ctx, req.NamespacedName, ing) - if apierrors.IsNotFound(err) { + switch { + case apierrors.IsNotFound(err): // Request object not found, could have been deleted after reconcile request. logger.Debugf("Ingress not found, assuming it was deleted") return res, nil - } else if err != nil { + case err != nil: return res, fmt.Errorf("failed to get Ingress: %w", err) } @@ -122,6 +114,23 @@ func (r *HAIngressReconciler) Reconcile(ctx context.Context, req reconcile.Reque hostname := hostnameForIngress(ing) logger = logger.With("hostname", hostname) + pgName := ing.Annotations[AnnotationProxyGroup] + pg := &tsapi.ProxyGroup{} + + err = r.Get(ctx, client.ObjectKey{Name: pgName}, pg) + switch { + case apierrors.IsNotFound(err): + logger.Infof("ProxyGroup %q does not exist, it may have been deleted. Reconciliation for ingress %q will be skipped until the ProxyGroup is found", pgName, ing.Name) + return res, nil + case err != nil: + return res, fmt.Errorf("getting ProxyGroup %q: %w", pgName, err) + } + + tailscaleClient, err := clientFromProxyGroup(ctx, r.Client, pg, r.tsNamespace, r.tsClient) + if err != nil { + return res, fmt.Errorf("failed to get tailscale client: %w", err) + } + // needsRequeue is set to true if the underlying Tailscale Service has // changed as a result of this reconcile. If that is the case, we // reconcile the Ingress one more time to ensure that concurrent updates @@ -129,9 +138,9 @@ func (r *HAIngressReconciler) Reconcile(ctx context.Context, req reconcile.Reque // resulted in another actor overwriting our Tailscale Service update. needsRequeue := false if !ing.DeletionTimestamp.IsZero() || !r.shouldExpose(ing) { - needsRequeue, err = r.maybeCleanup(ctx, hostname, ing, logger) + needsRequeue, err = r.maybeCleanup(ctx, hostname, ing, logger, tailscaleClient, pg) } else { - needsRequeue, err = r.maybeProvision(ctx, hostname, ing, logger) + needsRequeue, err = r.maybeProvision(ctx, hostname, ing, logger, tailscaleClient, pg) } if err != nil { return res, err @@ -150,16 +159,16 @@ func (r *HAIngressReconciler) Reconcile(ctx context.Context, req reconcile.Reque // If a Tailscale Service exists, but does not have an owner reference from any operator, we error // out assuming that this is an owner reference created by an unknown actor. // Returns true if the operation resulted in a Tailscale Service update. -func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger) (svcsChanged bool, err error) { +func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger, tsClient tsClient, pg *tsapi.ProxyGroup) (svcsChanged bool, err error) { // Currently (2025-05) Tailscale Services are behind an alpha feature flag that // needs to be explicitly enabled for a tailnet to be able to use them. serviceName := tailcfg.ServiceName("svc:" + hostname) - existingTSSvc, err := r.tsClient.GetVIPService(ctx, serviceName) + existingTSSvc, err := tsClient.GetVIPService(ctx, serviceName) if err != nil && !isErrorTailscaleServiceNotFound(err) { return false, fmt.Errorf("error getting Tailscale Service %q: %w", hostname, err) } - if err := validateIngressClass(ctx, r.Client, r.ingressClassName); err != nil { + if err = validateIngressClass(ctx, r.Client, r.ingressClassName); err != nil { logger.Infof("error validating tailscale IngressClass: %v.", err) return false, nil } @@ -171,14 +180,6 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin } logger = logger.With("ProxyGroup", pgName) - pg := &tsapi.ProxyGroup{} - if err := r.Get(ctx, client.ObjectKey{Name: pgName}, pg); err != nil { - if apierrors.IsNotFound(err) { - logger.Infof("ProxyGroup does not exist") - return false, nil - } - return false, fmt.Errorf("getting ProxyGroup %q: %w", pgName, err) - } if !tsoperator.ProxyGroupAvailable(pg) { logger.Infof("ProxyGroup is not (yet) ready") return false, nil @@ -219,7 +220,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin // that in edge cases (a single update changed both hostname and removed // ProxyGroup annotation) the Tailscale Service is more likely to be // (eventually) removed. - svcsChanged, err = r.maybeCleanupProxyGroup(ctx, pgName, logger) + svcsChanged, err = r.maybeCleanupProxyGroup(ctx, logger, tsClient, pg) if err != nil { return false, fmt.Errorf("failed to cleanup Tailscale Service resources for ProxyGroup: %w", err) } @@ -244,12 +245,12 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin return false, nil } // 3. Ensure that TLS Secret and RBAC exists - tcd, err := tailnetCertDomain(ctx, r.lc) + dnsName, err := dnsNameForService(ctx, r.Client, serviceName, pg, r.tsNamespace) if err != nil { - return false, fmt.Errorf("error determining DNS name base: %w", err) + return false, fmt.Errorf("error determining DNS name for service: %w", err) } - dnsName := hostname + "." + tcd - if err := r.ensureCertResources(ctx, pg, dnsName, ing); err != nil { + + if err = r.ensureCertResources(ctx, pg, dnsName, ing); err != nil { return false, fmt.Errorf("error ensuring cert resources: %w", err) } @@ -357,7 +358,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin !reflect.DeepEqual(tsSvc.Ports, existingTSSvc.Ports) || !ownersAreSetAndEqual(tsSvc, existingTSSvc) { logger.Infof("Ensuring Tailscale Service exists and is up to date") - if err := r.tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { + if err := tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { return false, fmt.Errorf("error creating Tailscale Service: %w", err) } } @@ -368,7 +369,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin if isHTTPEndpointEnabled(ing) || isHTTPRedirectEnabled(ing) { mode = serviceAdvertisementHTTPAndHTTPS } - if err = r.maybeUpdateAdvertiseServicesConfig(ctx, pg.Name, serviceName, mode, logger); err != nil { + if err = r.maybeUpdateAdvertiseServicesConfig(ctx, serviceName, mode, pg); err != nil { return false, fmt.Errorf("failed to update tailscaled config: %w", err) } @@ -385,7 +386,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin ing.Status.LoadBalancer.Ingress = nil default: var ports []networkingv1.IngressPortStatus - hasCerts, err := hasCerts(ctx, r.Client, r.lc, r.tsNamespace, serviceName) + hasCerts, err := hasCerts(ctx, r.Client, r.tsNamespace, serviceName, pg) if err != nil { return false, fmt.Errorf("error checking TLS credentials provisioned for Ingress: %w", err) } @@ -425,9 +426,10 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin logger.Infof("%s. %d Pod(s) advertising Tailscale Service", prefix, count) } - if err := r.Status().Update(ctx, ing); err != nil { + if err = r.Status().Update(ctx, ing); err != nil { return false, fmt.Errorf("failed to update Ingress status: %w", err) } + return svcsChanged, nil } @@ -437,9 +439,9 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin // operator instances, else the owner reference is cleaned up. Returns true if // the operation resulted in an existing Tailscale Service updates (owner // reference removal). -func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyGroupName string, logger *zap.SugaredLogger) (svcsChanged bool, err error) { +func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, logger *zap.SugaredLogger, tsClient tsClient, pg *tsapi.ProxyGroup) (svcsChanged bool, err error) { // Get serve config for the ProxyGroup - cm, cfg, err := r.proxyGroupServeConfig(ctx, proxyGroupName) + cm, cfg, err := r.proxyGroupServeConfig(ctx, pg.Name) if err != nil { return false, fmt.Errorf("getting serve config: %w", err) } @@ -467,7 +469,7 @@ func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG if !found { logger.Infof("Tailscale Service %q is not owned by any Ingress, cleaning up", tsSvcName) - tsService, err := r.tsClient.GetVIPService(ctx, tsSvcName) + tsService, err := tsClient.GetVIPService(ctx, tsSvcName) if isErrorTailscaleServiceNotFound(err) { return false, nil } @@ -476,22 +478,24 @@ func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG } // Delete the Tailscale Service from control if necessary. - svcsChanged, err = r.cleanupTailscaleService(ctx, tsService, logger) + svcsChanged, err = r.cleanupTailscaleService(ctx, tsService, logger, tsClient) if err != nil { return false, fmt.Errorf("deleting Tailscale Service %q: %w", tsSvcName, err) } // Make sure the Tailscale Service is not advertised in tailscaled or serve config. - if err = r.maybeUpdateAdvertiseServicesConfig(ctx, proxyGroupName, tsSvcName, serviceAdvertisementOff, logger); err != nil { + if err = r.maybeUpdateAdvertiseServicesConfig(ctx, tsSvcName, serviceAdvertisementOff, pg); err != nil { return false, fmt.Errorf("failed to update tailscaled config services: %w", err) } + _, ok := cfg.Services[tsSvcName] if ok { logger.Infof("Removing Tailscale Service %q from serve config", tsSvcName) delete(cfg.Services, tsSvcName) serveConfigChanged = true } - if err := cleanupCertResources(ctx, r.Client, r.lc, r.tsNamespace, proxyGroupName, tsSvcName); err != nil { + + if err = cleanupCertResources(ctx, r.Client, r.tsNamespace, tsSvcName, pg); err != nil { return false, fmt.Errorf("failed to clean up cert resources: %w", err) } } @@ -514,7 +518,7 @@ func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG // Ingress is being deleted or is unexposed. The cleanup is safe for a multi-cluster setup- the Tailscale Service is only // deleted if it does not contain any other owner references. If it does the cleanup only removes the owner reference // corresponding to this Ingress. -func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger) (svcChanged bool, err error) { +func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger, tsClient tsClient, pg *tsapi.ProxyGroup) (svcChanged bool, err error) { logger.Debugf("Ensuring any resources for Ingress are cleaned up") ix := slices.Index(ing.Finalizers, FinalizerNamePG) if ix < 0 { @@ -523,7 +527,7 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, } logger.Infof("Ensuring that Tailscale Service %q configuration is cleaned up", hostname) serviceName := tailcfg.ServiceName("svc:" + hostname) - svc, err := r.tsClient.GetVIPService(ctx, serviceName) + svc, err := tsClient.GetVIPService(ctx, serviceName) if err != nil && !isErrorTailscaleServiceNotFound(err) { return false, fmt.Errorf("error getting Tailscale Service: %w", err) } @@ -537,8 +541,7 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, }() // 1. Check if there is a Tailscale Service associated with this Ingress. - pg := ing.Annotations[AnnotationProxyGroup] - cm, cfg, err := r.proxyGroupServeConfig(ctx, pg) + cm, cfg, err := r.proxyGroupServeConfig(ctx, pg.Name) if err != nil { return false, fmt.Errorf("error getting ProxyGroup serve config: %w", err) } @@ -552,13 +555,13 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, } // 2. Clean up the Tailscale Service resources. - svcChanged, err = r.cleanupTailscaleService(ctx, svc, logger) + svcChanged, err = r.cleanupTailscaleService(ctx, svc, logger, tsClient) if err != nil { return false, fmt.Errorf("error deleting Tailscale Service: %w", err) } // 3. Clean up any cluster resources - if err := cleanupCertResources(ctx, r.Client, r.lc, r.tsNamespace, pg, serviceName); err != nil { + if err = cleanupCertResources(ctx, r.Client, r.tsNamespace, serviceName, pg); err != nil { return false, fmt.Errorf("failed to clean up cert resources: %w", err) } @@ -567,12 +570,12 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, } // 4. Unadvertise the Tailscale Service in tailscaled config. - if err = r.maybeUpdateAdvertiseServicesConfig(ctx, pg, serviceName, serviceAdvertisementOff, logger); err != nil { + if err = r.maybeUpdateAdvertiseServicesConfig(ctx, serviceName, serviceAdvertisementOff, pg); err != nil { return false, fmt.Errorf("failed to update tailscaled config services: %w", err) } // 5. Remove the Tailscale Service from the serve config for the ProxyGroup. - logger.Infof("Removing TailscaleService %q from serve config for ProxyGroup %q", hostname, pg) + logger.Infof("Removing TailscaleService %q from serve config for ProxyGroup %q", hostname, pg.Name) delete(cfg.Services, serviceName) cfgBytes, err := json.Marshal(cfg) if err != nil { @@ -630,19 +633,6 @@ func (r *HAIngressReconciler) proxyGroupServeConfig(ctx context.Context, pg stri return cm, cfg, nil } -type localClient interface { - StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) -} - -// tailnetCertDomain returns the base domain (TCD) of the current tailnet. -func tailnetCertDomain(ctx context.Context, lc localClient) (string, error) { - st, err := lc.StatusWithoutPeers(ctx) - if err != nil { - return "", fmt.Errorf("error getting tailscale status: %w", err) - } - return st.CurrentTailnet.MagicDNSSuffix, nil -} - // shouldExpose returns true if the Ingress should be exposed over Tailscale in HA mode (on a ProxyGroup). func (r *HAIngressReconciler) shouldExpose(ing *networkingv1.Ingress) bool { isTSIngress := ing != nil && @@ -707,7 +697,7 @@ func (r *HAIngressReconciler) validateIngress(ctx context.Context, ing *networki // If a Tailscale Service is found, but contains other owner references, only removes this operator's owner reference. // If a Tailscale Service by the given name is not found or does not contain this operator's owner reference, do nothing. // It returns true if an existing Tailscale Service was updated to remove owner reference, as well as any error that occurred. -func (r *HAIngressReconciler) cleanupTailscaleService(ctx context.Context, svc *tailscale.VIPService, logger *zap.SugaredLogger) (updated bool, _ error) { +func (r *HAIngressReconciler) cleanupTailscaleService(ctx context.Context, svc *tailscale.VIPService, logger *zap.SugaredLogger, tsClient tsClient) (updated bool, _ error) { if svc == nil { return false, nil } @@ -730,7 +720,7 @@ func (r *HAIngressReconciler) cleanupTailscaleService(ctx context.Context, svc * } if len(o.OwnerRefs) == 1 { logger.Infof("Deleting Tailscale Service %q", svc.Name) - if err = r.tsClient.DeleteVIPService(ctx, svc.Name); err != nil && !isErrorTailscaleServiceNotFound(err) { + if err = tsClient.DeleteVIPService(ctx, svc.Name); err != nil && !isErrorTailscaleServiceNotFound(err) { return false, err } @@ -744,7 +734,7 @@ func (r *HAIngressReconciler) cleanupTailscaleService(ctx context.Context, svc * return false, fmt.Errorf("error marshalling updated Tailscale Service owner reference: %w", err) } svc.Annotations[ownerAnnotation] = string(json) - return true, r.tsClient.CreateOrUpdateVIPService(ctx, svc) + return true, tsClient.CreateOrUpdateVIPService(ctx, svc) } // isHTTPEndpointEnabled returns true if the Ingress has been configured to expose an HTTP endpoint to tailnet. @@ -764,10 +754,10 @@ const ( serviceAdvertisementHTTPAndHTTPS // Both ports 80 and 443 should be advertised ) -func (a *HAIngressReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Context, pgName string, serviceName tailcfg.ServiceName, mode serviceAdvertisementMode, logger *zap.SugaredLogger) (err error) { +func (r *HAIngressReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Context, serviceName tailcfg.ServiceName, mode serviceAdvertisementMode, pg *tsapi.ProxyGroup) (err error) { // Get all config Secrets for this ProxyGroup. secrets := &corev1.SecretList{} - if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeConfig))); err != nil { + if err := r.List(ctx, secrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pg.Name, kubetypes.LabelSecretTypeConfig))); err != nil { return fmt.Errorf("failed to list config Secrets: %w", err) } @@ -779,7 +769,7 @@ func (a *HAIngressReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con // The only exception is Ingresses with an HTTP endpoint enabled - if an // Ingress has an HTTP endpoint enabled, it will be advertised even if the // TLS cert is not yet provisioned. - hasCert, err := hasCerts(ctx, a.Client, a.lc, a.tsNamespace, serviceName) + hasCert, err := hasCerts(ctx, r.Client, r.tsNamespace, serviceName, pg) if err != nil { return fmt.Errorf("error checking TLS credentials provisioned for service %q: %w", serviceName, err) } @@ -819,7 +809,7 @@ func (a *HAIngressReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con } if updated { - if err := a.Update(ctx, &secret); err != nil { + if err := r.Update(ctx, &secret); err != nil { return fmt.Errorf("error updating ProxyGroup config Secret: %w", err) } } @@ -979,12 +969,12 @@ func (r *HAIngressReconciler) ensureCertResources(ctx context.Context, pg *tsapi // cleanupCertResources ensures that the TLS Secret and associated RBAC // resources that allow proxies to read/write to the Secret are deleted. -func cleanupCertResources(ctx context.Context, cl client.Client, lc localClient, tsNamespace, pgName string, serviceName tailcfg.ServiceName) error { - domainName, err := dnsNameForService(ctx, lc, serviceName) +func cleanupCertResources(ctx context.Context, cl client.Client, tsNamespace string, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup) error { + domainName, err := dnsNameForService(ctx, cl, serviceName, pg, tsNamespace) if err != nil { return fmt.Errorf("error getting DNS name for Tailscale Service %s: %w", serviceName, err) } - labels := certResourceLabels(pgName, domainName) + labels := certResourceLabels(pg.Name, domainName) if err := cl.DeleteAllOf(ctx, &rbacv1.RoleBinding{}, client.InNamespace(tsNamespace), client.MatchingLabels(labels)); err != nil { return fmt.Errorf("error deleting RoleBinding for domain name %s: %w", domainName, err) } @@ -1094,19 +1084,9 @@ func certResourceLabels(pgName, domain string) map[string]string { } } -// dnsNameForService returns the DNS name for the given Tailscale Service's name. -func dnsNameForService(ctx context.Context, lc localClient, svc tailcfg.ServiceName) (string, error) { - s := svc.WithoutPrefix() - tcd, err := tailnetCertDomain(ctx, lc) - if err != nil { - return "", fmt.Errorf("error determining DNS name base: %w", err) - } - return s + "." + tcd, nil -} - // hasCerts checks if the TLS Secret for the given service has non-zero cert and key data. -func hasCerts(ctx context.Context, cl client.Client, lc localClient, ns string, svc tailcfg.ServiceName) (bool, error) { - domain, err := dnsNameForService(ctx, lc, svc) +func hasCerts(ctx context.Context, cl client.Client, ns string, svc tailcfg.ServiceName, pg *tsapi.ProxyGroup) (bool, error) { + domain, err := dnsNameForService(ctx, cl, svc, pg, ns) if err != nil { return false, fmt.Errorf("failed to get DNS name for service: %w", err) } diff --git a/cmd/k8s-operator/ingress-for-pg_test.go b/cmd/k8s-operator/ingress-for-pg_test.go index 0f5527185a738..3c9c839177bb5 100644 --- a/cmd/k8s-operator/ingress-for-pg_test.go +++ b/cmd/k8s-operator/ingress-for-pg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -28,7 +28,6 @@ import ( "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" - "tailscale.com/ipn/ipnstate" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" @@ -563,16 +562,18 @@ func TestIngressPGReconciler_HTTPEndpoint(t *testing.T) { } // Add the Tailscale Service to prefs to have the Ingress recognised as ready. - mustCreate(t, fc, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pg-0", - Namespace: "operator-ns", - Labels: pgSecretLabels("test-pg", kubetypes.LabelSecretTypeState), - }, - Data: map[string][]byte{ - "_current-profile": []byte("profile-foo"), - "profile-foo": []byte(`{"AdvertiseServices":["svc:my-svc"],"Config":{"NodeID":"node-foo"}}`), - }, + mustUpdate(t, fc, "operator-ns", "test-pg-0", func(o *corev1.Secret) { + var p prefs + var err error + if err = json.Unmarshal(o.Data["test"], &p); err != nil { + t.Errorf("failed to unmarshal preferences: %v", err) + } + + p.AdvertiseServices = []string{"svc:my-svc"} + o.Data["test"], err = json.Marshal(p) + if err != nil { + t.Errorf("failed to marshal preferences: %v", err) + } }) // Reconcile and re-fetch Ingress. @@ -686,17 +687,19 @@ func TestIngressPGReconciler_HTTPRedirect(t *testing.T) { t.Fatal(err) } - // Add the Tailscale Service to prefs to have the Ingress recognised as ready - mustCreate(t, fc, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pg-0", - Namespace: "operator-ns", - Labels: pgSecretLabels("test-pg", kubetypes.LabelSecretTypeState), - }, - Data: map[string][]byte{ - "_current-profile": []byte("profile-foo"), - "profile-foo": []byte(`{"AdvertiseServices":["svc:my-svc"],"Config":{"NodeID":"node-foo"}}`), - }, + // Add the Tailscale Service to prefs to have the Ingress recognised as ready. + mustUpdate(t, fc, "operator-ns", "test-pg-0", func(o *corev1.Secret) { + var p prefs + var err error + if err = json.Unmarshal(o.Data["test"], &p); err != nil { + t.Errorf("failed to unmarshal preferences: %v", err) + } + + p.AdvertiseServices = []string{"svc:my-svc"} + o.Data["test"], err = json.Marshal(p) + if err != nil { + t.Errorf("failed to marshal preferences: %v", err) + } }) // Reconcile and re-fetch Ingress @@ -819,17 +822,19 @@ func TestIngressPGReconciler_HTTPEndpointAndRedirectConflict(t *testing.T) { t.Fatal(err) } - // Add the Tailscale Service to prefs to have the Ingress recognised as ready - mustCreate(t, fc, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pg-0", - Namespace: "operator-ns", - Labels: pgSecretLabels("test-pg", kubetypes.LabelSecretTypeState), - }, - Data: map[string][]byte{ - "_current-profile": []byte("profile-foo"), - "profile-foo": []byte(`{"AdvertiseServices":["svc:my-svc"],"Config":{"NodeID":"node-foo"}}`), - }, + // Add the Tailscale Service to prefs to have the Ingress recognised as ready. + mustUpdate(t, fc, "operator-ns", "test-pg-0", func(o *corev1.Secret) { + var p prefs + var err error + if err = json.Unmarshal(o.Data["test"], &p); err != nil { + t.Errorf("failed to unmarshal preferences: %v", err) + } + + p.AdvertiseServices = []string{"svc:my-svc"} + o.Data["test"], err = json.Marshal(p) + if err != nil { + t.Errorf("failed to marshal preferences: %v", err) + } }) // Reconcile and re-fetch Ingress @@ -1110,6 +1115,7 @@ func verifyTailscaledConfig(t *testing.T, fc client.Client, pgName string, expec func createPGResources(t *testing.T, fc client.Client, pgName string) { t.Helper() + // Pre-create the ProxyGroup pg := &tsapi.ProxyGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -1146,6 +1152,30 @@ func createPGResources(t *testing.T, fc client.Client, pgName string) { }, } mustCreate(t, fc, pgCfgSecret) + + pr := prefs{} + pr.Config.UserProfile.LoginName = "test.ts.net" + pr.Config.NodeID = "test" + + p, err := json.Marshal(pr) + if err != nil { + t.Fatalf("marshaling prefs: %v", err) + } + + // Pre-create a state secret for the ProxyGroup + pgStateSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pgStateSecretName(pgName, 0), + Namespace: "operator-ns", + Labels: pgSecretLabels(pgName, kubetypes.LabelSecretTypeState), + }, + Data: map[string][]byte{ + currentProfileKey: []byte("test"), + "test": p, + }, + } + mustCreate(t, fc, pgStateSecret) + pg.Status.Conditions = []metav1.Condition{ { Type: string(tsapi.ProxyGroupAvailable), @@ -1180,14 +1210,6 @@ func setupIngressTest(t *testing.T) (*HAIngressReconciler, client.Client, *fakeT t.Fatal(err) } - lc := &fakeLocalClient{ - status: &ipnstate.Status{ - CurrentTailnet: &ipnstate.TailnetStatus{ - MagicDNSSuffix: "ts.net", - }, - }, - } - ingPGR := &HAIngressReconciler{ Client: fc, tsClient: ft, @@ -1196,7 +1218,6 @@ func setupIngressTest(t *testing.T) (*HAIngressReconciler, client.Client, *fakeT tsnetServer: fakeTsnetServer, logger: zl.Sugar(), recorder: record.NewFakeRecorder(10), - lc: lc, ingressClassName: tsIngressClass.Name, } diff --git a/cmd/k8s-operator/ingress.go b/cmd/k8s-operator/ingress.go index 050b03f55970f..4952e789f6a02 100644 --- a/cmd/k8s-operator/ingress.go +++ b/cmd/k8s-operator/ingress.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -102,7 +102,7 @@ func (a *IngressReconciler) maybeCleanup(ctx context.Context, logger *zap.Sugare return nil } - if done, err := a.ssr.Cleanup(ctx, logger, childResourceLabels(ing.Name, ing.Namespace, "ingress"), proxyTypeIngressResource); err != nil { + if done, err := a.ssr.Cleanup(ctx, operatorTailnet, logger, childResourceLabels(ing.Name, ing.Namespace, "ingress"), proxyTypeIngressResource); err != nil { return fmt.Errorf("failed to cleanup: %w", err) } else if !done { logger.Debugf("cleanup not done yet, waiting for next reconcile") diff --git a/cmd/k8s-operator/ingress_test.go b/cmd/k8s-operator/ingress_test.go index 52afc3be40c50..aac40897cc88e 100644 --- a/cmd/k8s-operator/ingress_test.go +++ b/cmd/k8s-operator/ingress_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/logger.go b/cmd/k8s-operator/logger.go index 46b1fc0c82d48..45018e37eaf30 100644 --- a/cmd/k8s-operator/logger.go +++ b/cmd/k8s-operator/logger.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/metrics_resources.go b/cmd/k8s-operator/metrics_resources.go index 0579e34661a11..afb055018bb13 100644 --- a/cmd/k8s-operator/metrics_resources.go +++ b/cmd/k8s-operator/metrics_resources.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/nameserver.go b/cmd/k8s-operator/nameserver.go index 39db5f0f9cf16..522b460031530 100644 --- a/cmd/k8s-operator/nameserver.go +++ b/cmd/k8s-operator/nameserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/nameserver_test.go b/cmd/k8s-operator/nameserver_test.go index 858cd973d82c2..531190cf21dc2 100644 --- a/cmd/k8s-operator/nameserver_test.go +++ b/cmd/k8s-operator/nameserver_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/nodeport-service-ports.go b/cmd/k8s-operator/nodeport-service-ports.go index a9504e3e94f88..f8d28860bf84e 100644 --- a/cmd/k8s-operator/nodeport-service-ports.go +++ b/cmd/k8s-operator/nodeport-service-ports.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/k8s-operator/nodeport-services-ports_test.go b/cmd/k8s-operator/nodeport-services-ports_test.go index 9418bb8446bd8..9c147f79aecbd 100644 --- a/cmd/k8s-operator/nodeport-services-ports_test.go +++ b/cmd/k8s-operator/nodeport-services-ports_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/operator.go b/cmd/k8s-operator/operator.go index b50be8ce7ba66..1060c6f3da9e7 100644 --- a/cmd/k8s-operator/operator.go +++ b/cmd/k8s-operator/operator.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -20,6 +20,7 @@ import ( "github.com/go-logr/zapr" "go.uber.org/zap" "go.uber.org/zap/zapcore" + "golang.org/x/time/rate" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -54,6 +55,8 @@ import ( "tailscale.com/ipn/store/kubestore" apiproxy "tailscale.com/k8s-operator/api-proxy" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler/proxygrouppolicy" + "tailscale.com/k8s-operator/reconciler/tailnet" "tailscale.com/kube/kubetypes" "tailscale.com/tsnet" "tailscale.com/tstime" @@ -325,6 +328,25 @@ func runReconcilers(opts reconcilerOpts) { startlog.Fatalf("could not create manager: %v", err) } + tailnetOptions := tailnet.ReconcilerOptions{ + Client: mgr.GetClient(), + TailscaleNamespace: opts.tailscaleNamespace, + Clock: tstime.DefaultClock{}, + Logger: opts.log, + } + + if err = tailnet.NewReconciler(tailnetOptions).Register(mgr); err != nil { + startlog.Fatalf("could not register tailnet reconciler: %v", err) + } + + proxyGroupPolicyOptions := proxygrouppolicy.ReconcilerOptions{ + Client: mgr.GetClient(), + } + + if err = proxygrouppolicy.NewReconciler(proxyGroupPolicyOptions).Register(mgr); err != nil { + startlog.Fatalf("could not register proxygrouppolicy reconciler: %v", err) + } + svcFilter := handler.EnqueueRequestsFromMapFunc(serviceHandler) svcChildFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("svc")) // If a ProxyClass changes, enqueue all Services labeled with that @@ -420,7 +442,6 @@ func runReconcilers(opts reconcilerOpts) { defaultTags: strings.Split(opts.proxyTags, ","), Client: mgr.GetClient(), logger: opts.log.Named("ingress-pg-reconciler"), - lc: lc, operatorID: id, tsNamespace: opts.tailscaleNamespace, ingressClassName: opts.ingressClassName, @@ -446,7 +467,6 @@ func runReconcilers(opts reconcilerOpts) { defaultTags: strings.Split(opts.proxyTags, ","), Client: mgr.GetClient(), logger: opts.log.Named("service-pg-reconciler"), - lc: lc, clock: tstime.DefaultClock{}, operatorID: id, tsNamespace: opts.tailscaleNamespace, @@ -665,7 +685,6 @@ func runReconcilers(opts reconcilerOpts) { logger: opts.log.Named("kube-apiserver-ts-service-reconciler"), tsClient: opts.tsClient, tsNamespace: opts.tailscaleNamespace, - lc: lc, defaultTags: strings.Split(opts.proxyTags, ","), operatorID: id, clock: tstime.DefaultClock{}, @@ -705,6 +724,8 @@ func runReconcilers(opts reconcilerOpts) { tsFirewallMode: opts.proxyFirewallMode, defaultProxyClass: opts.defaultProxyClass, loginServer: opts.tsServer.ControlURL, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), }) if err != nil { startlog.Fatalf("could not create ProxyGroup reconciler: %v", err) diff --git a/cmd/k8s-operator/operator_test.go b/cmd/k8s-operator/operator_test.go index d0f42fe6dfad5..53d16fbd225f3 100644 --- a/cmd/k8s-operator/operator_test.go +++ b/cmd/k8s-operator/operator_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/proxyclass.go b/cmd/k8s-operator/proxyclass.go index 2d51b351d3907..c0ea46116373b 100644 --- a/cmd/k8s-operator/proxyclass.go +++ b/cmd/k8s-operator/proxyclass.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/proxyclass_test.go b/cmd/k8s-operator/proxyclass_test.go index ae0f63d99ea4d..171cfc5904cd3 100644 --- a/cmd/k8s-operator/proxyclass_test.go +++ b/cmd/k8s-operator/proxyclass_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index 946e017a26f00..2aef6ff9e8226 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -16,10 +16,12 @@ import ( "sort" "strings" "sync" + "time" dockerref "github.com/distribution/reference" "go.uber.org/zap" xslices "golang.org/x/exp/slices" + "golang.org/x/time/rate" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -49,11 +51,12 @@ import ( ) const ( - reasonProxyGroupCreationFailed = "ProxyGroupCreationFailed" - reasonProxyGroupReady = "ProxyGroupReady" - reasonProxyGroupAvailable = "ProxyGroupAvailable" - reasonProxyGroupCreating = "ProxyGroupCreating" - reasonProxyGroupInvalid = "ProxyGroupInvalid" + reasonProxyGroupCreationFailed = "ProxyGroupCreationFailed" + reasonProxyGroupReady = "ProxyGroupReady" + reasonProxyGroupAvailable = "ProxyGroupAvailable" + reasonProxyGroupCreating = "ProxyGroupCreating" + reasonProxyGroupInvalid = "ProxyGroupInvalid" + reasonProxyGroupTailnetUnavailable = "ProxyGroupTailnetUnavailable" // Copied from k8s.io/apiserver/pkg/registry/generic/registry/store.go@cccad306d649184bf2a0e319ba830c53f65c445c optimisticLockErrorMsg = "the object has been modified; please apply your changes to the latest version and try again" @@ -94,10 +97,12 @@ type ProxyGroupReconciler struct { defaultProxyClass string loginServer string - mu sync.Mutex // protects following - egressProxyGroups set.Slice[types.UID] // for egress proxygroups gauge - ingressProxyGroups set.Slice[types.UID] // for ingress proxygroups gauge - apiServerProxyGroups set.Slice[types.UID] // for kube-apiserver proxygroups gauge + mu sync.Mutex // protects following + egressProxyGroups set.Slice[types.UID] // for egress proxygroups gauge + ingressProxyGroups set.Slice[types.UID] // for ingress proxygroups gauge + apiServerProxyGroups set.Slice[types.UID] // for kube-apiserver proxygroups gauge + authKeyRateLimits map[string]*rate.Limiter // per-ProxyGroup rate limiters for auth key re-issuance. + authKeyReissuing map[string]bool } func (r *ProxyGroupReconciler) logger(name string) *zap.SugaredLogger { @@ -117,6 +122,18 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ } else if err != nil { return reconcile.Result{}, fmt.Errorf("failed to get tailscale.com ProxyGroup: %w", err) } + + tailscaleClient, loginUrl, err := r.getClientAndLoginURL(ctx, pg.Spec.Tailnet) + if err != nil { + oldPGStatus := pg.Status.DeepCopy() + nrr := ¬ReadyReason{ + reason: reasonProxyGroupTailnetUnavailable, + message: fmt.Errorf("failed to get tailscale client and loginUrl: %w", err).Error(), + } + + return reconcile.Result{}, errors.Join(err, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, make(map[string][]netip.AddrPort))) + } + if markedForDeletion(pg) { logger.Debugf("ProxyGroup is being deleted, cleaning up resources") ix := xslices.Index(pg.Finalizers, FinalizerName) @@ -125,7 +142,7 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ return reconcile.Result{}, nil } - if done, err := r.maybeCleanup(ctx, pg); err != nil { + if done, err := r.maybeCleanup(ctx, tailscaleClient, pg); err != nil { if strings.Contains(err.Error(), optimisticLockErrorMsg) { logger.Infof("optimistic lock error, retrying: %s", err) return reconcile.Result{}, nil @@ -144,7 +161,7 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ } oldPGStatus := pg.Status.DeepCopy() - staticEndpoints, nrr, err := r.reconcilePG(ctx, pg, logger) + staticEndpoints, nrr, err := r.reconcilePG(ctx, tailscaleClient, loginUrl, pg, logger) return reconcile.Result{}, errors.Join(err, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, staticEndpoints)) } @@ -152,7 +169,7 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ // for deletion. It is separated out from Reconcile to make a clear separation // between reconciling the ProxyGroup, and posting the status of its created // resources onto the ProxyGroup status field. -func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (map[string][]netip.AddrPort, *notReadyReason, error) { +func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, tailscaleClient tsClient, loginUrl string, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (map[string][]netip.AddrPort, *notReadyReason, error) { if !slices.Contains(pg.Finalizers, FinalizerName) { // This log line is printed exactly once during initial provisioning, // because once the finalizer is in place this block gets skipped. So, @@ -193,7 +210,7 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyG return notReady(reasonProxyGroupInvalid, fmt.Sprintf("invalid ProxyGroup spec: %v", err)) } - staticEndpoints, nrr, err := r.maybeProvision(ctx, pg, proxyClass) + staticEndpoints, nrr, err := r.maybeProvision(ctx, tailscaleClient, loginUrl, pg, proxyClass) if err != nil { return nil, nrr, err } @@ -279,10 +296,10 @@ func (r *ProxyGroupReconciler) validate(ctx context.Context, pg *tsapi.ProxyGrou return errors.Join(errs...) } -func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) { +func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, loginUrl string, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) { logger := r.logger(pg.Name) r.mu.Lock() - r.ensureAddedToGaugeForProxyGroup(pg) + r.ensureStateAddedForProxyGroup(pg) r.mu.Unlock() svcToNodePorts := make(map[string]uint16) @@ -302,7 +319,7 @@ func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.Pro } } - staticEndpoints, err := r.ensureConfigSecretsCreated(ctx, pg, proxyClass, svcToNodePorts) + staticEndpoints, err := r.ensureConfigSecretsCreated(ctx, tailscaleClient, loginUrl, pg, proxyClass, svcToNodePorts) if err != nil { var selectorErr *FindStaticEndpointErr if errors.As(err, &selectorErr) { @@ -414,7 +431,7 @@ func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.Pro return r.notReadyErrf(pg, logger, "error reconciling metrics resources: %w", err) } - if err := r.cleanupDanglingResources(ctx, pg, proxyClass); err != nil { + if err := r.cleanupDanglingResources(ctx, tailscaleClient, pg, proxyClass); err != nil { return r.notReadyErrf(pg, logger, "error cleaning up dangling resources: %w", err) } @@ -611,21 +628,21 @@ func (r *ProxyGroupReconciler) ensureNodePortServiceCreated(ctx context.Context, // cleanupDanglingResources ensures we don't leak config secrets, state secrets, and // tailnet devices when the number of replicas specified is reduced. -func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass) error { +func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass) error { logger := r.logger(pg.Name) - metadata, err := r.getNodeMetadata(ctx, pg) + metadata, err := getNodeMetadata(ctx, pg, r.Client, r.tsNamespace) if err != nil { return err } for _, m := range metadata { - if m.ordinal+1 <= int(pgReplicas(pg)) { + if m.ordinal+1 <= pgReplicas(pg) { continue } // Dangling resource, delete the config + state Secrets, as well as // deleting the device from the tailnet. - if err := r.deleteTailnetDevice(ctx, m.tsID, logger); err != nil { + if err := r.ensureDeviceDeleted(ctx, tailscaleClient, m.tsID, logger); err != nil { return err } if err := r.Delete(ctx, m.stateSecret); err != nil && !apierrors.IsNotFound(err) { @@ -668,16 +685,16 @@ func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, pg // maybeCleanup just deletes the device from the tailnet. All the kubernetes // resources linked to a ProxyGroup will get cleaned up via owner references // (which we can use because they are all in the same namespace). -func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, pg *tsapi.ProxyGroup) (bool, error) { +func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup) (bool, error) { logger := r.logger(pg.Name) - metadata, err := r.getNodeMetadata(ctx, pg) + metadata, err := getNodeMetadata(ctx, pg, r.Client, r.tsNamespace) if err != nil { return false, err } for _, m := range metadata { - if err := r.deleteTailnetDevice(ctx, m.tsID, logger); err != nil { + if err := r.ensureDeviceDeleted(ctx, tailscaleClient, m.tsID, logger); err != nil { return false, err } } @@ -693,14 +710,14 @@ func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, pg *tsapi.Proxy logger.Infof("cleaned up ProxyGroup resources") r.mu.Lock() - r.ensureRemovedFromGaugeForProxyGroup(pg) + r.ensureStateRemovedForProxyGroup(pg) r.mu.Unlock() return true, nil } -func (r *ProxyGroupReconciler) deleteTailnetDevice(ctx context.Context, id tailcfg.StableNodeID, logger *zap.SugaredLogger) error { +func (r *ProxyGroupReconciler) ensureDeviceDeleted(ctx context.Context, tailscaleClient tsClient, id tailcfg.StableNodeID, logger *zap.SugaredLogger) error { logger.Debugf("deleting device %s from control", string(id)) - if err := r.tsClient.DeleteDevice(ctx, string(id)); err != nil { + if err := tailscaleClient.DeleteDevice(ctx, string(id)); err != nil { errResp := &tailscale.ErrResponse{} if ok := errors.As(err, errResp); ok && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", string(id)) @@ -714,10 +731,18 @@ func (r *ProxyGroupReconciler) deleteTailnetDevice(ctx context.Context, id tailc return nil } -func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass, svcToNodePorts map[string]uint16) (endpoints map[string][]netip.AddrPort, err error) { +func (r *ProxyGroupReconciler) ensureConfigSecretsCreated( + ctx context.Context, + tailscaleClient tsClient, + loginUrl string, + pg *tsapi.ProxyGroup, + proxyClass *tsapi.ProxyClass, + svcToNodePorts map[string]uint16, +) (endpoints map[string][]netip.AddrPort, err error) { logger := r.logger(pg.Name) endpoints = make(map[string][]netip.AddrPort, pgReplicas(pg)) // keyed by Service name. for i := range pgReplicas(pg) { + logger = logger.With("Pod", fmt.Sprintf("%s-%d", pg.Name, i)) cfgSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: pgConfigSecretName(pg.Name, i), @@ -728,45 +753,16 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p } var existingCfgSecret *corev1.Secret // unmodified copy of secret - if err := r.Get(ctx, client.ObjectKeyFromObject(cfgSecret), cfgSecret); err == nil { + if err = r.Get(ctx, client.ObjectKeyFromObject(cfgSecret), cfgSecret); err == nil { logger.Debugf("Secret %s/%s already exists", cfgSecret.GetNamespace(), cfgSecret.GetName()) existingCfgSecret = cfgSecret.DeepCopy() } else if !apierrors.IsNotFound(err) { return nil, err } - var authKey *string - if existingCfgSecret == nil { - logger.Debugf("Creating authkey for new ProxyGroup proxy") - tags := pg.Spec.Tags.Stringify() - if len(tags) == 0 { - tags = r.defaultTags - } - key, err := newAuthKey(ctx, r.tsClient, tags) - if err != nil { - return nil, err - } - authKey = &key - } - - if authKey == nil { - // Get state Secret to check if it's already authed. - stateSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: pgStateSecretName(pg.Name, i), - Namespace: r.tsNamespace, - }, - } - if err := r.Get(ctx, client.ObjectKeyFromObject(stateSecret), stateSecret); err != nil && !apierrors.IsNotFound(err) { - return nil, err - } - - if shouldRetainAuthKey(stateSecret) && existingCfgSecret != nil { - authKey, err = authKeyFromSecret(existingCfgSecret) - if err != nil { - return nil, fmt.Errorf("error retrieving auth key from existing config Secret: %w", err) - } - } + authKey, err := r.getAuthKey(ctx, tailscaleClient, pg, existingCfgSecret, i, logger) + if err != nil { + return nil, err } nodePortSvcName := pgNodePortServiceName(pg.Name, i) @@ -846,8 +842,8 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p } } - if r.loginServer != "" { - cfg.ServerURL = &r.loginServer + if loginUrl != "" { + cfg.ServerURL = new(loginUrl) } if proxyClass != nil && proxyClass.Spec.TailscaleConfig != nil { @@ -875,7 +871,7 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p return nil, err } - configs, err := pgTailscaledConfig(pg, proxyClass, i, authKey, endpoints[nodePortSvcName], existingAdvertiseServices, r.loginServer) + configs, err := pgTailscaledConfig(pg, loginUrl, proxyClass, i, authKey, endpoints[nodePortSvcName], existingAdvertiseServices) if err != nil { return nil, fmt.Errorf("error creating tailscaled config: %w", err) } @@ -902,11 +898,137 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p return nil, err } } + } return endpoints, nil } +// getAuthKey returns an auth key for the proxy, or nil if none is needed. +// A new key is created if the config Secret doesn't exist yet, or if the +// proxy has requested a reissue via its state Secret. An existing key is +// retained while the device hasn't authed or a reissue is in progress. +func (r *ProxyGroupReconciler) getAuthKey(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, existingCfgSecret *corev1.Secret, ordinal int32, logger *zap.SugaredLogger) (*string, error) { + // Get state Secret to check if it's already authed or has requested + // a fresh auth key. + stateSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pgStateSecretName(pg.Name, ordinal), + Namespace: r.tsNamespace, + }, + } + if err := r.Get(ctx, client.ObjectKeyFromObject(stateSecret), stateSecret); err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + + var createAuthKey bool + var cfgAuthKey *string + if existingCfgSecret == nil { + createAuthKey = true + } else { + var err error + cfgAuthKey, err = authKeyFromSecret(existingCfgSecret) + if err != nil { + return nil, fmt.Errorf("error retrieving auth key from existing config Secret: %w", err) + } + } + + if !createAuthKey { + var err error + createAuthKey, err = r.shouldReissueAuthKey(ctx, tailscaleClient, pg, stateSecret, cfgAuthKey) + if err != nil { + return nil, err + } + } + + var authKey *string + if createAuthKey { + logger.Debugf("creating auth key for ProxyGroup proxy %q", stateSecret.Name) + + tags := pg.Spec.Tags.Stringify() + if len(tags) == 0 { + tags = r.defaultTags + } + key, err := newAuthKey(ctx, tailscaleClient, tags) + if err != nil { + return nil, err + } + authKey = &key + } else { + // Retain auth key if the device hasn't authed yet, or if a + // reissue is in progress (device_id is stale during reissue). + _, reissueRequested := stateSecret.Data[kubetypes.KeyReissueAuthkey] + if !deviceAuthed(stateSecret) || reissueRequested { + authKey = cfgAuthKey + } + } + + return authKey, nil +} + +// shouldReissueAuthKey returns true if the proxy needs a new auth key. It +// tracks in-flight reissues via authKeyReissuing to avoid duplicate API calls +// across reconciles. +func (r *ProxyGroupReconciler) shouldReissueAuthKey(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, stateSecret *corev1.Secret, cfgAuthKey *string) (shouldReissue bool, err error) { + r.mu.Lock() + reissuing := r.authKeyReissuing[stateSecret.Name] + r.mu.Unlock() + + if reissuing { + // Check if reissue is complete by seeing if request was cleared + _, requestStillPresent := stateSecret.Data[kubetypes.KeyReissueAuthkey] + if !requestStillPresent { + // Containerboot cleared the request, reissue is complete + r.mu.Lock() + r.authKeyReissuing[stateSecret.Name] = false + r.mu.Unlock() + r.log.Debugf("auth key reissue completed for %q", stateSecret.Name) + return false, nil + } + + // Reissue still in-flight; waiting for containerboot to pick up new key + r.log.Debugf("auth key already in process of re-issuance, waiting for secret to be updated") + return false, nil + } + + defer func() { + r.mu.Lock() + r.authKeyReissuing[stateSecret.Name] = shouldReissue + r.mu.Unlock() + }() + + brokenAuthkey, ok := stateSecret.Data[kubetypes.KeyReissueAuthkey] + if !ok { + // reissue hasn't been requested since the key in the secret hasn't been populated + return false, nil + } + + empty := cfgAuthKey == nil || *cfgAuthKey == "" + broken := cfgAuthKey != nil && *cfgAuthKey == string(brokenAuthkey) + + // A new key has been written but the proxy hasn't picked it up yet. + if !empty && !broken { + return false, nil + } + + lim := r.authKeyRateLimits[pg.Name] + if !lim.Allow() { + r.log.Debugf("auth key re-issuance rate limit exceeded, limit: %.2f, burst: %d, tokens: %.2f", + lim.Limit(), lim.Burst(), lim.Tokens()) + return false, fmt.Errorf("auth key re-issuance rate limit exceeded for ProxyGroup %q, will retry with backoff", pg.Name) + } + + r.log.Infof("Proxy failing to auth; attempting cleanup and new key") + if tsID := stateSecret.Data[kubetypes.KeyDeviceID]; len(tsID) > 0 { + id := tailcfg.StableNodeID(tsID) + if err := r.ensureDeviceDeleted(ctx, tailscaleClient, id, r.log); err != nil { + return false, err + } + } + + return true, nil +} + type FindStaticEndpointErr struct { msg string } @@ -1000,9 +1122,9 @@ func getStaticEndpointAddress(a *corev1.NodeAddress, port uint16) *netip.AddrPor return ptr.To(netip.AddrPortFrom(addr, port)) } -// ensureAddedToGaugeForProxyGroup ensures the gauge metric for the ProxyGroup resource is updated when the ProxyGroup -// is created. r.mu must be held. -func (r *ProxyGroupReconciler) ensureAddedToGaugeForProxyGroup(pg *tsapi.ProxyGroup) { +// ensureStateAddedForProxyGroup ensures the gauge metric for the ProxyGroup resource is updated when the ProxyGroup +// is created, and initialises per-ProxyGroup rate limits on re-issuing auth keys. r.mu must be held. +func (r *ProxyGroupReconciler) ensureStateAddedForProxyGroup(pg *tsapi.ProxyGroup) { switch pg.Spec.Type { case tsapi.ProxyGroupTypeEgress: r.egressProxyGroups.Add(pg.UID) @@ -1014,11 +1136,24 @@ func (r *ProxyGroupReconciler) ensureAddedToGaugeForProxyGroup(pg *tsapi.ProxyGr gaugeEgressProxyGroupResources.Set(int64(r.egressProxyGroups.Len())) gaugeIngressProxyGroupResources.Set(int64(r.ingressProxyGroups.Len())) gaugeAPIServerProxyGroupResources.Set(int64(r.apiServerProxyGroups.Len())) + + if _, ok := r.authKeyRateLimits[pg.Name]; !ok { + // Allow every replica to have its auth key re-issued quickly the first + // time, but with an overall limit of 1 every 30s after a burst. + r.authKeyRateLimits[pg.Name] = rate.NewLimiter(rate.Every(30*time.Second), int(pgReplicas(pg))) + } + + for i := range pgReplicas(pg) { + rep := pgStateSecretName(pg.Name, i) + if _, ok := r.authKeyReissuing[rep]; !ok { + r.authKeyReissuing[rep] = false + } + } } -// ensureRemovedFromGaugeForProxyGroup ensures the gauge metric for the ProxyGroup resource type is updated when the -// ProxyGroup is deleted. r.mu must be held. -func (r *ProxyGroupReconciler) ensureRemovedFromGaugeForProxyGroup(pg *tsapi.ProxyGroup) { +// ensureStateRemovedForProxyGroup ensures the gauge metric for the ProxyGroup resource type is updated when the +// ProxyGroup is deleted, and deletes the per-ProxyGroup rate limiter to free memory. r.mu must be held. +func (r *ProxyGroupReconciler) ensureStateRemovedForProxyGroup(pg *tsapi.ProxyGroup) { switch pg.Spec.Type { case tsapi.ProxyGroupTypeEgress: r.egressProxyGroups.Remove(pg.UID) @@ -1030,9 +1165,10 @@ func (r *ProxyGroupReconciler) ensureRemovedFromGaugeForProxyGroup(pg *tsapi.Pro gaugeEgressProxyGroupResources.Set(int64(r.egressProxyGroups.Len())) gaugeIngressProxyGroupResources.Set(int64(r.ingressProxyGroups.Len())) gaugeAPIServerProxyGroupResources.Set(int64(r.apiServerProxyGroups.Len())) + delete(r.authKeyRateLimits, pg.Name) } -func pgTailscaledConfig(pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass, idx int32, authKey *string, staticEndpoints []netip.AddrPort, oldAdvertiseServices []string, loginServer string) (tailscaledConfigs, error) { +func pgTailscaledConfig(pg *tsapi.ProxyGroup, loginServer string, pc *tsapi.ProxyClass, idx int32, authKey *string, staticEndpoints []netip.AddrPort, oldAdvertiseServices []string) (tailscaledConfigs, error) { conf := &ipn.ConfigVAlpha{ Version: "alpha0", AcceptDNS: "false", @@ -1083,14 +1219,14 @@ func extractAdvertiseServicesConfig(cfgSecret *corev1.Secret) ([]string, error) // some pods have failed to write state. // // The returned metadata will contain an entry for each state Secret that exists. -func (r *ProxyGroupReconciler) getNodeMetadata(ctx context.Context, pg *tsapi.ProxyGroup) (metadata []nodeMetadata, _ error) { +func getNodeMetadata(ctx context.Context, pg *tsapi.ProxyGroup, cl client.Client, tsNamespace string) (metadata []nodeMetadata, _ error) { // List all state Secrets owned by this ProxyGroup. secrets := &corev1.SecretList{} - if err := r.List(ctx, secrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pg.Name, kubetypes.LabelSecretTypeState))); err != nil { + if err := cl.List(ctx, secrets, client.InNamespace(tsNamespace), client.MatchingLabels(pgSecretLabels(pg.Name, kubetypes.LabelSecretTypeState))); err != nil { return nil, fmt.Errorf("failed to list state Secrets: %w", err) } for _, secret := range secrets.Items { - var ordinal int + var ordinal int32 if _, err := fmt.Sscanf(secret.Name, pg.Name+"-%d", &ordinal); err != nil { return nil, fmt.Errorf("unexpected secret %s was labelled as owned by the ProxyGroup %s: %w", secret.Name, pg.Name, err) } @@ -1110,7 +1246,7 @@ func (r *ProxyGroupReconciler) getNodeMetadata(ctx context.Context, pg *tsapi.Pr } pod := &corev1.Pod{} - if err := r.Get(ctx, client.ObjectKey{Namespace: r.tsNamespace, Name: fmt.Sprintf("%s-%d", pg.Name, ordinal)}, pod); err != nil && !apierrors.IsNotFound(err) { + if err := cl.Get(ctx, client.ObjectKey{Namespace: tsNamespace, Name: fmt.Sprintf("%s-%d", pg.Name, ordinal)}, pod); err != nil && !apierrors.IsNotFound(err) { return nil, err } else if err == nil { nm.podUID = string(pod.UID) @@ -1129,7 +1265,7 @@ func (r *ProxyGroupReconciler) getNodeMetadata(ctx context.Context, pg *tsapi.Pr // getRunningProxies will return status for all proxy Pods whose state Secret // has an up to date Pod UID and at least a hostname. func (r *ProxyGroupReconciler) getRunningProxies(ctx context.Context, pg *tsapi.ProxyGroup, staticEndpoints map[string][]netip.AddrPort) (devices []tsapi.TailnetDevice, _ error) { - metadata, err := r.getNodeMetadata(ctx, pg) + metadata, err := getNodeMetadata(ctx, pg, r.Client, r.tsNamespace) if err != nil { return nil, err } @@ -1173,8 +1309,31 @@ func (r *ProxyGroupReconciler) getRunningProxies(ctx context.Context, pg *tsapi. return devices, nil } +// getClientAndLoginURL returns the appropriate Tailscale client and resolved login URL +// for the given tailnet name. If no tailnet is specified, returns the default client +// and login server. Applies fallback to the operator's login server if the tailnet +// doesn't specify a custom login URL. +func (r *ProxyGroupReconciler) getClientAndLoginURL(ctx context.Context, tailnetName string) (tsClient, + string, error) { + if tailnetName == "" { + return r.tsClient, r.loginServer, nil + } + + tc, loginUrl, err := clientForTailnet(ctx, r.Client, r.tsNamespace, tailnetName) + if err != nil { + return nil, "", err + } + + // Apply fallback if tailnet doesn't specify custom login URL + if loginUrl == "" { + loginUrl = r.loginServer + } + + return tc, loginUrl, nil +} + type nodeMetadata struct { - ordinal int + ordinal int32 stateSecret *corev1.Secret podUID string // or empty if the Pod no longer exists. tsID tailcfg.StableNodeID diff --git a/cmd/k8s-operator/proxygroup_specs.go b/cmd/k8s-operator/proxygroup_specs.go index 930b7049d8ea9..05e0ed0b26013 100644 --- a/cmd/k8s-operator/proxygroup_specs.go +++ b/cmd/k8s-operator/proxygroup_specs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -173,6 +173,10 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode string Name: "TS_KUBE_SECRET", Value: "$(POD_NAME)", }, + { + Name: "TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", + Value: "false", + }, { // TODO(tomhjp): This is tsrecorder-specific and does nothing. Delete. Name: "TS_STATE", diff --git a/cmd/k8s-operator/proxygroup_test.go b/cmd/k8s-operator/proxygroup_test.go index 2bcc9fb7a9720..b27f5e67aa043 100644 --- a/cmd/k8s-operator/proxygroup_test.go +++ b/cmd/k8s-operator/proxygroup_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -6,15 +6,19 @@ package main import ( + "context" "encoding/json" "fmt" "net/netip" + "reflect" "slices" + "strings" "testing" "time" "github.com/google/go-cmp/cmp" "go.uber.org/zap" + "golang.org/x/time/rate" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -28,7 +32,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "tailscale.com/client/tailscale" "tailscale.com/ipn" - kube "tailscale.com/k8s-operator" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/k8s-proxy/conf" @@ -638,10 +641,12 @@ func TestProxyGroupWithStaticEndpoints(t *testing.T) { tsFirewallMode: "auto", defaultProxyClass: "default-pc", - Client: fc, - tsClient: tsClient, - recorder: fr, - clock: cl, + Client: fc, + tsClient: tsClient, + recorder: fr, + clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } for i, r := range tt.reconciles { @@ -781,11 +786,13 @@ func TestProxyGroupWithStaticEndpoints(t *testing.T) { tsFirewallMode: "auto", defaultProxyClass: "default-pc", - Client: fc, - tsClient: tsClient, - recorder: fr, - log: zl.Sugar().With("TestName", tt.name).With("Reconcile", "cleanup"), - clock: cl, + Client: fc, + tsClient: tsClient, + recorder: fr, + log: zl.Sugar().With("TestName", tt.name).With("Reconcile", "cleanup"), + clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } if err := fc.Delete(t.Context(), pg); err != nil { @@ -842,12 +849,15 @@ func TestProxyGroup(t *testing.T) { tsFirewallMode: "auto", defaultProxyClass: "default-pc", - Client: fc, - tsClient: tsClient, - recorder: fr, - log: zl.Sugar(), - clock: cl, + Client: fc, + tsClient: tsClient, + recorder: fr, + log: zl.Sugar(), + clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } + crd := &apiextensionsv1.CustomResourceDefinition{ObjectMeta: metav1.ObjectMeta{Name: serviceMonitorCRD}} opts := configOpts{ proxyType: "proxygroup", @@ -864,7 +874,7 @@ func TestProxyGroup(t *testing.T) { tsoperator.SetProxyGroupCondition(pg, tsapi.ProxyGroupReady, metav1.ConditionFalse, reasonProxyGroupCreating, "the ProxyGroup's ProxyClass \"default-pc\" is not yet in a ready state, waiting...", 1, cl, zl.Sugar()) expectEqual(t, fc, pg) expectProxyGroupResources(t, fc, pg, false, pc) - if kube.ProxyGroupAvailable(pg) { + if tsoperator.ProxyGroupAvailable(pg) { t.Fatal("expected ProxyGroup to not be available") } }) @@ -892,7 +902,7 @@ func TestProxyGroup(t *testing.T) { tsoperator.SetProxyGroupCondition(pg, tsapi.ProxyGroupAvailable, metav1.ConditionFalse, reasonProxyGroupCreating, "0/2 ProxyGroup pods running", 0, cl, zl.Sugar()) expectEqual(t, fc, pg) expectProxyGroupResources(t, fc, pg, true, pc) - if kube.ProxyGroupAvailable(pg) { + if tsoperator.ProxyGroupAvailable(pg) { t.Fatal("expected ProxyGroup to not be available") } if expected := 1; reconciler.egressProxyGroups.Len() != expected { @@ -936,7 +946,7 @@ func TestProxyGroup(t *testing.T) { tsoperator.SetProxyGroupCondition(pg, tsapi.ProxyGroupAvailable, metav1.ConditionTrue, reasonProxyGroupAvailable, "2/2 ProxyGroup pods running", 0, cl, zl.Sugar()) expectEqual(t, fc, pg) expectProxyGroupResources(t, fc, pg, true, pc) - if !kube.ProxyGroupAvailable(pg) { + if !tsoperator.ProxyGroupAvailable(pg) { t.Fatal("expected ProxyGroup to be available") } }) @@ -1046,12 +1056,14 @@ func TestProxyGroupTypes(t *testing.T) { zl, _ := zap.NewDevelopment() reconciler := &ProxyGroupReconciler{ - tsNamespace: tsNamespace, - tsProxyImage: testProxyImage, - Client: fc, - log: zl.Sugar(), - tsClient: &fakeTSClient{}, - clock: tstest.NewClock(tstest.ClockOpts{}), + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + Client: fc, + log: zl.Sugar(), + tsClient: &fakeTSClient{}, + clock: tstest.NewClock(tstest.ClockOpts{}), + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } t.Run("egress_type", func(t *testing.T) { @@ -1286,12 +1298,14 @@ func TestKubeAPIServerStatusConditionFlow(t *testing.T) { WithStatusSubresource(pg). Build() r := &ProxyGroupReconciler{ - tsNamespace: tsNamespace, - tsProxyImage: testProxyImage, - Client: fc, - log: zap.Must(zap.NewDevelopment()).Sugar(), - tsClient: &fakeTSClient{}, - clock: tstest.NewClock(tstest.ClockOpts{}), + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + Client: fc, + log: zap.Must(zap.NewDevelopment()).Sugar(), + tsClient: &fakeTSClient{}, + clock: tstest.NewClock(tstest.ClockOpts{}), + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } expectReconciled(t, r, "", pg.Name) @@ -1339,12 +1353,14 @@ func TestKubeAPIServerType_DoesNotOverwriteServicesConfig(t *testing.T) { Build() reconciler := &ProxyGroupReconciler{ - tsNamespace: tsNamespace, - tsProxyImage: testProxyImage, - Client: fc, - log: zap.Must(zap.NewDevelopment()).Sugar(), - tsClient: &fakeTSClient{}, - clock: tstest.NewClock(tstest.ClockOpts{}), + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + Client: fc, + log: zap.Must(zap.NewDevelopment()).Sugar(), + tsClient: &fakeTSClient{}, + clock: tstest.NewClock(tstest.ClockOpts{}), + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } pg := &tsapi.ProxyGroup{ @@ -1368,10 +1384,10 @@ func TestKubeAPIServerType_DoesNotOverwriteServicesConfig(t *testing.T) { cfg := conf.VersionedConfig{ Version: "v1alpha1", ConfigV1Alpha1: &conf.ConfigV1Alpha1{ - AuthKey: ptr.To("secret-authkey"), - State: ptr.To(fmt.Sprintf("kube:%s", pgPodName(pg.Name, 0))), - App: ptr.To(kubetypes.AppProxyGroupKubeAPIServer), - LogLevel: ptr.To("debug"), + AuthKey: new("new-authkey"), + State: new(fmt.Sprintf("kube:%s", pgPodName(pg.Name, 0))), + App: new(kubetypes.AppProxyGroupKubeAPIServer), + LogLevel: new("debug"), Hostname: ptr.To("test-k8s-apiserver-0"), APIServerProxy: &conf.APIServerProxyConfig{ @@ -1424,12 +1440,14 @@ func TestIngressAdvertiseServicesConfigPreserved(t *testing.T) { WithStatusSubresource(&tsapi.ProxyGroup{}). Build() reconciler := &ProxyGroupReconciler{ - tsNamespace: tsNamespace, - tsProxyImage: testProxyImage, - Client: fc, - log: zap.Must(zap.NewDevelopment()).Sugar(), - tsClient: &fakeTSClient{}, - clock: tstest.NewClock(tstest.ClockOpts{}), + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + Client: fc, + log: zap.Must(zap.NewDevelopment()).Sugar(), + tsClient: &fakeTSClient{}, + clock: tstest.NewClock(tstest.ClockOpts{}), + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } existingServices := []string{"svc1", "svc2"} @@ -1654,6 +1672,197 @@ func TestValidateProxyGroup(t *testing.T) { } } +func TestProxyGroupGetAuthKey(t *testing.T) { + pg := &tsapi.ProxyGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Finalizers: []string{"tailscale.com/finalizer"}, + }, + Spec: tsapi.ProxyGroupSpec{ + Type: tsapi.ProxyGroupTypeEgress, + Replicas: new(int32(1)), + }, + } + tsClient := &fakeTSClient{} + + // Variables to reference in test cases. + existingAuthKey := new("existing-auth-key") + newAuthKey := new("new-authkey") + configWith := func(authKey *string) map[string][]byte { + value := []byte("{}") + if authKey != nil { + value = fmt.Appendf(nil, `{"AuthKey": "%s"}`, *authKey) + } + return map[string][]byte{ + tsoperator.TailscaledConfigFileName(pgMinCapabilityVersion): value, + } + } + + initTest := func() (*ProxyGroupReconciler, client.WithWatch) { + fc := fake.NewClientBuilder(). + WithScheme(tsapi.GlobalScheme). + WithObjects(pg). + WithStatusSubresource(pg). + Build() + zl, _ := zap.NewDevelopment() + fr := record.NewFakeRecorder(1) + cl := tstest.NewClock(tstest.ClockOpts{}) + reconciler := &ProxyGroupReconciler{ + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + defaultTags: []string{"tag:test-tag"}, + tsFirewallMode: "auto", + + Client: fc, + tsClient: tsClient, + recorder: fr, + log: zl.Sugar(), + clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), + } + reconciler.ensureStateAddedForProxyGroup(pg) + + return reconciler, fc + } + + // Config Secret: exists or not, has key or not. + // State Secret: has device ID or not, requested reissue or not. + for name, tc := range map[string]struct { + configData map[string][]byte + stateData map[string][]byte + expectedAuthKey *string + expectReissue bool + }{ + "no_secrets_needs_new": { + expectedAuthKey: newAuthKey, // New ProxyGroup or manually cleared Pod. + }, + "no_config_secret_state_authed_ok": { + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + }, + expectedAuthKey: newAuthKey, // Always create an auth key if we're creating the config Secret. + }, + "config_secret_without_key_state_authed_with_reissue_needs_new": { + configData: configWith(nil), + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + kubetypes.KeyReissueAuthkey: []byte(""), + }, + expectedAuthKey: newAuthKey, + expectReissue: true, // Device is authed but reissue was requested. + }, + "config_secret_with_key_state_with_reissue_stale_ok": { + configData: configWith(existingAuthKey), + stateData: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte("some-older-authkey"), + }, + expectedAuthKey: existingAuthKey, // Config's auth key is different from the one marked for reissue. + }, + "config_secret_with_key_state_with_reissue_existing_key_needs_new": { + configData: configWith(existingAuthKey), + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + kubetypes.KeyReissueAuthkey: []byte(*existingAuthKey), + }, + expectedAuthKey: newAuthKey, + expectReissue: true, // Current config's auth key is marked for reissue. + }, + "config_secret_without_key_no_state_ok": { + configData: configWith(nil), + expectedAuthKey: nil, // Proxy will set reissue_authkey and then next reconcile will reissue. + }, + "config_secret_without_key_state_authed_ok": { + configData: configWith(nil), + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + }, + expectedAuthKey: nil, // Device is already authed. + }, + "config_secret_with_key_state_authed_ok": { + configData: configWith(existingAuthKey), + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + }, + expectedAuthKey: nil, // Auth key getting removed because device is authed. + }, + "config_secret_with_key_no_state_keeps_existing": { + configData: configWith(existingAuthKey), + expectedAuthKey: existingAuthKey, // No state, waiting for containerboot to try the auth key. + }, + } { + t.Run(name, func(t *testing.T) { + tsClient.deleted = tsClient.deleted[:0] // Reset deleted devices for each test case. + reconciler, fc := initTest() + var cfgSecret *corev1.Secret + if tc.configData != nil { + cfgSecret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pgConfigSecretName(pg.Name, 0), + Namespace: tsNamespace, + }, + Data: tc.configData, + } + } + if tc.stateData != nil { + mustCreate(t, fc, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pgStateSecretName(pg.Name, 0), + Namespace: tsNamespace, + }, + Data: tc.stateData, + }) + } + + authKey, err := reconciler.getAuthKey(t.Context(), tsClient, pg, cfgSecret, 0, reconciler.log.With("TestName", t.Name())) + if err != nil { + t.Fatalf("unexpected error getting auth key: %v", err) + } + if !reflect.DeepEqual(authKey, tc.expectedAuthKey) { + deref := func(s *string) string { + if s == nil { + return "" + } + return *s + } + t.Errorf("expected auth key %v, got %v", deref(tc.expectedAuthKey), deref(authKey)) + } + + // Use the device deletion as a proxy for the fact the new auth key + // was due to a reissue. + switch { + case tc.expectReissue && len(tsClient.deleted) != 1: + t.Errorf("expected 1 deleted device, got %v", tsClient.deleted) + case !tc.expectReissue && len(tsClient.deleted) != 0: + t.Errorf("expected no deleted devices, got %v", tsClient.deleted) + } + + if tc.expectReissue { + // Trigger the rate limit in a tight loop. Up to 100 iterations + // to allow for CI that is extremely slow, but should happen on + // first try for any reasonable machine. + stateSecretName := pgStateSecretName(pg.Name, 0) + for range 100 { + //NOTE: (ChaosInTheCRD) we added some protection here to avoid + // trying to reissue when already reissung. This overrides it. + reconciler.mu.Lock() + reconciler.authKeyReissuing[stateSecretName] = false + reconciler.mu.Unlock() + _, err := reconciler.getAuthKey(context.Background(), tsClient, pg, cfgSecret, 0, + reconciler.log.With("TestName", t.Name())) + if err != nil { + if !strings.Contains(err.Error(), "rate limit exceeded") { + t.Fatalf("unexpected error getting auth key: %v", err) + } + return // Expected rate limit error. + } + } + t.Fatal("expected rate limit error, but got none") + } + }) + } +} + func proxyClassesForLEStagingTest() (*tsapi.ProxyClass, *tsapi.ProxyClass, *tsapi.ProxyClass) { pcLEStaging := &tsapi.ProxyClass{ ObjectMeta: metav1.ObjectMeta{ @@ -1904,6 +2113,8 @@ func TestProxyGroupLetsEncryptStaging(t *testing.T) { tsClient: &fakeTSClient{}, log: zl.Sugar(), clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } expectReconciled(t, reconciler, "", pg.Name) diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index 2b6d1290e53f8..ea38ddece2749 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -107,6 +107,7 @@ const ( letsEncryptStagingEndpoint = "https://acme-staging-v02.api.letsencrypt.org/directory" mainContainerName = "tailscale" + operatorTailnet = "" ) var ( @@ -152,6 +153,9 @@ type tailscaleSTSConfig struct { // HostnamePrefix specifies the desired prefix for the device's hostname. The hostname will be suffixed with the // ordinal number generated by the StatefulSet. HostnamePrefix string + + // Tailnet specifies the Tailnet resource to use for producing auth keys. + Tailnet string } type connector struct { @@ -194,6 +198,11 @@ func IsHTTPSEnabledOnTailnet(tsnetServer tsnetServer) bool { // Provision ensures that the StatefulSet for the given service is running and // up to date. func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.SugaredLogger, sts *tailscaleSTSConfig) (*corev1.Service, error) { + tailscaleClient, loginUrl, err := a.getClientAndLoginURL(ctx, sts.Tailnet) + if err != nil { + return nil, fmt.Errorf("failed to get tailscale client and loginUrl: %w", err) + } + // Do full reconcile. // TODO (don't create Service for the Connector) hsvc, err := a.reconcileHeadlessService(ctx, logger, sts) @@ -213,7 +222,7 @@ func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.Suga } sts.ProxyClass = proxyClass - secretNames, err := a.provisionSecrets(ctx, logger, sts, hsvc) + secretNames, err := a.provisionSecrets(ctx, tailscaleClient, loginUrl, sts, hsvc, logger) if err != nil { return nil, fmt.Errorf("failed to create or get API key secret: %w", err) } @@ -234,10 +243,44 @@ func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.Suga return hsvc, nil } +// getClientAndLoginURL returns the appropriate Tailscale client and resolved login URL +// for the given tailnet name. If no tailnet is specified, returns the default client +// and login server. Applies fallback to the operator's login server if the tailnet +// doesn't specify a custom login URL. +func (a *tailscaleSTSReconciler) getClientAndLoginURL(ctx context.Context, tailnetName string) (tsClient, + string, error) { + if tailnetName == "" { + return a.tsClient, a.loginServer, nil + } + + tc, loginUrl, err := clientForTailnet(ctx, a.Client, a.operatorNamespace, tailnetName) + if err != nil { + return nil, "", err + } + + // Apply fallback if tailnet doesn't specify custom login URL + if loginUrl == "" { + loginUrl = a.loginServer + } + + return tc, loginUrl, nil +} + // Cleanup removes all resources associated that were created by Provision with // the given labels. It returns true when all resources have been removed, // otherwise it returns false and the caller should retry later. -func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, logger *zap.SugaredLogger, labels map[string]string, typ string) (done bool, _ error) { +func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, tailnet string, logger *zap.SugaredLogger, labels map[string]string, typ string) (done bool, _ error) { + tailscaleClient := a.tsClient + if tailnet != "" { + tc, _, err := clientForTailnet(ctx, a.Client, a.operatorNamespace, tailnet) + if err != nil { + logger.Errorf("failed to get tailscale client: %v", err) + return false, nil + } + + tailscaleClient = tc + } + // Need to delete the StatefulSet first, and delete it with foreground // cascading deletion. That way, the pod that's writing to the Secret will // stop running before we start looking at the Secret's contents, and @@ -279,7 +322,7 @@ func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, logger *zap.Sugare for _, dev := range devices { if dev.id != "" { logger.Debugf("deleting device %s from control", string(dev.id)) - if err = a.tsClient.DeleteDevice(ctx, string(dev.id)); err != nil { + if err = tailscaleClient.DeleteDevice(ctx, string(dev.id)); err != nil { errResp := &tailscale.ErrResponse{} if ok := errors.As(err, errResp); ok && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", string(dev.id)) @@ -360,7 +403,7 @@ func (a *tailscaleSTSReconciler) reconcileHeadlessService(ctx context.Context, l return createOrUpdate(ctx, a.Client, a.operatorNamespace, hsvc, func(svc *corev1.Service) { svc.Spec = hsvc.Spec }) } -func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, logger *zap.SugaredLogger, stsC *tailscaleSTSConfig, hsvc *corev1.Service) ([]string, error) { +func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, tailscaleClient tsClient, loginUrl string, stsC *tailscaleSTSConfig, hsvc *corev1.Service, logger *zap.SugaredLogger) ([]string, error) { secretNames := make([]string, stsC.Replicas) // Start by ensuring we have Secrets for the desired number of replicas. This will handle both creating and scaling @@ -403,13 +446,13 @@ func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, logger *z if len(tags) == 0 { tags = a.defaultTags } - authKey, err = newAuthKey(ctx, a.tsClient, tags) + authKey, err = newAuthKey(ctx, tailscaleClient, tags) if err != nil { return nil, err } } - configs, err := tailscaledConfig(stsC, authKey, orig, hostname) + configs, err := tailscaledConfig(stsC, loginUrl, authKey, orig, hostname) if err != nil { return nil, fmt.Errorf("error creating tailscaled config: %w", err) } @@ -477,7 +520,7 @@ func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, logger *z if dev != nil && dev.id != "" { var errResp *tailscale.ErrResponse - err = a.tsClient.DeleteDevice(ctx, string(dev.id)) + err = tailscaleClient.DeleteDevice(ctx, string(dev.id)) switch { case errors.As(err, &errResp) && errResp.Status == http.StatusNotFound: // This device has possibly already been deleted in the admin console. So we can ignore this @@ -667,6 +710,10 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S Name: "TS_KUBE_SECRET", Value: "$(POD_NAME)", }, + corev1.EnvVar{ + Name: "TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", + Value: "false", + }, corev1.EnvVar{ Name: "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR", Value: "/etc/tsconfig/$(POD_NAME)", @@ -1038,7 +1085,7 @@ func isMainContainer(c *corev1.Container) bool { // tailscaledConfig takes a proxy config, a newly generated auth key if generated and a Secret with the previous proxy // state and auth key and returns tailscaled config files for currently supported proxy versions. -func tailscaledConfig(stsC *tailscaleSTSConfig, newAuthkey string, oldSecret *corev1.Secret, hostname string) (tailscaledConfigs, error) { +func tailscaledConfig(stsC *tailscaleSTSConfig, loginUrl string, newAuthkey string, oldSecret *corev1.Secret, hostname string) (tailscaledConfigs, error) { conf := &ipn.ConfigVAlpha{ Version: "alpha0", AcceptDNS: "false", @@ -1069,7 +1116,7 @@ func tailscaledConfig(stsC *tailscaleSTSConfig, newAuthkey string, oldSecret *co if newAuthkey != "" { conf.AuthKey = &newAuthkey - } else if shouldRetainAuthKey(oldSecret) { + } else if !deviceAuthed(oldSecret) { key, err := authKeyFromSecret(oldSecret) if err != nil { return nil, fmt.Errorf("error retrieving auth key from Secret: %w", err) @@ -1077,6 +1124,10 @@ func tailscaledConfig(stsC *tailscaleSTSConfig, newAuthkey string, oldSecret *co conf.AuthKey = key } + if loginUrl != "" { + conf.ServerURL = new(loginUrl) + } + capVerConfigs := make(map[tailcfg.CapabilityVersion]ipn.ConfigVAlpha) capVerConfigs[107] = *conf @@ -1118,6 +1169,8 @@ func latestConfigFromSecret(s *corev1.Secret) (*ipn.ConfigVAlpha, error) { return conf, nil } +// authKeyFromSecret returns the auth key from the latest config version if +// found, or else nil. func authKeyFromSecret(s *corev1.Secret) (key *string, err error) { conf, err := latestConfigFromSecret(s) if err != nil { @@ -1134,13 +1187,13 @@ func authKeyFromSecret(s *corev1.Secret) (key *string, err error) { return key, nil } -// shouldRetainAuthKey returns true if the state stored in a proxy's state Secret suggests that auth key should be -// retained (because the proxy has not yet successfully authenticated). -func shouldRetainAuthKey(s *corev1.Secret) bool { +// deviceAuthed returns true if the state stored in a proxy's state Secret +// suggests that the proxy has successfully authenticated. +func deviceAuthed(s *corev1.Secret) bool { if s == nil { - return false // nothing to retain here + return false // No state Secret means no device state. } - return len(s.Data["device_id"]) == 0 // proxy has not authed yet + return len(s.Data["device_id"]) > 0 } func shouldAcceptRoutes(pc *tsapi.ProxyClass) bool { diff --git a/cmd/k8s-operator/sts_test.go b/cmd/k8s-operator/sts_test.go index afe54ed98bc49..81c0d25ec0ba4 100644 --- a/cmd/k8s-operator/sts_test.go +++ b/cmd/k8s-operator/sts_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/svc-for-pg.go b/cmd/k8s-operator/svc-for-pg.go index 144d3755811da..3e58db1b6cb0f 100644 --- a/cmd/k8s-operator/svc-for-pg.go +++ b/cmd/k8s-operator/svc-for-pg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -27,6 +27,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" tsoperator "tailscale.com/k8s-operator" @@ -41,13 +42,10 @@ import ( ) const ( - svcPGFinalizerName = "tailscale.com/service-pg-finalizer" - + svcPGFinalizerName = "tailscale.com/service-pg-finalizer" reasonIngressSvcInvalid = "IngressSvcInvalid" - reasonIngressSvcValid = "IngressSvcValid" reasonIngressSvcConfigured = "IngressSvcConfigured" reasonIngressSvcNoBackendsConfigured = "IngressSvcNoBackendsConfigured" - reasonIngressSvcCreationFailed = "IngressSvcCreationFailed" ) var gaugePGServiceResources = clientmetric.NewGauge(kubetypes.MetricServicePGResourceCount) @@ -61,7 +59,6 @@ type HAServiceReconciler struct { logger *zap.SugaredLogger tsClient tsClient tsNamespace string - lc localClient defaultTags []string operatorID string // stableID of the operator's Tailscale device @@ -100,12 +97,41 @@ func (r *HAServiceReconciler) Reconcile(ctx context.Context, req reconcile.Reque return res, fmt.Errorf("failed to get Service: %w", err) } + pgName := svc.Annotations[AnnotationProxyGroup] + if pgName == "" { + logger.Infof("[unexpected] no ProxyGroup annotation, skipping Tailscale Service provisioning") + return res, nil + } + + logger = logger.With("ProxyGroup", pgName) + + pg := &tsapi.ProxyGroup{} + err = r.Get(ctx, client.ObjectKey{Name: pgName}, pg) + switch { + case apierrors.IsNotFound(err): + logger.Infof("ProxyGroup %q does not exist, it may have been deleted. Reconciliation for service %q will be skipped until the ProxyGroup is found", pgName, svc.Name) + r.recorder.Event(svc, corev1.EventTypeWarning, "ProxyGroupNotFound", "ProxyGroup not found") + return res, nil + case err != nil: + return res, fmt.Errorf("getting ProxyGroup %q: %w", pgName, err) + } + + if !tsoperator.ProxyGroupAvailable(pg) { + logger.Infof("ProxyGroup is not (yet) ready") + return res, nil + } + + tailscaleClient, err := clientFromProxyGroup(ctx, r.Client, pg, r.tsNamespace, r.tsClient) + if err != nil { + return res, fmt.Errorf("failed to get tailscale client: %w", err) + } + hostname := nameForService(svc) logger = logger.With("hostname", hostname) if !svc.DeletionTimestamp.IsZero() || !r.isTailscaleService(svc) { logger.Debugf("Service is being deleted or is (no longer) referring to Tailscale ingress/egress, ensuring any created resources are cleaned up") - _, err = r.maybeCleanup(ctx, hostname, svc, logger) + _, err = r.maybeCleanup(ctx, hostname, svc, logger, tailscaleClient) return res, err } @@ -113,7 +139,7 @@ func (r *HAServiceReconciler) Reconcile(ctx context.Context, req reconcile.Reque // is the case, we reconcile the Ingress one more time to ensure that concurrent updates to the Tailscale Service in a // multi-cluster Ingress setup have not resulted in another actor overwriting our Tailscale Service update. needsRequeue := false - needsRequeue, err = r.maybeProvision(ctx, hostname, svc, logger) + needsRequeue, err = r.maybeProvision(ctx, hostname, svc, pg, logger, tailscaleClient) if err != nil { if strings.Contains(err.Error(), optimisticLockErrorMsg) { logger.Infof("optimistic lock error, retrying: %s", err) @@ -136,7 +162,7 @@ func (r *HAServiceReconciler) Reconcile(ctx context.Context, req reconcile.Reque // If a Tailscale Service exists, but does not have an owner reference from any operator, we error // out assuming that this is an owner reference created by an unknown actor. // Returns true if the operation resulted in a Tailscale Service update. -func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname string, svc *corev1.Service, logger *zap.SugaredLogger) (svcsChanged bool, err error) { +func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname string, svc *corev1.Service, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger, tsClient tsClient) (svcsChanged bool, err error) { oldSvcStatus := svc.Status.DeepCopy() defer func() { if !apiequality.Semantic.DeepEqual(oldSvcStatus, &svc.Status) { @@ -145,30 +171,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin } }() - pgName := svc.Annotations[AnnotationProxyGroup] - if pgName == "" { - logger.Infof("[unexpected] no ProxyGroup annotation, skipping Tailscale Service provisioning") - return false, nil - } - - logger = logger.With("ProxyGroup", pgName) - - pg := &tsapi.ProxyGroup{} - if err := r.Get(ctx, client.ObjectKey{Name: pgName}, pg); err != nil { - if apierrors.IsNotFound(err) { - msg := fmt.Sprintf("ProxyGroup %q does not exist", pgName) - logger.Warnf(msg) - r.recorder.Event(svc, corev1.EventTypeWarning, "ProxyGroupNotFound", msg) - return false, nil - } - return false, fmt.Errorf("getting ProxyGroup %q: %w", pgName, err) - } - if !tsoperator.ProxyGroupAvailable(pg) { - logger.Infof("ProxyGroup is not (yet) ready") - return false, nil - } - - if err := r.validateService(ctx, svc, pg); err != nil { + if err = r.validateService(ctx, svc, pg); err != nil { r.recorder.Event(svc, corev1.EventTypeWarning, reasonIngressSvcInvalid, err.Error()) tsoperator.SetServiceCondition(svc, tsapi.IngressSvcValid, metav1.ConditionFalse, reasonIngressSvcInvalid, err.Error(), r.clock, logger) return false, nil @@ -198,7 +201,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin // that in edge cases (a single update changed both hostname and removed // ProxyGroup annotation) the Tailscale Service is more likely to be // (eventually) removed. - svcsChanged, err = r.maybeCleanupProxyGroup(ctx, pgName, logger) + svcsChanged, err = r.maybeCleanupProxyGroup(ctx, pg.Name, logger, tsClient) if err != nil { return false, fmt.Errorf("failed to cleanup Tailscale Service resources for ProxyGroup: %w", err) } @@ -206,7 +209,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin // 2. Ensure that there isn't a Tailscale Service with the same hostname // already created and not owned by this Service. serviceName := tailcfg.ServiceName("svc:" + hostname) - existingTSSvc, err := r.tsClient.GetVIPService(ctx, serviceName) + existingTSSvc, err := tsClient.GetVIPService(ctx, serviceName) if err != nil && !isErrorTailscaleServiceNotFound(err) { return false, fmt.Errorf("error getting Tailscale Service %q: %w", hostname, err) } @@ -248,13 +251,13 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin !reflect.DeepEqual(tsSvc.Tags, existingTSSvc.Tags) || !ownersAreSetAndEqual(tsSvc, existingTSSvc) { logger.Infof("Ensuring Tailscale Service exists and is up to date") - if err := r.tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { + if err := tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { return false, fmt.Errorf("error creating Tailscale Service: %w", err) } existingTSSvc = tsSvc } - cm, cfgs, err := ingressSvcsConfigs(ctx, r.Client, pgName, r.tsNamespace) + cm, cfgs, err := ingressSvcsConfigs(ctx, r.Client, pg.Name, r.tsNamespace) if err != nil { return false, fmt.Errorf("error retrieving ingress services configuration: %w", err) } @@ -264,7 +267,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin } if existingTSSvc.Addrs == nil { - existingTSSvc, err = r.tsClient.GetVIPService(ctx, tsSvc.Name) + existingTSSvc, err = tsClient.GetVIPService(ctx, tsSvc.Name) if err != nil { return false, fmt.Errorf("error getting Tailscale Service: %w", err) } @@ -329,7 +332,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin return false, fmt.Errorf("failed to update tailscaled config: %w", err) } - count, err := r.numberPodsAdvertising(ctx, pgName, serviceName) + count, err := r.numberPodsAdvertising(ctx, pg.Name, serviceName) if err != nil { return false, fmt.Errorf("failed to get number of advertised Pods: %w", err) } @@ -345,7 +348,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin conditionReason := reasonIngressSvcNoBackendsConfigured conditionMessage := fmt.Sprintf("%d/%d proxy backends ready and advertising", count, pgReplicas(pg)) if count != 0 { - dnsName, err := r.dnsNameForService(ctx, serviceName) + dnsName, err := dnsNameForService(ctx, r.Client, serviceName, pg, r.tsNamespace) if err != nil { return false, fmt.Errorf("error getting DNS name for Service: %w", err) } @@ -371,7 +374,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin // Service is being deleted or is unexposed. The cleanup is safe for a multi-cluster setup- the Tailscale Service is only // deleted if it does not contain any other owner references. If it does the cleanup only removes the owner reference // corresponding to this Service. -func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, svc *corev1.Service, logger *zap.SugaredLogger) (svcChanged bool, err error) { +func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, svc *corev1.Service, logger *zap.SugaredLogger, tsClient tsClient) (svcChanged bool, err error) { logger.Debugf("Ensuring any resources for Service are cleaned up") ix := slices.Index(svc.Finalizers, svcPGFinalizerName) if ix < 0 { @@ -389,7 +392,7 @@ func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, serviceName := tailcfg.ServiceName("svc:" + hostname) // 1. Clean up the Tailscale Service. - svcChanged, err = cleanupTailscaleService(ctx, r.tsClient, serviceName, r.operatorID, logger) + svcChanged, err = cleanupTailscaleService(ctx, tsClient, serviceName, r.operatorID, logger) if err != nil { return false, fmt.Errorf("error deleting Tailscale Service: %w", err) } @@ -422,14 +425,14 @@ func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, // Tailscale Services that are associated with the provided ProxyGroup and no longer managed this operator's instance are deleted, if not owned by other operator instances, else the owner reference is cleaned up. // Returns true if the operation resulted in existing Tailscale Service updates (owner reference removal). -func (r *HAServiceReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyGroupName string, logger *zap.SugaredLogger) (svcsChanged bool, err error) { +func (r *HAServiceReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyGroupName string, logger *zap.SugaredLogger, tsClient tsClient) (svcsChanged bool, err error) { cm, config, err := ingressSvcsConfigs(ctx, r.Client, proxyGroupName, r.tsNamespace) if err != nil { return false, fmt.Errorf("failed to get ingress service config: %s", err) } svcList := &corev1.ServiceList{} - if err := r.Client.List(ctx, svcList, client.MatchingFields{indexIngressProxyGroup: proxyGroupName}); err != nil { + if err = r.Client.List(ctx, svcList, client.MatchingFields{indexIngressProxyGroup: proxyGroupName}); err != nil { return false, fmt.Errorf("failed to find Services for ProxyGroup %q: %w", proxyGroupName, err) } @@ -450,7 +453,7 @@ func (r *HAServiceReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG return false, fmt.Errorf("failed to update tailscaled config services: %w", err) } - svcsChanged, err = cleanupTailscaleService(ctx, r.tsClient, tailcfg.ServiceName(tsSvcName), r.operatorID, logger) + svcsChanged, err = cleanupTailscaleService(ctx, tsClient, tailcfg.ServiceName(tsSvcName), r.operatorID, logger) if err != nil { return false, fmt.Errorf("deleting Tailscale Service %q: %w", tsSvcName, err) } @@ -510,15 +513,6 @@ func (r *HAServiceReconciler) shouldExposeClusterIP(svc *corev1.Service) bool { return isTailscaleLoadBalancerService(svc, r.isDefaultLoadBalancer) || hasExposeAnnotation(svc) } -// tailnetCertDomain returns the base domain (TCD) of the current tailnet. -func (r *HAServiceReconciler) tailnetCertDomain(ctx context.Context) (string, error) { - st, err := r.lc.StatusWithoutPeers(ctx) - if err != nil { - return "", fmt.Errorf("error getting tailscale status: %w", err) - } - return st.CurrentTailnet.MagicDNSSuffix, nil -} - // cleanupTailscaleService deletes any Tailscale Service by the provided name if it is not owned by operator instances other than this one. // If a Tailscale Service is found, but contains other owner references, only removes this operator's owner reference. // If a Tailscale Service by the given name is not found or does not contain this operator's owner reference, do nothing. @@ -571,10 +565,10 @@ func cleanupTailscaleService(ctx context.Context, tsClient tsClient, name tailcf return true, tsClient.CreateOrUpdateVIPService(ctx, svc) } -func (a *HAServiceReconciler) backendRoutesSetup(ctx context.Context, serviceName, replicaName, pgName string, wantsCfg *ingressservices.Config, logger *zap.SugaredLogger) (bool, error) { +func (r *HAServiceReconciler) backendRoutesSetup(ctx context.Context, serviceName, replicaName string, wantsCfg *ingressservices.Config, logger *zap.SugaredLogger) (bool, error) { logger.Debugf("checking backend routes for service '%s'", serviceName) pod := &corev1.Pod{} - err := a.Get(ctx, client.ObjectKey{Namespace: a.tsNamespace, Name: replicaName}, pod) + err := r.Get(ctx, client.ObjectKey{Namespace: r.tsNamespace, Name: replicaName}, pod) if apierrors.IsNotFound(err) { logger.Debugf("Pod %q not found", replicaName) return false, nil @@ -583,7 +577,7 @@ func (a *HAServiceReconciler) backendRoutesSetup(ctx context.Context, serviceNam return false, fmt.Errorf("failed to get Pod: %w", err) } secret := &corev1.Secret{} - err = a.Get(ctx, client.ObjectKey{Namespace: a.tsNamespace, Name: replicaName}, secret) + err = r.Get(ctx, client.ObjectKey{Namespace: r.tsNamespace, Name: replicaName}, secret) if apierrors.IsNotFound(err) { logger.Debugf("Secret %q not found", replicaName) return false, nil @@ -638,17 +632,17 @@ func isCurrentStatus(gotCfgs ingressservices.Status, pod *corev1.Pod, logger *za return true, nil } -func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Context, svc *corev1.Service, pgName string, serviceName tailcfg.ServiceName, cfg *ingressservices.Config, shouldBeAdvertised bool, logger *zap.SugaredLogger) (err error) { +func (r *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Context, svc *corev1.Service, pgName string, serviceName tailcfg.ServiceName, cfg *ingressservices.Config, shouldBeAdvertised bool, logger *zap.SugaredLogger) (err error) { logger.Debugf("checking advertisement for service '%s'", serviceName) // Get all config Secrets for this ProxyGroup. // Get all Pods secrets := &corev1.SecretList{} - if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeConfig))); err != nil { + if err := r.List(ctx, secrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeConfig))); err != nil { return fmt.Errorf("failed to list config Secrets: %w", err) } if svc != nil && shouldBeAdvertised { - shouldBeAdvertised, err = a.checkEndpointsReady(ctx, svc, logger) + shouldBeAdvertised, err = r.checkEndpointsReady(ctx, svc, logger) if err != nil { return fmt.Errorf("failed to check readiness of Service '%s' endpoints: %w", svc.Name, err) } @@ -680,7 +674,7 @@ func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con logger.Infof("[unexpected] unable to determine replica name from config Secret name %q, unable to determine if backend routing has been configured", secret.Name) return nil } - ready, err := a.backendRoutesSetup(ctx, serviceName.String(), replicaName, pgName, cfg, logger) + ready, err := r.backendRoutesSetup(ctx, serviceName.String(), replicaName, cfg, logger) if err != nil { return fmt.Errorf("error checking backend routes: %w", err) } @@ -699,7 +693,7 @@ func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con updated = true } if updated { - if err := a.Update(ctx, &secret); err != nil { + if err := r.Update(ctx, &secret); err != nil { return fmt.Errorf("error updating ProxyGroup config Secret: %w", err) } } @@ -707,10 +701,10 @@ func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con return nil } -func (a *HAServiceReconciler) numberPodsAdvertising(ctx context.Context, pgName string, serviceName tailcfg.ServiceName) (int, error) { +func (r *HAServiceReconciler) numberPodsAdvertising(ctx context.Context, pgName string, serviceName tailcfg.ServiceName) (int, error) { // Get all state Secrets for this ProxyGroup. secrets := &corev1.SecretList{} - if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeState))); err != nil { + if err := r.List(ctx, secrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeState))); err != nil { return 0, fmt.Errorf("failed to list ProxyGroup %q state Secrets: %w", pgName, err) } @@ -732,12 +726,28 @@ func (a *HAServiceReconciler) numberPodsAdvertising(ctx context.Context, pgName } // dnsNameForService returns the DNS name for the given Tailscale Service name. -func (r *HAServiceReconciler) dnsNameForService(ctx context.Context, svc tailcfg.ServiceName) (string, error) { +func dnsNameForService(ctx context.Context, cl client.Client, svc tailcfg.ServiceName, pg *tsapi.ProxyGroup, namespace string) (string, error) { s := svc.WithoutPrefix() - tcd, err := r.tailnetCertDomain(ctx) - if err != nil { - return "", fmt.Errorf("error determining DNS name base: %w", err) + + md, err := getNodeMetadata(ctx, pg, cl, namespace) + switch { + case err != nil: + return "", fmt.Errorf("error getting node metadata: %w", err) + case len(md) == 0: + return "", fmt.Errorf("failed to find node metadata for ProxyGroup %q", pg.Name) + } + + // To determine the appropriate magic DNS name we take the first dns name we can find that is not empty and + // contains a period. + idx := slices.IndexFunc(md, func(metadata nodeMetadata) bool { + return metadata.dnsName != "" && strings.ContainsRune(metadata.dnsName, '.') + }) + if idx == -1 { + return "", fmt.Errorf("failed to find dns name for ProxyGroup %q", pg.Name) } + + tcd := strings.SplitN(md[idx].dnsName, ".", 2)[1] + return s + "." + tcd, nil } diff --git a/cmd/k8s-operator/svc-for-pg_test.go b/cmd/k8s-operator/svc-for-pg_test.go index baaa07727df06..07a2393115330 100644 --- a/cmd/k8s-operator/svc-for-pg_test.go +++ b/cmd/k8s-operator/svc-for-pg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -22,7 +22,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "tailscale.com/ipn/ipnstate" + tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/ingressservices" @@ -195,14 +195,6 @@ func setupServiceTest(t *testing.T) (*HAServiceReconciler, *corev1.Secret, clien t.Fatal(err) } - lc := &fakeLocalClient{ - status: &ipnstate.Status{ - CurrentTailnet: &ipnstate.TailnetStatus{ - MagicDNSSuffix: "ts.net", - }, - }, - } - cl := tstest.NewClock(tstest.ClockOpts{}) svcPGR := &HAServiceReconciler{ Client: fc, @@ -212,7 +204,6 @@ func setupServiceTest(t *testing.T) (*HAServiceReconciler, *corev1.Secret, clien tsNamespace: "operator-ns", logger: zl.Sugar(), recorder: record.NewFakeRecorder(10), - lc: lc, } return svcPGR, pgStateSecret, fc, ft, cl @@ -280,15 +271,12 @@ func TestValidateService(t *testing.T) { func TestServicePGReconciler_MultiCluster(t *testing.T) { var ft *fakeTSClient - var lc localClient for i := 0; i <= 10; i++ { pgr, stateSecret, fc, fti, _ := setupServiceTest(t) if i == 0 { ft = fti - lc = pgr.lc } else { pgr.tsClient = ft - pgr.lc = lc } svc, _ := setupTestService(t, "test-multi-cluster", "", "4.3.2.1", fc, stateSecret) diff --git a/cmd/k8s-operator/svc.go b/cmd/k8s-operator/svc.go index 5c163e081f5a6..31be22aa12ca3 100644 --- a/cmd/k8s-operator/svc.go +++ b/cmd/k8s-operator/svc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -23,6 +23,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" @@ -167,7 +168,7 @@ func (a *ServiceReconciler) maybeCleanup(ctx context.Context, logger *zap.Sugare proxyTyp = proxyTypeIngressService } - if done, err := a.ssr.Cleanup(ctx, logger, childResourceLabels(svc.Name, svc.Namespace, "svc"), proxyTyp); err != nil { + if done, err := a.ssr.Cleanup(ctx, operatorTailnet, logger, childResourceLabels(svc.Name, svc.Namespace, "svc"), proxyTyp); err != nil { return fmt.Errorf("failed to cleanup: %w", err) } else if !done { logger.Debugf("cleanup not done yet, waiting for next reconcile") diff --git a/cmd/k8s-operator/tailnet.go b/cmd/k8s-operator/tailnet.go new file mode 100644 index 0000000000000..439489f750665 --- /dev/null +++ b/cmd/k8s-operator/tailnet.go @@ -0,0 +1,71 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package main + +import ( + "context" + "fmt" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "tailscale.com/internal/client/tailscale" + "tailscale.com/ipn" + operatorutils "tailscale.com/k8s-operator" + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" +) + +func clientForTailnet(ctx context.Context, cl client.Client, namespace, name string) (tsClient, string, error) { + var tn tsapi.Tailnet + if err := cl.Get(ctx, client.ObjectKey{Name: name}, &tn); err != nil { + return nil, "", fmt.Errorf("failed to get tailnet %q: %w", name, err) + } + + if !operatorutils.TailnetIsReady(&tn) { + return nil, "", fmt.Errorf("tailnet %q is not ready", name) + } + + var secret corev1.Secret + if err := cl.Get(ctx, client.ObjectKey{Name: tn.Spec.Credentials.SecretName, Namespace: namespace}, &secret); err != nil { + return nil, "", fmt.Errorf("failed to get Secret %q in namespace %q: %w", tn.Spec.Credentials.SecretName, namespace, err) + } + + baseURL := ipn.DefaultControlURL + if tn.Spec.LoginURL != "" { + baseURL = tn.Spec.LoginURL + } + + credentials := clientcredentials.Config{ + ClientID: string(secret.Data["client_id"]), + ClientSecret: string(secret.Data["client_secret"]), + TokenURL: baseURL + "/api/v2/oauth/token", + } + + source := credentials.TokenSource(ctx) + httpClient := oauth2.NewClient(ctx, source) + + ts := tailscale.NewClient(defaultTailnet, nil) + ts.UserAgent = "tailscale-k8s-operator" + ts.HTTPClient = httpClient + ts.BaseURL = baseURL + + return ts, baseURL, nil +} + +func clientFromProxyGroup(ctx context.Context, cl client.Client, pg *tsapi.ProxyGroup, namespace string, def tsClient) (tsClient, error) { + if pg.Spec.Tailnet == "" { + return def, nil + } + + tailscaleClient, _, err := clientForTailnet(ctx, cl, namespace, pg.Spec.Tailnet) + if err != nil { + return nil, err + } + + return tailscaleClient, nil +} diff --git a/cmd/k8s-operator/testutils_test.go b/cmd/k8s-operator/testutils_test.go index b0e2cfd734fad..8e055e0dd164e 100644 --- a/cmd/k8s-operator/testutils_test.go +++ b/cmd/k8s-operator/testutils_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -30,9 +30,9 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" - "tailscale.com/ipn/ipnstate" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" "tailscale.com/tailcfg" @@ -91,6 +91,7 @@ func expectedSTS(t *testing.T, cl client.Client, opts configOpts) *appsv1.Statef {Name: "POD_NAME", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "", FieldPath: "metadata.name"}, ResourceFieldRef: nil, ConfigMapKeyRef: nil, SecretKeyRef: nil}}, {Name: "POD_UID", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "", FieldPath: "metadata.uid"}, ResourceFieldRef: nil, ConfigMapKeyRef: nil, SecretKeyRef: nil}}, {Name: "TS_KUBE_SECRET", Value: "$(POD_NAME)"}, + {Name: "TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", Value: "false"}, {Name: "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR", Value: "/etc/tsconfig/$(POD_NAME)"}, {Name: "TS_DEBUG_ACME_FORCE_RENEWAL", Value: "true"}, }, @@ -287,6 +288,7 @@ func expectedSTSUserspace(t *testing.T, cl client.Client, opts configOpts) *apps {Name: "POD_NAME", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "", FieldPath: "metadata.name"}, ResourceFieldRef: nil, ConfigMapKeyRef: nil, SecretKeyRef: nil}}, {Name: "POD_UID", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "", FieldPath: "metadata.uid"}, ResourceFieldRef: nil, ConfigMapKeyRef: nil, SecretKeyRef: nil}}, {Name: "TS_KUBE_SECRET", Value: "$(POD_NAME)"}, + {Name: "TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", Value: "false"}, {Name: "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR", Value: "/etc/tsconfig/$(POD_NAME)"}, {Name: "TS_DEBUG_ACME_FORCE_RENEWAL", Value: "true"}, {Name: "TS_SERVE_CONFIG", Value: "/etc/tailscaled/$(POD_NAME)/serve-config"}, @@ -527,7 +529,7 @@ func expectedSecret(t *testing.T, cl client.Client, opts configOpts) *corev1.Sec AcceptDNS: "false", Hostname: &opts.hostname, Locked: "false", - AuthKey: ptr.To("secret-authkey"), + AuthKey: new("new-authkey"), AcceptRoutes: "false", AppConnector: &ipn.AppConnectorPrefs{Advertise: false}, NoStatefulFiltering: "true", @@ -860,7 +862,7 @@ func (c *fakeTSClient) CreateKey(ctx context.Context, caps tailscale.KeyCapabili Created: time.Now(), Capabilities: caps, } - return "secret-authkey", k, nil + return "new-authkey", k, nil } func (c *fakeTSClient) Device(ctx context.Context, deviceID string, fields *tailscale.DeviceFieldsOpts) (*tailscale.Device, error) { @@ -986,18 +988,3 @@ func (c *fakeTSClient) DeleteVIPService(ctx context.Context, name tailcfg.Servic } return nil } - -type fakeLocalClient struct { - status *ipnstate.Status -} - -func (f *fakeLocalClient) StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) { - if f.status == nil { - return &ipnstate.Status{ - Self: &ipnstate.PeerStatus{ - DNSName: "test-node.test.ts.net.", - }, - }, nil - } - return f.status, nil -} diff --git a/cmd/k8s-operator/tsclient.go b/cmd/k8s-operator/tsclient.go index d22fa1797dd5c..063c2f768c6c6 100644 --- a/cmd/k8s-operator/tsclient.go +++ b/cmd/k8s-operator/tsclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsclient_test.go b/cmd/k8s-operator/tsclient_test.go index 16de512d5809f..c08705c78ed8b 100644 --- a/cmd/k8s-operator/tsclient_test.go +++ b/cmd/k8s-operator/tsclient_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsrecorder.go b/cmd/k8s-operator/tsrecorder.go index bfb01fa86de67..3857908f2bc1c 100644 --- a/cmd/k8s-operator/tsrecorder.go +++ b/cmd/k8s-operator/tsrecorder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -42,10 +42,11 @@ import ( ) const ( - reasonRecorderCreationFailed = "RecorderCreationFailed" - reasonRecorderCreating = "RecorderCreating" - reasonRecorderCreated = "RecorderCreated" - reasonRecorderInvalid = "RecorderInvalid" + reasonRecorderCreationFailed = "RecorderCreationFailed" + reasonRecorderCreating = "RecorderCreating" + reasonRecorderCreated = "RecorderCreated" + reasonRecorderInvalid = "RecorderInvalid" + reasonRecorderTailnetUnavailable = "RecorderTailnetUnavailable" currentProfileKey = "_current-profile" ) @@ -84,6 +85,25 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques } else if err != nil { return reconcile.Result{}, fmt.Errorf("failed to get tailscale.com Recorder: %w", err) } + + oldTSRStatus := tsr.Status.DeepCopy() + setStatusReady := func(tsr *tsapi.Recorder, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) { + tsoperator.SetRecorderCondition(tsr, tsapi.RecorderReady, status, reason, message, tsr.Generation, r.clock, logger) + if !apiequality.Semantic.DeepEqual(oldTSRStatus, &tsr.Status) { + // An error encountered here should get returned by the Reconcile function. + if updateErr := r.Client.Status().Update(ctx, tsr); updateErr != nil { + return reconcile.Result{}, errors.Join(err, updateErr) + } + } + + return reconcile.Result{}, nil + } + + tailscaleClient, loginUrl, err := r.getClientAndLoginURL(ctx, tsr.Spec.Tailnet) + if err != nil { + return setStatusReady(tsr, metav1.ConditionFalse, reasonRecorderTailnetUnavailable, err.Error()) + } + if markedForDeletion(tsr) { logger.Debugf("Recorder is being deleted, cleaning up resources") ix := xslices.Index(tsr.Finalizers, FinalizerName) @@ -92,7 +112,7 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return reconcile.Result{}, nil } - if done, err := r.maybeCleanup(ctx, tsr); err != nil { + if done, err := r.maybeCleanup(ctx, tsr, tailscaleClient); err != nil { return reconcile.Result{}, err } else if !done { logger.Debugf("Recorder resource cleanup not yet finished, will retry...") @@ -106,19 +126,6 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return reconcile.Result{}, nil } - oldTSRStatus := tsr.Status.DeepCopy() - setStatusReady := func(tsr *tsapi.Recorder, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) { - tsoperator.SetRecorderCondition(tsr, tsapi.RecorderReady, status, reason, message, tsr.Generation, r.clock, logger) - if !apiequality.Semantic.DeepEqual(oldTSRStatus, &tsr.Status) { - // An error encountered here should get returned by the Reconcile function. - if updateErr := r.Client.Status().Update(ctx, tsr); updateErr != nil { - return reconcile.Result{}, errors.Join(err, updateErr) - } - } - - return reconcile.Result{}, nil - } - if !slices.Contains(tsr.Finalizers, FinalizerName) { // This log line is printed exactly once during initial provisioning, // because once the finalizer is in place this block gets skipped. So, @@ -137,7 +144,7 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return setStatusReady(tsr, metav1.ConditionFalse, reasonRecorderInvalid, message) } - if err = r.maybeProvision(ctx, tsr); err != nil { + if err = r.maybeProvision(ctx, tailscaleClient, loginUrl, tsr); err != nil { reason := reasonRecorderCreationFailed message := fmt.Sprintf("failed creating Recorder: %s", err) if strings.Contains(err.Error(), optimisticLockErrorMsg) { @@ -155,7 +162,30 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return setStatusReady(tsr, metav1.ConditionTrue, reasonRecorderCreated, reasonRecorderCreated) } -func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Recorder) error { +// getClientAndLoginURL returns the appropriate Tailscale client and resolved login URL +// for the given tailnet name. If no tailnet is specified, returns the default client +// and login server. Applies fallback to the operator's login server if the tailnet +// doesn't specify a custom login URL. +func (r *RecorderReconciler) getClientAndLoginURL(ctx context.Context, tailnetName string) (tsClient, + string, error) { + if tailnetName == "" { + return r.tsClient, r.loginServer, nil + } + + tc, loginUrl, err := clientForTailnet(ctx, r.Client, r.tsNamespace, tailnetName) + if err != nil { + return nil, "", err + } + + // Apply fallback if tailnet doesn't specify custom login URL + if loginUrl == "" { + loginUrl = r.loginServer + } + + return tc, loginUrl, nil +} + +func (r *RecorderReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, loginUrl string, tsr *tsapi.Recorder) error { logger := r.logger(tsr.Name) r.mu.Lock() @@ -163,7 +193,7 @@ func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Reco gaugeRecorderResources.Set(int64(r.recorders.Len())) r.mu.Unlock() - if err := r.ensureAuthSecretsCreated(ctx, tsr); err != nil { + if err := r.ensureAuthSecretsCreated(ctx, tailscaleClient, tsr); err != nil { return fmt.Errorf("error creating secrets: %w", err) } @@ -222,7 +252,7 @@ func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Reco return fmt.Errorf("error creating RoleBinding: %w", err) } - ss := tsrStatefulSet(tsr, r.tsNamespace, r.loginServer) + ss := tsrStatefulSet(tsr, r.tsNamespace, loginUrl) _, err = createOrUpdate(ctx, r.Client, r.tsNamespace, ss, func(s *appsv1.StatefulSet) { s.ObjectMeta.Labels = ss.ObjectMeta.Labels s.ObjectMeta.Annotations = ss.ObjectMeta.Annotations @@ -241,13 +271,13 @@ func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Reco // If we have scaled the recorder down, we will have dangling state secrets // that we need to clean up. - if err = r.maybeCleanupSecrets(ctx, tsr); err != nil { + if err = r.maybeCleanupSecrets(ctx, tailscaleClient, tsr); err != nil { return fmt.Errorf("error cleaning up Secrets: %w", err) } var devices []tsapi.RecorderTailnetDevice for replica := range replicas { - dev, ok, err := r.getDeviceInfo(ctx, tsr.Name, replica) + dev, ok, err := r.getDeviceInfo(ctx, tailscaleClient, tsr.Name, replica) switch { case err != nil: return fmt.Errorf("failed to get device info: %w", err) @@ -312,7 +342,7 @@ func (r *RecorderReconciler) maybeCleanupServiceAccounts(ctx context.Context, ts return nil } -func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tsr *tsapi.Recorder) error { +func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tailscaleClient tsClient, tsr *tsapi.Recorder) error { options := []client.ListOption{ client.InNamespace(r.tsNamespace), client.MatchingLabels(tsrLabels("recorder", tsr.Name, nil)), @@ -354,7 +384,7 @@ func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tsr *tsapi var errResp *tailscale.ErrResponse r.log.Debugf("deleting device %s", devicePrefs.Config.NodeID) - err = r.tsClient.DeleteDevice(ctx, string(devicePrefs.Config.NodeID)) + err = tailscaleClient.DeleteDevice(ctx, string(devicePrefs.Config.NodeID)) switch { case errors.As(err, &errResp) && errResp.Status == http.StatusNotFound: // This device has possibly already been deleted in the admin console. So we can ignore this @@ -375,7 +405,7 @@ func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tsr *tsapi // maybeCleanup just deletes the device from the tailnet. All the kubernetes // resources linked to a Recorder will get cleaned up via owner references // (which we can use because they are all in the same namespace). -func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Recorder) (bool, error) { +func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Recorder, tailscaleClient tsClient) (bool, error) { logger := r.logger(tsr.Name) var replicas int32 = 1 @@ -399,7 +429,7 @@ func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Record nodeID := string(devicePrefs.Config.NodeID) logger.Debugf("deleting device %s from control", nodeID) - if err = r.tsClient.DeleteDevice(ctx, nodeID); err != nil { + if err = tailscaleClient.DeleteDevice(ctx, nodeID); err != nil { errResp := &tailscale.ErrResponse{} if errors.As(err, errResp) && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", nodeID) @@ -425,7 +455,7 @@ func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Record return true, nil } -func (r *RecorderReconciler) ensureAuthSecretsCreated(ctx context.Context, tsr *tsapi.Recorder) error { +func (r *RecorderReconciler) ensureAuthSecretsCreated(ctx context.Context, tailscaleClient tsClient, tsr *tsapi.Recorder) error { var replicas int32 = 1 if tsr.Spec.Replicas != nil { replicas = *tsr.Spec.Replicas @@ -453,7 +483,7 @@ func (r *RecorderReconciler) ensureAuthSecretsCreated(ctx context.Context, tsr * return fmt.Errorf("failed to get Secret %q: %w", key.Name, err) } - authKey, err := newAuthKey(ctx, r.tsClient, tags.Stringify()) + authKey, err := newAuthKey(ctx, tailscaleClient, tags.Stringify()) if err != nil { return err } @@ -555,7 +585,7 @@ func getDevicePrefs(secret *corev1.Secret) (prefs prefs, ok bool, err error) { return prefs, ok, nil } -func (r *RecorderReconciler) getDeviceInfo(ctx context.Context, tsrName string, replica int32) (d tsapi.RecorderTailnetDevice, ok bool, err error) { +func (r *RecorderReconciler) getDeviceInfo(ctx context.Context, tailscaleClient tsClient, tsrName string, replica int32) (d tsapi.RecorderTailnetDevice, ok bool, err error) { secret, err := r.getStateSecret(ctx, tsrName, replica) if err != nil || secret == nil { return tsapi.RecorderTailnetDevice{}, false, err @@ -569,7 +599,7 @@ func (r *RecorderReconciler) getDeviceInfo(ctx context.Context, tsrName string, // TODO(tomhjp): The profile info doesn't include addresses, which is why we // need the API. Should maybe update tsrecorder to write IPs to the state // Secret like containerboot does. - device, err := r.tsClient.Device(ctx, string(prefs.Config.NodeID), nil) + device, err := tailscaleClient.Device(ctx, string(prefs.Config.NodeID), nil) if err != nil { return tsapi.RecorderTailnetDevice{}, false, fmt.Errorf("failed to get device info from API: %w", err) } diff --git a/cmd/k8s-operator/tsrecorder_specs.go b/cmd/k8s-operator/tsrecorder_specs.go index b4a10f2962ae9..ab06c01f81b7d 100644 --- a/cmd/k8s-operator/tsrecorder_specs.go +++ b/cmd/k8s-operator/tsrecorder_specs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsrecorder_specs_test.go b/cmd/k8s-operator/tsrecorder_specs_test.go index 0d78129fc76b3..47997d1d31b0f 100644 --- a/cmd/k8s-operator/tsrecorder_specs_test.go +++ b/cmd/k8s-operator/tsrecorder_specs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsrecorder_test.go b/cmd/k8s-operator/tsrecorder_test.go index f7ff797b1ebba..5d315f8c52e93 100644 --- a/cmd/k8s-operator/tsrecorder_test.go +++ b/cmd/k8s-operator/tsrecorder_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -285,7 +285,7 @@ func expectRecorderResources(t *testing.T, fc client.WithWatch, tsr *tsapi.Recor } for replica := range replicas { - auth := tsrAuthSecret(tsr, tsNamespace, "secret-authkey", replica) + auth := tsrAuthSecret(tsr, tsNamespace, "new-authkey", replica) state := tsrStateSecret(tsr, tsNamespace, replica) if shouldExist { diff --git a/cmd/k8s-proxy/internal/config/config.go b/cmd/k8s-proxy/internal/config/config.go index 0f0bd1bfcf39d..91b4c54a5c32d 100644 --- a/cmd/k8s-proxy/internal/config/config.go +++ b/cmd/k8s-proxy/internal/config/config.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-proxy/internal/config/config_test.go b/cmd/k8s-proxy/internal/config/config_test.go index bcb1b9ebd14e6..ac6c6cf93f623 100644 --- a/cmd/k8s-proxy/internal/config/config_test.go +++ b/cmd/k8s-proxy/internal/config/config_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package config diff --git a/cmd/k8s-proxy/k8s-proxy.go b/cmd/k8s-proxy/k8s-proxy.go index 9b2bb67494659..38a86a5e0ade5 100644 --- a/cmd/k8s-proxy/k8s-proxy.go +++ b/cmd/k8s-proxy/k8s-proxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -50,6 +50,12 @@ import ( "tailscale.com/tsnet" ) +const ( + // proxyProtocolV2 enables PROXY protocol v2 to preserve original client + // connection info after TLS termination. + proxyProtocolV2 = 2 +) + func main() { encoderCfg := zap.NewProductionEncoderConfig() encoderCfg.EncodeTime = zapcore.RFC3339TimeEncoder @@ -441,24 +447,16 @@ func setServeConfig(ctx context.Context, lc *local.Client, cm *certs.CertManager if err != nil { return fmt.Errorf("error getting local client status: %w", err) } - serviceHostPort := ipn.HostPort(fmt.Sprintf("%s.%s:443", name.WithoutPrefix(), status.CurrentTailnet.MagicDNSSuffix)) + serviceSNI := fmt.Sprintf("%s.%s", name.WithoutPrefix(), status.CurrentTailnet.MagicDNSSuffix) serveConfig := ipn.ServeConfig{ - // Configure for the Service hostname. Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ name: { TCP: map[uint16]*ipn.TCPPortHandler{ 443: { - HTTPS: true, - }, - }, - Web: map[ipn.HostPort]*ipn.WebServerConfig{ - serviceHostPort: { - Handlers: map[string]*ipn.HTTPHandler{ - "/": { - Proxy: "http://localhost:80", - }, - }, + TCPForward: "localhost:80", + TerminateTLS: serviceSNI, + ProxyProtocol: proxyProtocolV2, }, }, }, diff --git a/cmd/mkmanifest/main.go b/cmd/mkmanifest/main.go index fb3c729f12d21..d08700341e7dc 100644 --- a/cmd/mkmanifest/main.go +++ b/cmd/mkmanifest/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The mkmanifest command is a simple helper utility to create a '.syso' file diff --git a/cmd/mkpkg/main.go b/cmd/mkpkg/main.go index 5e26b07f8f9f8..6f4de7e299b50 100644 --- a/cmd/mkpkg/main.go +++ b/cmd/mkpkg/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // mkpkg builds the Tailscale rpm and deb packages. diff --git a/cmd/mkversion/mkversion.go b/cmd/mkversion/mkversion.go index c8c8bf17930f6..ec9b0bb85ace4 100644 --- a/cmd/mkversion/mkversion.go +++ b/cmd/mkversion/mkversion.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // mkversion gets version info from git and outputs a bunch of shell variables diff --git a/cmd/nardump/nardump.go b/cmd/nardump/nardump.go index f8947b02b852c..c8db24cb6736d 100644 --- a/cmd/nardump/nardump.go +++ b/cmd/nardump/nardump.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // nardump is like nix-store --dump, but in Go, writing a NAR diff --git a/cmd/nardump/nardump_test.go b/cmd/nardump/nardump_test.go index 3b87e7962d638..c1ca825e1e288 100644 --- a/cmd/nardump/nardump_test.go +++ b/cmd/nardump/nardump_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/natc/ippool/consensusippool.go b/cmd/natc/ippool/consensusippool.go index bfa909b69a3b4..d595d3e7ddc7a 100644 --- a/cmd/natc/ippool/consensusippool.go +++ b/cmd/natc/ippool/consensusippool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/consensusippool_test.go b/cmd/natc/ippool/consensusippool_test.go index 242cdffaf26d3..fe42b2b223a8b 100644 --- a/cmd/natc/ippool/consensusippool_test.go +++ b/cmd/natc/ippool/consensusippool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/consensusippoolserialize.go b/cmd/natc/ippool/consensusippoolserialize.go index 97dc02f2c7d7c..be3312d300bad 100644 --- a/cmd/natc/ippool/consensusippoolserialize.go +++ b/cmd/natc/ippool/consensusippoolserialize.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/ippool.go b/cmd/natc/ippool/ippool.go index 5a2dcbec911e0..641702f5d31e8 100644 --- a/cmd/natc/ippool/ippool.go +++ b/cmd/natc/ippool/ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // ippool implements IP address storage, creation, and retrieval for cmd/natc diff --git a/cmd/natc/ippool/ippool_test.go b/cmd/natc/ippool/ippool_test.go index 8d474f86a97ed..405ec61564ed8 100644 --- a/cmd/natc/ippool/ippool_test.go +++ b/cmd/natc/ippool/ippool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/ipx.go b/cmd/natc/ippool/ipx.go index 8259a56dbf30e..4f52d6ede049a 100644 --- a/cmd/natc/ippool/ipx.go +++ b/cmd/natc/ippool/ipx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/ipx_test.go b/cmd/natc/ippool/ipx_test.go index 2e2b9d3d45baf..cb6889b683978 100644 --- a/cmd/natc/ippool/ipx_test.go +++ b/cmd/natc/ippool/ipx_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/natc.go b/cmd/natc/natc.go index a4f53d657d98e..11975b7d2e1a6 100644 --- a/cmd/natc/natc.go +++ b/cmd/natc/natc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The natc command is a work-in-progress implementation of a NAT based diff --git a/cmd/natc/natc_test.go b/cmd/natc/natc_test.go index c0a66deb8a4da..e1cc061234d0e 100644 --- a/cmd/natc/natc_test.go +++ b/cmd/natc/natc_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/netlogfmt/main.go b/cmd/netlogfmt/main.go index 0af52f862936c..212b36fb6b0ae 100644 --- a/cmd/netlogfmt/main.go +++ b/cmd/netlogfmt/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // netlogfmt parses a stream of JSON log messages from stdin and diff --git a/cmd/nginx-auth/nginx-auth.go b/cmd/nginx-auth/nginx-auth.go index 09da74da1d3c8..6b791eb6c35fa 100644 --- a/cmd/nginx-auth/nginx-auth.go +++ b/cmd/nginx-auth/nginx-auth.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/omitsize/omitsize.go b/cmd/omitsize/omitsize.go index 35e03d268e186..84863865991bc 100644 --- a/cmd/omitsize/omitsize.go +++ b/cmd/omitsize/omitsize.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The omitsize tool prints out how large the Tailscale binaries are with diff --git a/cmd/pgproxy/pgproxy.go b/cmd/pgproxy/pgproxy.go index e102c8ae47411..ded6fa695fe88 100644 --- a/cmd/pgproxy/pgproxy.go +++ b/cmd/pgproxy/pgproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The pgproxy server is a proxy for the Postgres wire protocol. diff --git a/cmd/printdep/printdep.go b/cmd/printdep/printdep.go index 044283209c08c..f5aeab7a561b6 100644 --- a/cmd/printdep/printdep.go +++ b/cmd/printdep/printdep.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The printdep command is a build system tool for printing out information @@ -19,6 +19,7 @@ var ( goToolchain = flag.Bool("go", false, "print the supported Go toolchain git hash (a github.com/tailscale/go commit)") goToolchainURL = flag.Bool("go-url", false, "print the URL to the tarball of the Tailscale Go toolchain") alpine = flag.Bool("alpine", false, "print the tag of alpine docker image") + next = flag.Bool("next", false, "if set, modifies --go or --go-url to use the upcoming/unreleased/rc Go release version instead") ) func main() { @@ -27,8 +28,12 @@ func main() { fmt.Println(strings.TrimSpace(ts.AlpineDockerTag)) return } + goRev := strings.TrimSpace(ts.GoToolchainRev) + if *next { + goRev = strings.TrimSpace(ts.GoToolchainNextRev) + } if *goToolchain { - fmt.Println(strings.TrimSpace(ts.GoToolchainRev)) + fmt.Println(goRev) } if *goToolchainURL { switch runtime.GOOS { @@ -36,6 +41,6 @@ func main() { default: log.Fatalf("unsupported GOOS %q", runtime.GOOS) } - fmt.Printf("https://github.com/tailscale/go/releases/download/build-%s/%s-%s.tar.gz\n", strings.TrimSpace(ts.GoToolchainRev), runtime.GOOS, runtime.GOARCH) + fmt.Printf("https://github.com/tailscale/go/releases/download/build-%s/%s-%s.tar.gz\n", goRev, runtime.GOOS, runtime.GOARCH) } } diff --git a/cmd/proxy-test-server/proxy-test-server.go b/cmd/proxy-test-server/proxy-test-server.go index 9f8c94a384ea5..2c705670446ba 100644 --- a/cmd/proxy-test-server/proxy-test-server.go +++ b/cmd/proxy-test-server/proxy-test-server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The proxy-test-server command is a simple HTTP proxy server for testing diff --git a/cmd/proxy-to-grafana/proxy-to-grafana.go b/cmd/proxy-to-grafana/proxy-to-grafana.go index 27f5e338c8d65..23f2640597d59 100644 --- a/cmd/proxy-to-grafana/proxy-to-grafana.go +++ b/cmd/proxy-to-grafana/proxy-to-grafana.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // proxy-to-grafana is a reverse proxy which identifies users based on their diff --git a/cmd/proxy-to-grafana/proxy-to-grafana_test.go b/cmd/proxy-to-grafana/proxy-to-grafana_test.go index 4831d54364943..be217043f12d3 100644 --- a/cmd/proxy-to-grafana/proxy-to-grafana_test.go +++ b/cmd/proxy-to-grafana/proxy-to-grafana_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/handlers.go b/cmd/sniproxy/handlers.go index 1973eecc017a3..157b9b75f885a 100644 --- a/cmd/sniproxy/handlers.go +++ b/cmd/sniproxy/handlers.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/handlers_test.go b/cmd/sniproxy/handlers_test.go index 4f9fc6a34b184..ad0637421cecc 100644 --- a/cmd/sniproxy/handlers_test.go +++ b/cmd/sniproxy/handlers_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/server.go b/cmd/sniproxy/server.go index b322b6f4b1137..0ff301fe92136 100644 --- a/cmd/sniproxy/server.go +++ b/cmd/sniproxy/server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/server_test.go b/cmd/sniproxy/server_test.go index d56f2aa754f85..8e06e8abedf8c 100644 --- a/cmd/sniproxy/server_test.go +++ b/cmd/sniproxy/server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/sniproxy.go b/cmd/sniproxy/sniproxy.go index 2115c8095b351..45503feca8718 100644 --- a/cmd/sniproxy/sniproxy.go +++ b/cmd/sniproxy/sniproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The sniproxy is an outbound SNI proxy. It receives TLS connections over diff --git a/cmd/sniproxy/sniproxy_test.go b/cmd/sniproxy/sniproxy_test.go index 65e059efaa1d4..a404799d29d7d 100644 --- a/cmd/sniproxy/sniproxy_test.go +++ b/cmd/sniproxy/sniproxy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/speedtest/speedtest.go b/cmd/speedtest/speedtest.go index 9a457ed6c7486..2cea97b1edef1 100644 --- a/cmd/speedtest/speedtest.go +++ b/cmd/speedtest/speedtest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Program speedtest provides the speedtest command. The reason to keep it separate from diff --git a/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go b/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go index 39af584ecd481..3c3ade3cd35a3 100644 --- a/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go +++ b/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // ssh-auth-none-demo is a demo SSH server that's meant to run on the diff --git a/cmd/stunc/stunc.go b/cmd/stunc/stunc.go index c4b2eedd39f90..e51cd15ba2248 100644 --- a/cmd/stunc/stunc.go +++ b/cmd/stunc/stunc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command stunc makes a STUN request to a STUN server and prints the result. diff --git a/cmd/stund/depaware.txt b/cmd/stund/depaware.txt index 7b945dd77ea79..d25974b2df424 100644 --- a/cmd/stund/depaware.txt +++ b/cmd/stund/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depaware) + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg 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/go-json-experiment/json from tailscale.com/types/opt+ @@ -100,7 +101,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar LD golang.org/x/sys/unix from github.com/prometheus/procfs+ W golang.org/x/sys/windows from github.com/prometheus/client_golang/prometheus vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -118,12 +119,12 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar bufio from compress/flate+ bytes from bufio+ cmp from slices+ - compress/flate from compress/gzip + compress/flate from compress/gzip+ compress/gzip from google.golang.org/protobuf/internal/impl+ container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -131,13 +132,14 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -152,7 +154,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -166,19 +168,21 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from net/http+ @@ -218,9 +222,8 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -233,14 +236,17 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from encoding/asn1 internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -280,7 +286,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar os/signal from tailscale.com/cmd/stund path from github.com/prometheus/client_golang/prometheus/internal+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from encoding/asn1+ regexp from github.com/prometheus/client_golang/prometheus/internal+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/cmd/stund/stund.go b/cmd/stund/stund.go index 1055d966f42c5..a27e520444464 100644 --- a/cmd/stund/stund.go +++ b/cmd/stund/stund.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The stund binary is a standalone STUN server. diff --git a/cmd/stunstamp/stunstamp.go b/cmd/stunstamp/stunstamp.go index 153dc9303bbb0..cfedd82bdd5cc 100644 --- a/cmd/stunstamp/stunstamp.go +++ b/cmd/stunstamp/stunstamp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The stunstamp binary measures round-trip latency with DERPs. diff --git a/cmd/stunstamp/stunstamp_default.go b/cmd/stunstamp/stunstamp_default.go index a244d9aea6410..3f6613cd060ee 100644 --- a/cmd/stunstamp/stunstamp_default.go +++ b/cmd/stunstamp/stunstamp_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/cmd/stunstamp/stunstamp_linux.go b/cmd/stunstamp/stunstamp_linux.go index 387805feff2f1..201e2f83b384c 100644 --- a/cmd/stunstamp/stunstamp_linux.go +++ b/cmd/stunstamp/stunstamp_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sync-containers/main.go b/cmd/sync-containers/main.go index 63efa54531b10..ab2a38bd66dab 100644 --- a/cmd/sync-containers/main.go +++ b/cmd/sync-containers/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/systray/systray.go b/cmd/systray/systray.go index d35595e258e0f..9dc35f1420bee 100644 --- a/cmd/systray/systray.go +++ b/cmd/systray/systray.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo || !darwin diff --git a/cmd/tailscale/cli/appcroutes.go b/cmd/tailscale/cli/appcroutes.go index 4a1ba87e35bcc..2ea001aec9c84 100644 --- a/cmd/tailscale/cli/appcroutes.go +++ b/cmd/tailscale/cli/appcroutes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/bugreport.go b/cmd/tailscale/cli/bugreport.go index 50e6ffd82bedc..3ffaffa8b1fa5 100644 --- a/cmd/tailscale/cli/bugreport.go +++ b/cmd/tailscale/cli/bugreport.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/cert.go b/cmd/tailscale/cli/cert.go index 171eebe1eafc9..6d78a8d8abf5f 100644 --- a/cmd/tailscale/cli/cert.go +++ b/cmd/tailscale/cli/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_acme @@ -108,8 +108,9 @@ func runCert(ctx context.Context, args []string) error { log.SetFlags(0) } if certArgs.certFile == "" && certArgs.keyFile == "" { - certArgs.certFile = domain + ".crt" - certArgs.keyFile = domain + ".key" + fileBase := strings.Replace(domain, "*.", "wildcard_.", 1) + certArgs.certFile = fileBase + ".crt" + certArgs.keyFile = fileBase + ".key" } certPEM, keyPEM, err := localClient.CertPairWithValidity(ctx, domain, certArgs.minValidity) if err != nil { diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 5ebc23a5befea..fda6b4546324a 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cli contains the cmd/tailscale CLI code in a package that can be included @@ -6,6 +6,7 @@ package cli import ( + "bytes" "context" "encoding/json" "errors" @@ -20,8 +21,6 @@ import ( "text/tabwriter" "time" - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/client/local" "tailscale.com/cmd/tailscale/cli/ffcomplete" @@ -218,6 +217,7 @@ var ( maybeFunnelCmd, maybeServeCmd, maybeCertCmd, + maybeUpdateCmd, _ func() *ffcli.Command ) @@ -269,7 +269,7 @@ change in the future. nilOrCall(maybeNetlockCmd), licensesCmd, exitNodeCmd(), - updateCmd, + nilOrCall(maybeUpdateCmd), whoisCmd, debugCmd(), nilOrCall(maybeDriveCmd), @@ -277,6 +277,7 @@ change in the future. configureHostCmd(), systrayCmd, appcRoutesCmd, + waitCmd, ), FlagSet: rootfs, Exec: func(ctx context.Context, args []string) error { @@ -294,6 +295,10 @@ change in the future. if w.UsageFunc == nil { w.UsageFunc = usageFunc } + if w.FlagSet != nil { + // If flags cannot be parsed, redact any keys in the error output . + w.FlagSet.SetOutput(sanitizeOutput(w.FlagSet.Output())) + } return true }) @@ -477,20 +482,6 @@ func countFlags(fs *flag.FlagSet) (n int) { return n } -// colorableOutput returns a colorable writer if stdout is a terminal (not, say, -// redirected to a file or pipe), the Stdout writer is os.Stdout (we're not -// embedding the CLI in wasm or a mobile app), and NO_COLOR is not set (see -// https://no-color.org/). If any of those is not the case, ok is false -// and w is Stdout. -func colorableOutput() (w io.Writer, ok bool) { - if Stdout != os.Stdout || - os.Getenv("NO_COLOR") != "" || - !isatty.IsTerminal(os.Stdout.Fd()) { - return Stdout, false - } - return colorable.NewColorableStdout(), true -} - type commandDoc struct { Name string Desc string @@ -566,3 +557,41 @@ func fixTailscaledConnectError(origErr error) error { } return origErr } + +func sanitizeOutput(w io.Writer) io.Writer { + return sanitizeWriter{w} +} + +type sanitizeWriter struct { + w io.Writer +} + +// Write logically replaces /tskey-[A-Za-z0-9-]+/ with /tskey-XXXX.../ in buf +// before writing to the underlying writer. +// +// We avoid the "regexp" package to not bloat the minbox build, and without +// making this a featuretag-omittable protection. +func (w sanitizeWriter) Write(buf []byte) (int, error) { + const prefix = "tskey-" + scrub := buf + for { + i := bytes.Index(scrub, []byte(prefix)) + if i == -1 { + break + } + scrub = scrub[i+len(prefix):] + + for i, b := range scrub { + if (b >= 'a' && b <= 'z') || + (b >= 'A' && b <= 'Z') || + (b >= '0' && b <= '9') || + b == '-' { + scrub[i] = 'X' + } else { + break + } + } + } + + return w.w.Write(buf) +} diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go index 8762b7aaeb905..537e641fc4160 100644 --- a/cmd/tailscale/cli/cli_test.go +++ b/cmd/tailscale/cli/cli_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli @@ -6,11 +6,14 @@ package cli import ( "bytes" stdcmp "cmp" + "context" "encoding/json" "flag" "fmt" "io" "net/netip" + "os" + "path/filepath" "reflect" "strings" "testing" @@ -20,6 +23,7 @@ import ( "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/envknob" "tailscale.com/health/healthmsg" + "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" @@ -1696,6 +1700,78 @@ func TestDocs(t *testing.T) { walk(t, root) } +func TestUpResolves(t *testing.T) { + const testARN = "arn:aws:ssm:us-east-1:123456789012:parameter/my-parameter" + undo := tailscale.HookResolveValueFromParameterStore.SetForTest(func(_ context.Context, valueOrARN string) (string, error) { + if valueOrARN == testARN { + return "resolved-value", nil + } + return valueOrARN, nil + }) + defer undo() + + const content = "file-content" + fpath := filepath.Join(t.TempDir(), "testfile") + if err := os.WriteFile(fpath, []byte(content), 0600); err != nil { + t.Fatal(err) + } + + testCases := []struct { + name string + arg string + want string + }{ + {"parameter_store", testARN, "resolved-value"}, + {"file", "file:" + fpath, "file-content"}, + } + + for _, tt := range testCases { + t.Run(tt.name+"_auth_key", func(t *testing.T) { + args := upArgsT{authKeyOrFile: tt.arg} + got, err := args.getAuthKey(t.Context()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + + t.Run(tt.name+"_client_secret", func(t *testing.T) { + args := upArgsT{clientSecretOrFile: tt.arg} + got, err := args.getClientSecret(t.Context()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + + t.Run(tt.name+"_id_token", func(t *testing.T) { + args := upArgsT{idTokenOrFile: tt.arg} + got, err := args.getIDToken(t.Context()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + } + + t.Run("passthrough", func(t *testing.T) { + args := upArgsT{authKeyOrFile: "tskey-abcd1234"} + got, err := args.getAuthKey(t.Context()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != "tskey-abcd1234" { + t.Errorf("got %q, want %q", got, "tskey-abcd1234") + } + }) +} + func TestDeps(t *testing.T) { deptest.DepChecker{ GOOS: "linux", @@ -1723,3 +1799,21 @@ func TestDepsNoCapture(t *testing.T) { }.Check(t) } + +func TestSanitizeWriter(t *testing.T) { + buf := new(bytes.Buffer) + w := sanitizeOutput(buf) + + in := []byte(`my auth key is tskey-auth-abc123-def456 and tskey-foo, what's yours?`) + want := []byte(`my auth key is tskey-XXXXXXXXXXXXXXXXXX and tskey-XXX, what's yours?`) + n, err := w.Write(in) + if err != nil { + t.Fatal(err) + } + if n != len(in) { + t.Errorf("unexpected write length %d, want %d", n, len(in)) + } + if got := buf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("unexpected sanitized content\ngot: %q\nwant: %q", got, want) + } +} diff --git a/cmd/tailscale/cli/colorable.go b/cmd/tailscale/cli/colorable.go new file mode 100644 index 0000000000000..6ecd36b1a409f --- /dev/null +++ b/cmd/tailscale/cli/colorable.go @@ -0,0 +1,28 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_colorable + +package cli + +import ( + "io" + "os" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// colorableOutput returns a colorable writer if stdout is a terminal (not, say, +// redirected to a file or pipe), the Stdout writer is os.Stdout (we're not +// embedding the CLI in wasm or a mobile app), and NO_COLOR is not set (see +// https://no-color.org/). If any of those is not the case, ok is false +// and w is Stdout. +func colorableOutput() (w io.Writer, ok bool) { + if Stdout != os.Stdout || + os.Getenv("NO_COLOR") != "" || + !isatty.IsTerminal(os.Stdout.Fd()) { + return Stdout, false + } + return colorable.NewColorableStdout(), true +} diff --git a/cmd/tailscale/cli/colorable_omit.go b/cmd/tailscale/cli/colorable_omit.go new file mode 100644 index 0000000000000..a821bdbbdc92e --- /dev/null +++ b/cmd/tailscale/cli/colorable_omit.go @@ -0,0 +1,12 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build ts_omit_colorable + +package cli + +import "io" + +func colorableOutput() (w io.Writer, ok bool) { + return Stdout, false +} diff --git a/cmd/tailscale/cli/configure-jetkvm.go b/cmd/tailscale/cli/configure-jetkvm.go index c80bf673605cf..1956ac836fe74 100644 --- a/cmd/tailscale/cli/configure-jetkvm.go +++ b/cmd/tailscale/cli/configure-jetkvm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && arm diff --git a/cmd/tailscale/cli/configure-kube.go b/cmd/tailscale/cli/configure-kube.go index bf5624856167a..3dcec250f01ef 100644 --- a/cmd/tailscale/cli/configure-kube.go +++ b/cmd/tailscale/cli/configure-kube.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_kube diff --git a/cmd/tailscale/cli/configure-kube_omit.go b/cmd/tailscale/cli/configure-kube_omit.go index 130f2870fab44..946fa2294d5aa 100644 --- a/cmd/tailscale/cli/configure-kube_omit.go +++ b/cmd/tailscale/cli/configure-kube_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_kube diff --git a/cmd/tailscale/cli/configure-kube_test.go b/cmd/tailscale/cli/configure-kube_test.go index 0c8b6b2b6cc0e..2df54d5751497 100644 --- a/cmd/tailscale/cli/configure-kube_test.go +++ b/cmd/tailscale/cli/configure-kube_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_kube diff --git a/cmd/tailscale/cli/configure-synology-cert.go b/cmd/tailscale/cli/configure-synology-cert.go index b5168ef92d11f..0f38f2df2941c 100644 --- a/cmd/tailscale/cli/configure-synology-cert.go +++ b/cmd/tailscale/cli/configure-synology-cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_acme && !ts_omit_synology diff --git a/cmd/tailscale/cli/configure-synology-cert_test.go b/cmd/tailscale/cli/configure-synology-cert_test.go index c7da5622fb629..08369c135f154 100644 --- a/cmd/tailscale/cli/configure-synology-cert_test.go +++ b/cmd/tailscale/cli/configure-synology-cert_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_acme diff --git a/cmd/tailscale/cli/configure-synology.go b/cmd/tailscale/cli/configure-synology.go index f0f05f75765b9..4cfd4160e066a 100644 --- a/cmd/tailscale/cli/configure-synology.go +++ b/cmd/tailscale/cli/configure-synology.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure.go b/cmd/tailscale/cli/configure.go index 20236eb28b5f5..e7a6448e70822 100644 --- a/cmd/tailscale/cli/configure.go +++ b/cmd/tailscale/cli/configure.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure_apple-all.go b/cmd/tailscale/cli/configure_apple-all.go index 5f0da9b95420e..95e9259e96cf7 100644 --- a/cmd/tailscale/cli/configure_apple-all.go +++ b/cmd/tailscale/cli/configure_apple-all.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure_apple.go b/cmd/tailscale/cli/configure_apple.go index c0d99b90aa2c4..465bc7a47ed2c 100644 --- a/cmd/tailscale/cli/configure_apple.go +++ b/cmd/tailscale/cli/configure_apple.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/cmd/tailscale/cli/configure_linux-all.go b/cmd/tailscale/cli/configure_linux-all.go index e645e9654dfe5..2db970eeef497 100644 --- a/cmd/tailscale/cli/configure_linux-all.go +++ b/cmd/tailscale/cli/configure_linux-all.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure_linux.go b/cmd/tailscale/cli/configure_linux.go index 4bbde872140ca..9ba3b8e878d52 100644 --- a/cmd/tailscale/cli/configure_linux.go +++ b/cmd/tailscale/cli/configure_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_systray @@ -33,7 +33,7 @@ func systrayConfigCmd() *ffcli.Command { FlagSet: (func() *flag.FlagSet { fs := newFlagSet("systray") fs.StringVar(&systrayArgs.initSystem, "enable-startup", "", - "Install startup script for init system. Currently supported systems are [systemd].") + "Install startup script for init system. Currently supported systems are [systemd, freedesktop].") return fs })(), } diff --git a/cmd/tailscale/cli/debug-capture.go b/cmd/tailscale/cli/debug-capture.go index a54066fa614cb..ce282b291a587 100644 --- a/cmd/tailscale/cli/debug-capture.go +++ b/cmd/tailscale/cli/debug-capture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_capture diff --git a/cmd/tailscale/cli/debug-peer-relay.go b/cmd/tailscale/cli/debug-peer-relay.go index bef8b83693aca..1b28c3f6bb1a4 100644 --- a/cmd/tailscale/cli/debug-peer-relay.go +++ b/cmd/tailscale/cli/debug-peer-relay.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_relayserver diff --git a/cmd/tailscale/cli/debug-portmap.go b/cmd/tailscale/cli/debug-portmap.go index d8db1442c7073..a876971ef00b4 100644 --- a/cmd/tailscale/cli/debug-portmap.go +++ b/cmd/tailscale/cli/debug-portmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_debugportmapper diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go index ccbfb59de9221..629c694c0c6b4 100644 --- a/cmd/tailscale/cli/debug.go +++ b/cmd/tailscale/cli/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli @@ -124,6 +124,12 @@ func debugCmd() *ffcli.Command { return fs })(), }, + { + Name: "daemon-bus-queues", + ShortUsage: "tailscale debug daemon-bus-queues", + Exec: runDaemonBusQueues, + ShortHelp: "Print event bus queue depths per client", + }, { Name: "metrics", ShortUsage: "tailscale debug metrics", @@ -840,6 +846,15 @@ func runDaemonBusGraph(ctx context.Context, args []string) error { return nil } +func runDaemonBusQueues(ctx context.Context, args []string) error { + data, err := localClient.EventBusQueues(ctx) + if err != nil { + return err + } + fmt.Print(string(data)) + return nil +} + // generateDOTGraph generates the DOT graph format based on the events func generateDOTGraph(topics []eventbus.DebugTopic) string { var sb strings.Builder diff --git a/cmd/tailscale/cli/diag.go b/cmd/tailscale/cli/diag.go index 3b2aa504b9ea7..8a244ba8817bb 100644 --- a/cmd/tailscale/cli/diag.go +++ b/cmd/tailscale/cli/diag.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux || windows || darwin) && !ts_omit_cliconndiag diff --git a/cmd/tailscale/cli/dns-query.go b/cmd/tailscale/cli/dns-query.go index 11f64453732fa..2993441b3d2fc 100644 --- a/cmd/tailscale/cli/dns-query.go +++ b/cmd/tailscale/cli/dns-query.go @@ -1,97 +1,169 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli import ( "context" + "encoding/json" + "errors" "flag" "fmt" "net/netip" - "os" "strings" "text/tabwriter" "github.com/peterbourgon/ff/v3/ffcli" "golang.org/x/net/dns/dnsmessage" - "tailscale.com/types/dnstype" + "tailscale.com/cmd/tailscale/cli/jsonoutput" ) +var dnsQueryArgs struct { + json bool +} + var dnsQueryCmd = &ffcli.Command{ Name: "query", - ShortUsage: "tailscale dns query [a|aaaa|cname|mx|ns|opt|ptr|srv|txt]", + ShortUsage: "tailscale dns query [--json] [type]", Exec: runDNSQuery, ShortHelp: "Perform a DNS query", LongHelp: strings.TrimSpace(` The 'tailscale dns query' subcommand performs a DNS query for the specified name using the internal DNS forwarder (100.100.100.100). -By default, the DNS query will request an A record. Another DNS record type can -be specified as the second parameter. +By default, the DNS query will request an A record. Specify the record type as +a second argument after the name (e.g. AAAA, CNAME, MX, NS, PTR, SRV, TXT). The output also provides information about the resolver(s) used to resolve the query. `), + FlagSet: (func() *flag.FlagSet { + fs := newFlagSet("query") + fs.BoolVar(&dnsQueryArgs.json, "json", false, "output in JSON format") + return fs + })(), } func runDNSQuery(ctx context.Context, args []string) error { - if len(args) < 1 { - return flag.ErrHelp + if len(args) == 0 { + return errors.New("missing required argument: name") + } + if len(args) > 1 { + var flags []string + for _, a := range args[1:] { + if strings.HasPrefix(a, "-") { + flags = append(flags, a) + } + } + if len(flags) > 0 { + return fmt.Errorf("unexpected flags after query name: %s; see 'tailscale dns query --help'", strings.Join(flags, ", ")) + } + if len(args) > 2 { + return fmt.Errorf("unexpected extra arguments: %s", strings.Join(args[2:], " ")) + } } name := args[0] queryType := "A" - if len(args) >= 2 { - queryType = args[1] + if len(args) > 1 { + queryType = strings.ToUpper(args[1]) } - fmt.Printf("DNS query for %q (%s) using internal resolver:\n", name, queryType) - fmt.Println() - bytes, resolvers, err := localClient.QueryDNS(ctx, name, queryType) + + rawBytes, resolvers, err := localClient.QueryDNS(ctx, name, queryType) if err != nil { - fmt.Printf("failed to query DNS: %v\n", err) - return nil + return fmt.Errorf("failed to query DNS: %w", err) } - if len(resolvers) == 1 { - fmt.Printf("Forwarding to resolver: %v\n", makeResolverString(*resolvers[0])) - } else { - fmt.Println("Multiple resolvers available:") - for _, r := range resolvers { - fmt.Printf(" - %v\n", makeResolverString(*r)) - } + data := &jsonoutput.DNSQueryResult{ + Name: name, + QueryType: queryType, + } + + for _, r := range resolvers { + data.Resolvers = append(data.Resolvers, makeDNSResolverInfo(r)) } - fmt.Println() + var p dnsmessage.Parser - header, err := p.Start(bytes) + header, err := p.Start(rawBytes) if err != nil { - fmt.Printf("failed to parse DNS response: %v\n", err) - return err + return fmt.Errorf("failed to parse DNS response: %w", err) } - fmt.Printf("Response code: %v\n", header.RCode.String()) - fmt.Println() + data.ResponseCode = header.RCode.String() + p.SkipAllQuestions() - if header.RCode != dnsmessage.RCodeSuccess { - fmt.Println("No answers were returned.") + + if header.RCode == dnsmessage.RCodeSuccess { + answers, err := p.AllAnswers() + if err != nil { + return fmt.Errorf("failed to parse DNS answers: %w", err) + } + data.Answers = make([]jsonoutput.DNSAnswer, 0, len(answers)) + for _, a := range answers { + data.Answers = append(data.Answers, jsonoutput.DNSAnswer{ + Name: a.Header.Name.String(), + TTL: a.Header.TTL, + Class: a.Header.Class.String(), + Type: a.Header.Type.String(), + Body: makeAnswerBody(a), + }) + } + } + + if dnsQueryArgs.json { + j, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + printf("%s\n", j) return nil } - answers, err := p.AllAnswers() - if err != nil { - fmt.Printf("failed to parse DNS answers: %v\n", err) - return err + printf("%s", formatDNSQueryText(data)) + return nil +} + +func formatDNSQueryText(data *jsonoutput.DNSQueryResult) string { + var sb strings.Builder + + fmt.Fprintf(&sb, "DNS query for %q (%s) using internal resolver:\n", data.Name, data.QueryType) + fmt.Fprintf(&sb, "\n") + if len(data.Resolvers) == 1 { + fmt.Fprintf(&sb, "Forwarding to resolver: %v\n", formatResolverString(data.Resolvers[0])) + } else { + fmt.Fprintf(&sb, "Multiple resolvers available:\n") + for _, r := range data.Resolvers { + fmt.Fprintf(&sb, " - %v\n", formatResolverString(r)) + } } - if len(answers) == 0 { - fmt.Println(" (no answers found)") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "Response code: %v\n", data.ResponseCode) + fmt.Fprintf(&sb, "\n") + + if data.Answers == nil { + fmt.Fprintf(&sb, "No answers were returned.\n") + return sb.String() } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + if len(data.Answers) == 0 { + fmt.Fprintf(&sb, " (no answers found)\n") + } + + w := tabwriter.NewWriter(&sb, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "Name\tTTL\tClass\tType\tBody") fmt.Fprintln(w, "----\t---\t-----\t----\t----") - for _, a := range answers { - fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", a.Header.Name.String(), a.Header.TTL, a.Header.Class.String(), a.Header.Type.String(), makeAnswerBody(a)) + for _, a := range data.Answers { + fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", a.Name, a.TTL, a.Class, a.Type, a.Body) } w.Flush() - fmt.Println() - return nil + fmt.Fprintf(&sb, "\n") + return sb.String() +} + +// formatResolverString formats a jsonoutput.DNSResolverInfo for human-readable text output. +func formatResolverString(r jsonoutput.DNSResolverInfo) string { + if len(r.BootstrapResolution) > 0 { + return fmt.Sprintf("%s (bootstrap: %v)", r.Addr, r.BootstrapResolution) + } + return r.Addr } // makeAnswerBody returns a string with the DNS answer body in a human-readable format. @@ -174,9 +246,3 @@ func makeTXTBody(txt dnsmessage.ResourceBody) string { } return "" } -func makeResolverString(r dnstype.Resolver) string { - if len(r.BootstrapResolution) > 0 { - return fmt.Sprintf("%s (bootstrap: %v)", r.Addr, r.BootstrapResolution) - } - return fmt.Sprintf("%s", r.Addr) -} diff --git a/cmd/tailscale/cli/dns-status.go b/cmd/tailscale/cli/dns-status.go index 8c18622ce45af..66a5e21d89700 100644 --- a/cmd/tailscale/cli/dns-status.go +++ b/cmd/tailscale/cli/dns-status.go @@ -1,10 +1,11 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli import ( "context" + "encoding/json" "flag" "fmt" "maps" @@ -12,13 +13,15 @@ import ( "strings" "github.com/peterbourgon/ff/v3/ffcli" + "tailscale.com/cmd/tailscale/cli/jsonoutput" "tailscale.com/ipn" + "tailscale.com/types/dnstype" "tailscale.com/types/netmap" ) var dnsStatusCmd = &ffcli.Command{ Name: "status", - ShortUsage: "tailscale dns status [--all]", + ShortUsage: "tailscale dns status [--all] [--json]", Exec: runDNSStatus, ShortHelp: "Print the current DNS status and configuration", LongHelp: strings.TrimSpace(` @@ -72,17 +75,30 @@ https://tailscale.com/kb/1054/dns. FlagSet: (func() *flag.FlagSet { fs := newFlagSet("status") fs.BoolVar(&dnsStatusArgs.all, "all", false, "outputs advanced debugging information") + fs.BoolVar(&dnsStatusArgs.json, "json", false, "output in JSON format") return fs })(), } // dnsStatusArgs are the arguments for the "dns status" subcommand. var dnsStatusArgs struct { - all bool + all bool + json bool +} + +// makeDNSResolverInfo converts a dnstype.Resolver to a jsonoutput.DNSResolverInfo. +func makeDNSResolverInfo(r *dnstype.Resolver) jsonoutput.DNSResolverInfo { + info := jsonoutput.DNSResolverInfo{Addr: r.Addr} + if r.BootstrapResolution != nil { + info.BootstrapResolution = make([]string, 0, len(r.BootstrapResolution)) + for _, a := range r.BootstrapResolution { + info.BootstrapResolution = append(info.BootstrapResolution, a.String()) + } + } + return info } func runDNSStatus(ctx context.Context, args []string) error { - all := dnsStatusArgs.all s, err := localClient.Status(ctx) if err != nil { return err @@ -92,167 +108,254 @@ func runDNSStatus(ctx context.Context, args []string) error { if err != nil { return err } - enabledStr := "disabled.\n\n(Run 'tailscale set --accept-dns=true' to start sending DNS queries to the Tailscale DNS resolver)" - if prefs.CorpDNS { - enabledStr = "enabled.\n\nTailscale is configured to handle DNS queries on this device.\nRun 'tailscale set --accept-dns=false' to revert to your system default DNS resolver." + + data := &jsonoutput.DNSStatusResult{ + TailscaleDNS: prefs.CorpDNS, } - fmt.Print("\n") - fmt.Println("=== 'Use Tailscale DNS' status ===") - fmt.Print("\n") - fmt.Printf("Tailscale DNS: %s\n", enabledStr) - fmt.Print("\n") - fmt.Println("=== MagicDNS configuration ===") - fmt.Print("\n") - fmt.Println("This is the DNS configuration provided by the coordination server to this device.") - fmt.Print("\n") - if s.CurrentTailnet == nil { - fmt.Println("No tailnet information available; make sure you're logged in to a tailnet.") + + if s.CurrentTailnet != nil { + data.CurrentTailnet = &jsonoutput.DNSTailnetInfo{ + MagicDNSEnabled: s.CurrentTailnet.MagicDNSEnabled, + MagicDNSSuffix: s.CurrentTailnet.MagicDNSSuffix, + SelfDNSName: s.Self.DNSName, + } + + netMap, err := fetchNetMap() + if err != nil { + return fmt.Errorf("failed to fetch network map: %w", err) + } + dnsConfig := netMap.DNS + + for _, r := range dnsConfig.Resolvers { + data.Resolvers = append(data.Resolvers, makeDNSResolverInfo(r)) + } + + data.SplitDNSRoutes = make(map[string][]jsonoutput.DNSResolverInfo) + for k, v := range dnsConfig.Routes { + for _, r := range v { + data.SplitDNSRoutes[k] = append(data.SplitDNSRoutes[k], makeDNSResolverInfo(r)) + } + } + + for _, r := range dnsConfig.FallbackResolvers { + data.FallbackResolvers = append(data.FallbackResolvers, makeDNSResolverInfo(r)) + } + + domains := slices.Clone(dnsConfig.Domains) + slices.Sort(domains) + data.SearchDomains = domains + + for _, a := range dnsConfig.Nameservers { + data.Nameservers = append(data.Nameservers, a.String()) + } + + data.CertDomains = dnsConfig.CertDomains + + for _, er := range dnsConfig.ExtraRecords { + data.ExtraRecords = append(data.ExtraRecords, jsonoutput.DNSExtraRecord{ + Name: er.Name, + Type: er.Type, + Value: er.Value, + }) + } + + data.ExitNodeFilteredSet = dnsConfig.ExitNodeFilteredSet + + osCfg, err := localClient.GetDNSOSConfig(ctx) + if err != nil { + if strings.Contains(err.Error(), "not supported") { + data.SystemDNSError = "not supported on this platform" + } else { + data.SystemDNSError = err.Error() + } + } else if osCfg != nil { + data.SystemDNS = &jsonoutput.DNSSystemConfig{ + Nameservers: osCfg.Nameservers, + SearchDomains: osCfg.SearchDomains, + MatchDomains: osCfg.MatchDomains, + } + } + } + + if dnsStatusArgs.json { + j, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + printf("%s\n", j) return nil - } else if s.CurrentTailnet.MagicDNSEnabled { - fmt.Printf("MagicDNS: enabled tailnet-wide (suffix = %s)", s.CurrentTailnet.MagicDNSSuffix) - fmt.Print("\n\n") - fmt.Printf("Other devices in your tailnet can reach this device at %s\n", s.Self.DNSName) + } + printf("%s", formatDNSStatusText(data, dnsStatusArgs.all)) + return nil +} + +func formatDNSStatusText(data *jsonoutput.DNSStatusResult, all bool) string { + var sb strings.Builder + + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "=== 'Use Tailscale DNS' status ===\n") + fmt.Fprintf(&sb, "\n") + if data.TailscaleDNS { + fmt.Fprintf(&sb, "Tailscale DNS: enabled.\n\nTailscale is configured to handle DNS queries on this device.\nRun 'tailscale set --accept-dns=false' to revert to your system default DNS resolver.\n") } else { - fmt.Printf("MagicDNS: disabled tailnet-wide.\n") + fmt.Fprintf(&sb, "Tailscale DNS: disabled.\n\n(Run 'tailscale set --accept-dns=true' to start sending DNS queries to the Tailscale DNS resolver)\n") + } + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "=== MagicDNS configuration ===\n") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "This is the DNS configuration provided by the coordination server to this device.\n") + fmt.Fprintf(&sb, "\n") + if data.CurrentTailnet == nil { + fmt.Fprintf(&sb, "No tailnet information available; make sure you're logged in to a tailnet.\n") + return sb.String() } - fmt.Print("\n") - netMap, err := fetchNetMap() - if err != nil { - fmt.Printf("Failed to fetch network map: %v\n", err) - return err + if data.CurrentTailnet.MagicDNSEnabled { + fmt.Fprintf(&sb, "MagicDNS: enabled tailnet-wide (suffix = %s)", data.CurrentTailnet.MagicDNSSuffix) + fmt.Fprintf(&sb, "\n\n") + fmt.Fprintf(&sb, "Other devices in your tailnet can reach this device at %s\n", data.CurrentTailnet.SelfDNSName) + } else { + fmt.Fprintf(&sb, "MagicDNS: disabled tailnet-wide.\n") } - dnsConfig := netMap.DNS - fmt.Println("Resolvers (in preference order):") - if len(dnsConfig.Resolvers) == 0 { - fmt.Println(" (no resolvers configured, system default will be used: see 'System DNS configuration' below)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Resolvers (in preference order):\n") + if len(data.Resolvers) == 0 { + fmt.Fprintf(&sb, " (no resolvers configured, system default will be used: see 'System DNS configuration' below)\n") } - for _, r := range dnsConfig.Resolvers { - fmt.Printf(" - %v", r.Addr) + for _, r := range data.Resolvers { + fmt.Fprintf(&sb, " - %v", r.Addr) if r.BootstrapResolution != nil { - fmt.Printf(" (bootstrap: %v)", r.BootstrapResolution) + fmt.Fprintf(&sb, " (bootstrap: %v)", r.BootstrapResolution) } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") } - fmt.Print("\n") - fmt.Println("Split DNS Routes:") - if len(dnsConfig.Routes) == 0 { - fmt.Println(" (no routes configured: split DNS disabled)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Split DNS Routes:\n") + if len(data.SplitDNSRoutes) == 0 { + fmt.Fprintf(&sb, " (no routes configured: split DNS disabled)\n") } - for _, k := range slices.Sorted(maps.Keys(dnsConfig.Routes)) { - v := dnsConfig.Routes[k] - for _, r := range v { - fmt.Printf(" - %-30s -> %v", k, r.Addr) + for _, k := range slices.Sorted(maps.Keys(data.SplitDNSRoutes)) { + for _, r := range data.SplitDNSRoutes[k] { + fmt.Fprintf(&sb, " - %-30s -> %v", k, r.Addr) if r.BootstrapResolution != nil { - fmt.Printf(" (bootstrap: %v)", r.BootstrapResolution) + fmt.Fprintf(&sb, " (bootstrap: %v)", r.BootstrapResolution) } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") } } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") + if all { - fmt.Println("Fallback Resolvers:") - if len(dnsConfig.FallbackResolvers) == 0 { - fmt.Println(" (no fallback resolvers configured)") + fmt.Fprintf(&sb, "Fallback Resolvers:\n") + if len(data.FallbackResolvers) == 0 { + fmt.Fprintf(&sb, " (no fallback resolvers configured)\n") } - for i, r := range dnsConfig.FallbackResolvers { - fmt.Printf(" %d: %v\n", i, r) + for i, r := range data.FallbackResolvers { + fmt.Fprintf(&sb, " %d: %v", i, r.Addr) + if r.BootstrapResolution != nil { + fmt.Fprintf(&sb, " (bootstrap: %v)", r.BootstrapResolution) + } + fmt.Fprintf(&sb, "\n") } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") } - fmt.Println("Search Domains:") - if len(dnsConfig.Domains) == 0 { - fmt.Println(" (no search domains configured)") + + fmt.Fprintf(&sb, "Search Domains:\n") + if len(data.SearchDomains) == 0 { + fmt.Fprintf(&sb, " (no search domains configured)\n") } - domains := dnsConfig.Domains - slices.Sort(domains) - for _, r := range domains { - fmt.Printf(" - %v\n", r) + for _, r := range data.SearchDomains { + fmt.Fprintf(&sb, " - %v\n", r) } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") + if all { - fmt.Println("Nameservers IP Addresses:") - if len(dnsConfig.Nameservers) == 0 { - fmt.Println(" (none were provided)") + fmt.Fprintf(&sb, "Nameservers IP Addresses:\n") + if len(data.Nameservers) == 0 { + fmt.Fprintf(&sb, " (none were provided)\n") } - for _, r := range dnsConfig.Nameservers { - fmt.Printf(" - %v\n", r) + for _, r := range data.Nameservers { + fmt.Fprintf(&sb, " - %v\n", r) } - fmt.Print("\n") - fmt.Println("Certificate Domains:") - if len(dnsConfig.CertDomains) == 0 { - fmt.Println(" (no certificate domains are configured)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Certificate Domains:\n") + if len(data.CertDomains) == 0 { + fmt.Fprintf(&sb, " (no certificate domains are configured)\n") } - for _, r := range dnsConfig.CertDomains { - fmt.Printf(" - %v\n", r) + for _, r := range data.CertDomains { + fmt.Fprintf(&sb, " - %v\n", r) } - fmt.Print("\n") - fmt.Println("Additional DNS Records:") - if len(dnsConfig.ExtraRecords) == 0 { - fmt.Println(" (no extra records are configured)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Additional DNS Records:\n") + if len(data.ExtraRecords) == 0 { + fmt.Fprintf(&sb, " (no extra records are configured)\n") } - for _, er := range dnsConfig.ExtraRecords { + for _, er := range data.ExtraRecords { if er.Type == "" { - fmt.Printf(" - %-50s -> %v\n", er.Name, er.Value) + fmt.Fprintf(&sb, " - %-50s -> %v\n", er.Name, er.Value) } else { - fmt.Printf(" - [%s] %-50s -> %v\n", er.Type, er.Name, er.Value) + fmt.Fprintf(&sb, " - [%s] %-50s -> %v\n", er.Type, er.Name, er.Value) } } - fmt.Print("\n") - fmt.Println("Filtered suffixes when forwarding DNS queries as an exit node:") - if len(dnsConfig.ExitNodeFilteredSet) == 0 { - fmt.Println(" (no suffixes are filtered)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Filtered suffixes when forwarding DNS queries as an exit node:\n") + if len(data.ExitNodeFilteredSet) == 0 { + fmt.Fprintf(&sb, " (no suffixes are filtered)\n") } - for _, s := range dnsConfig.ExitNodeFilteredSet { - fmt.Printf(" - %s\n", s) + for _, s := range data.ExitNodeFilteredSet { + fmt.Fprintf(&sb, " - %s\n", s) } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") } - fmt.Println("=== System DNS configuration ===") - fmt.Print("\n") - fmt.Println("This is the DNS configuration that Tailscale believes your operating system is using.\nTailscale may use this configuration if 'Override Local DNS' is disabled in the admin console,\nor if no resolvers are provided by the coordination server.") - fmt.Print("\n") - osCfg, err := localClient.GetDNSOSConfig(ctx) - if err != nil { - if strings.Contains(err.Error(), "not supported") { - // avoids showing the HTTP error code which would be odd here - fmt.Println(" (reading the system DNS configuration is not supported on this platform)") + fmt.Fprintf(&sb, "=== System DNS configuration ===\n") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "This is the DNS configuration that Tailscale believes your operating system is using.\nTailscale may use this configuration if 'Override Local DNS' is disabled in the admin console,\nor if no resolvers are provided by the coordination server.\n") + fmt.Fprintf(&sb, "\n") + + if data.SystemDNSError != "" { + if strings.Contains(data.SystemDNSError, "not supported") { + fmt.Fprintf(&sb, " (reading the system DNS configuration is not supported on this platform)\n") } else { - fmt.Printf(" (failed to read system DNS configuration: %v)\n", err) + fmt.Fprintf(&sb, " (failed to read system DNS configuration: %s)\n", data.SystemDNSError) } - } else if osCfg == nil { - fmt.Println(" (no OS DNS configuration available)") + } else if data.SystemDNS == nil { + fmt.Fprintf(&sb, " (no OS DNS configuration available)\n") } else { - fmt.Println("Nameservers:") - if len(osCfg.Nameservers) == 0 { - fmt.Println(" (no nameservers found, DNS queries might fail\nunless the coordination server is providing a nameserver)") + fmt.Fprintf(&sb, "Nameservers:\n") + if len(data.SystemDNS.Nameservers) == 0 { + fmt.Fprintf(&sb, " (no nameservers found, DNS queries might fail\nunless the coordination server is providing a nameserver)\n") } - for _, ns := range osCfg.Nameservers { - fmt.Printf(" - %v\n", ns) + for _, ns := range data.SystemDNS.Nameservers { + fmt.Fprintf(&sb, " - %v\n", ns) } - fmt.Print("\n") - fmt.Println("Search domains:") - if len(osCfg.SearchDomains) == 0 { - fmt.Println(" (no search domains found)") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "Search domains:\n") + if len(data.SystemDNS.SearchDomains) == 0 { + fmt.Fprintf(&sb, " (no search domains found)\n") } - for _, sd := range osCfg.SearchDomains { - fmt.Printf(" - %v\n", sd) + for _, sd := range data.SystemDNS.SearchDomains { + fmt.Fprintf(&sb, " - %v\n", sd) } if all { - fmt.Print("\n") - fmt.Println("Match domains:") - if len(osCfg.MatchDomains) == 0 { - fmt.Println(" (no match domains found)") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "Match domains:\n") + if len(data.SystemDNS.MatchDomains) == 0 { + fmt.Fprintf(&sb, " (no match domains found)\n") } - for _, md := range osCfg.MatchDomains { - fmt.Printf(" - %v\n", md) + for _, md := range data.SystemDNS.MatchDomains { + fmt.Fprintf(&sb, " - %v\n", md) } } } - fmt.Print("\n") - fmt.Println("[this is a preliminary version of this command; the output format may change in the future]") - return nil + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "[this is a preliminary version of this command; the output format may change in the future]\n") + return sb.String() } func fetchNetMap() (netMap *netmap.NetworkMap, err error) { diff --git a/cmd/tailscale/cli/dns.go b/cmd/tailscale/cli/dns.go index 086abefd6b2bf..d8db5d466d6b2 100644 --- a/cmd/tailscale/cli/dns.go +++ b/cmd/tailscale/cli/dns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/dns_test.go b/cmd/tailscale/cli/dns_test.go new file mode 100644 index 0000000000000..cc01a52702fac --- /dev/null +++ b/cmd/tailscale/cli/dns_test.go @@ -0,0 +1,65 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "context" + "strings" + "testing" +) + +func TestRunDNSQueryArgs(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "no_args", + args: []string{}, + wantErr: "missing required argument: name", + }, + { + name: "flag_after_name", + args: []string{"example.com", "--json"}, + wantErr: "unexpected flags after query name: --json", + }, + { + name: "flag_after_name_and_type", + args: []string{"example.com", "AAAA", "--json"}, + wantErr: "unexpected flags after query name: --json", + }, + { + name: "extra_args_after_type", + args: []string{"example.com", "AAAA", "extra"}, + wantErr: "unexpected extra arguments: extra", + }, + { + name: "multiple_extra_args", + args: []string{"example.com", "AAAA", "extra1", "extra2"}, + wantErr: "unexpected extra arguments: extra1 extra2", + }, + { + name: "non_flag_then_flag", + args: []string{"example.com", "AAAA", "foo", "--json"}, + wantErr: "unexpected flags after query name: --json", + }, + { + name: "multiple_misplaced_flags", + args: []string{"example.com", "--json", "--verbose"}, + wantErr: "unexpected flags after query name: --json, --verbose", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := runDNSQuery(context.Background(), tt.args) + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("error = %q, want it to contain %q", err.Error(), tt.wantErr) + } + }) + } +} diff --git a/cmd/tailscale/cli/down.go b/cmd/tailscale/cli/down.go index 224198a98deb5..6fecbd76cec12 100644 --- a/cmd/tailscale/cli/down.go +++ b/cmd/tailscale/cli/down.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/drive.go b/cmd/tailscale/cli/drive.go index 131f468477314..280ff3172fb92 100644 --- a/cmd/tailscale/cli/drive.go +++ b/cmd/tailscale/cli/drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive && !ts_mac_gui diff --git a/cmd/tailscale/cli/exitnode.go b/cmd/tailscale/cli/exitnode.go index b47b9f0bd4949..0445b66ae14ff 100644 --- a/cmd/tailscale/cli/exitnode.go +++ b/cmd/tailscale/cli/exitnode.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/exitnode_test.go b/cmd/tailscale/cli/exitnode_test.go index cc38fd3a4d39e..9a77cf5d7d3fd 100644 --- a/cmd/tailscale/cli/exitnode_test.go +++ b/cmd/tailscale/cli/exitnode_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ffcomplete/complete.go b/cmd/tailscale/cli/ffcomplete/complete.go index fbd5b9d62823d..7d280f691a407 100644 --- a/cmd/tailscale/cli/ffcomplete/complete.go +++ b/cmd/tailscale/cli/ffcomplete/complete.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && !ts_omit_completion diff --git a/cmd/tailscale/cli/ffcomplete/complete_omit.go b/cmd/tailscale/cli/ffcomplete/complete_omit.go index bafc059e7b71d..06efa63fcd3a7 100644 --- a/cmd/tailscale/cli/ffcomplete/complete_omit.go +++ b/cmd/tailscale/cli/ffcomplete/complete_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && ts_omit_completion diff --git a/cmd/tailscale/cli/ffcomplete/ffcomplete.go b/cmd/tailscale/cli/ffcomplete/ffcomplete.go index 4b8207ec60a0c..e6af2515ff26f 100644 --- a/cmd/tailscale/cli/ffcomplete/ffcomplete.go +++ b/cmd/tailscale/cli/ffcomplete/ffcomplete.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ffcomplete diff --git a/cmd/tailscale/cli/ffcomplete/internal/complete.go b/cmd/tailscale/cli/ffcomplete/internal/complete.go index b6c39dc837215..911972518d331 100644 --- a/cmd/tailscale/cli/ffcomplete/internal/complete.go +++ b/cmd/tailscale/cli/ffcomplete/internal/complete.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package internal contains internal code for the ffcomplete package. diff --git a/cmd/tailscale/cli/ffcomplete/internal/complete_test.go b/cmd/tailscale/cli/ffcomplete/internal/complete_test.go index c216bdeec500d..2bba72283b044 100644 --- a/cmd/tailscale/cli/ffcomplete/internal/complete_test.go +++ b/cmd/tailscale/cli/ffcomplete/internal/complete_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package internal_test diff --git a/cmd/tailscale/cli/ffcomplete/scripts.go b/cmd/tailscale/cli/ffcomplete/scripts.go index 8218683afa349..bccebed7feec1 100644 --- a/cmd/tailscale/cli/ffcomplete/scripts.go +++ b/cmd/tailscale/cli/ffcomplete/scripts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && !ts_omit_completion && !ts_omit_completion_scripts diff --git a/cmd/tailscale/cli/ffcomplete/scripts_omit.go b/cmd/tailscale/cli/ffcomplete/scripts_omit.go index b5d520c3fe1d9..4c082d9d1ed06 100644 --- a/cmd/tailscale/cli/ffcomplete/scripts_omit.go +++ b/cmd/tailscale/cli/ffcomplete/scripts_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && !ts_omit_completion && ts_omit_completion_scripts diff --git a/cmd/tailscale/cli/file.go b/cmd/tailscale/cli/file.go index e0879197e2dbb..94b36f535bcab 100644 --- a/cmd/tailscale/cli/file.go +++ b/cmd/tailscale/cli/file.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_taildrop diff --git a/cmd/tailscale/cli/funnel.go b/cmd/tailscale/cli/funnel.go index 34b0c74c23949..f16f571e09508 100644 --- a/cmd/tailscale/cli/funnel.go +++ b/cmd/tailscale/cli/funnel.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/cmd/tailscale/cli/id-token.go b/cmd/tailscale/cli/id-token.go index a4d02c95a82c1..e2707ee84ca42 100644 --- a/cmd/tailscale/cli/id-token.go +++ b/cmd/tailscale/cli/id-token.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ip.go b/cmd/tailscale/cli/ip.go index 8379329120436..7159904c732d6 100644 --- a/cmd/tailscale/cli/ip.go +++ b/cmd/tailscale/cli/ip.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli @@ -25,14 +25,16 @@ var ipCmd = &ffcli.Command{ fs.BoolVar(&ipArgs.want1, "1", false, "only print one IP address") fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address") fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address") + fs.StringVar(&ipArgs.assert, "assert", "", "assert that one of the node's IP(s) matches this IP address") return fs })(), } var ipArgs struct { - want1 bool - want4 bool - want6 bool + want1 bool + want4 bool + want6 bool + assert string } func runIP(ctx context.Context, args []string) error { @@ -62,6 +64,14 @@ func runIP(ctx context.Context, args []string) error { return err } ips := st.TailscaleIPs + if ipArgs.assert != "" { + for _, ip := range ips { + if ip.String() == ipArgs.assert { + return nil + } + } + return fmt.Errorf("assertion failed: IP %q not found among %v", ipArgs.assert, ips) + } if of != "" { ip, _, err := tailscaleIPFromArg(ctx, of) if err != nil { diff --git a/cmd/tailscale/cli/jsonoutput/dns.go b/cmd/tailscale/cli/jsonoutput/dns.go new file mode 100644 index 0000000000000..d9d3cc0bbb3b6 --- /dev/null +++ b/cmd/tailscale/cli/jsonoutput/dns.go @@ -0,0 +1,116 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package jsonoutput + +// DNSResolverInfo is the JSON form of [dnstype.Resolver]. +type DNSResolverInfo struct { + // Addr is a plain IP, IP:port, DoH URL, or HTTP-over-WireGuard URL. + Addr string + + // BootstrapResolution is optional pre-resolved IPs for DoT/DoH + // resolvers whose address is not already an IP. + BootstrapResolution []string `json:",omitempty"` +} + +// DNSExtraRecord is the JSON form of [tailcfg.DNSRecord]. +type DNSExtraRecord struct { + Name string + Type string `json:",omitempty"` // empty means A or AAAA, depending on Value + Value string // typically an IP address +} + +// DNSSystemConfig is the OS DNS configuration as observed by Tailscale, +// mirroring [net/dns.OSConfig]. +type DNSSystemConfig struct { + Nameservers []string `json:",omitzero"` + SearchDomains []string `json:",omitzero"` + + // MatchDomains are DNS suffixes restricting which queries use + // these Nameservers. Empty means Nameservers is the primary + // resolver. + MatchDomains []string `json:",omitzero"` +} + +// DNSTailnetInfo describes MagicDNS configuration for the tailnet, +// combining [ipnstate.TailnetStatus] and [ipnstate.PeerStatus]. +type DNSTailnetInfo struct { + // MagicDNSEnabled is whether MagicDNS is enabled for the + // tailnet. The device may still not use it if + // --accept-dns=false. + MagicDNSEnabled bool + + // MagicDNSSuffix is the tailnet's MagicDNS suffix + // (e.g. "tail1234.ts.net"), without surrounding dots. + MagicDNSSuffix string `json:",omitempty"` + + // SelfDNSName is this device's FQDN + // (e.g. "host.tail1234.ts.net."), with trailing dot. + SelfDNSName string `json:",omitempty"` +} + +// DNSStatusResult is the full DNS status collected from the local +// Tailscale daemon. +type DNSStatusResult struct { + // TailscaleDNS is whether the Tailscale DNS configuration is + // installed on this device (the --accept-dns setting). + TailscaleDNS bool + + // CurrentTailnet describes MagicDNS configuration for the tailnet. + CurrentTailnet *DNSTailnetInfo `json:",omitzero"` // nil if not connected + + // Resolvers are the DNS resolvers, in preference order. If + // empty, the system defaults are used. + Resolvers []DNSResolverInfo `json:",omitzero"` + + // SplitDNSRoutes maps domain suffixes to dedicated resolvers. + // An empty resolver slice means the suffix is handled by + // Tailscale's built-in resolver (100.100.100.100). + SplitDNSRoutes map[string][]DNSResolverInfo `json:",omitzero"` + + // FallbackResolvers are like Resolvers but only used when + // split DNS needs explicit default resolvers. + FallbackResolvers []DNSResolverInfo `json:",omitzero"` + + SearchDomains []string `json:",omitzero"` + + // Nameservers are nameserver IPs. + // + // Deprecated: old protocol versions only. Use Resolvers. + Nameservers []string `json:",omitzero"` + + // CertDomains are FQDNs for which the coordination server + // provisions TLS certificates via dns-01 ACME challenges. + CertDomains []string `json:",omitzero"` + + // ExtraRecords contains extra DNS records in the MagicDNS config. + ExtraRecords []DNSExtraRecord `json:",omitzero"` + + // ExitNodeFilteredSet are DNS suffixes this node won't resolve + // when acting as an exit node DNS proxy. Period-prefixed + // entries are suffix matches; others are exact. Always + // lowercase, no trailing dots. + ExitNodeFilteredSet []string `json:",omitzero"` + + SystemDNS *DNSSystemConfig `json:",omitzero"` // nil if unavailable + SystemDNSError string `json:",omitempty"` +} + +// DNSAnswer is a single DNS resource record from a query response. +type DNSAnswer struct { + Name string + TTL uint32 + Class string // e.g. "ClassINET" + Type string // e.g. "TypeA", "TypeAAAA" + Body string // human-readable record data +} + +// DNSQueryResult is the result of a DNS query via the Tailscale +// internal forwarder (100.100.100.100). +type DNSQueryResult struct { + Name string + QueryType string // e.g. "A", "AAAA" + Resolvers []DNSResolverInfo `json:",omitzero"` + ResponseCode string // e.g. "RCodeSuccess", "RCodeNameError" + Answers []DNSAnswer `json:",omitzero"` +} diff --git a/cmd/tailscale/cli/jsonoutput/jsonoutput.go b/cmd/tailscale/cli/jsonoutput/jsonoutput.go index aa49acc28baae..69e7374d93342 100644 --- a/cmd/tailscale/cli/jsonoutput/jsonoutput.go +++ b/cmd/tailscale/cli/jsonoutput/jsonoutput.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsonoutput provides stable and versioned JSON serialisation for CLI output. diff --git a/cmd/tailscale/cli/jsonoutput/network-lock-log.go b/cmd/tailscale/cli/jsonoutput/network-lock-log.go index 88e449db36d2a..c3190e6bac9c7 100644 --- a/cmd/tailscale/cli/jsonoutput/network-lock-log.go +++ b/cmd/tailscale/cli/jsonoutput/network-lock-log.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/cmd/tailscale/cli/jsonoutput/network-lock-status.go b/cmd/tailscale/cli/jsonoutput/network-lock-status.go index 0c6481093c9d6..a1d95b871549c 100644 --- a/cmd/tailscale/cli/jsonoutput/network-lock-status.go +++ b/cmd/tailscale/cli/jsonoutput/network-lock-status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/cmd/tailscale/cli/licenses.go b/cmd/tailscale/cli/licenses.go index bede827edf693..35d636aa2eb84 100644 --- a/cmd/tailscale/cli/licenses.go +++ b/cmd/tailscale/cli/licenses.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/login.go b/cmd/tailscale/cli/login.go index fb5b786920660..bdf97c70f8e1d 100644 --- a/cmd/tailscale/cli/login.go +++ b/cmd/tailscale/cli/login.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/logout.go b/cmd/tailscale/cli/logout.go index fbc39473026a1..90843edc2e299 100644 --- a/cmd/tailscale/cli/logout.go +++ b/cmd/tailscale/cli/logout.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/maybe_syspolicy.go b/cmd/tailscale/cli/maybe_syspolicy.go index 937a278334fd9..a66c1a65df5e4 100644 --- a/cmd/tailscale/cli/maybe_syspolicy.go +++ b/cmd/tailscale/cli/maybe_syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/cmd/tailscale/cli/metrics.go b/cmd/tailscale/cli/metrics.go index dbdedd5a61037..d16ce76d2725f 100644 --- a/cmd/tailscale/cli/metrics.go +++ b/cmd/tailscale/cli/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/nc.go b/cmd/tailscale/cli/nc.go index 4ea62255412ea..34490ec212557 100644 --- a/cmd/tailscale/cli/nc.go +++ b/cmd/tailscale/cli/nc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/netcheck.go b/cmd/tailscale/cli/netcheck.go index a8a8992f5ba23..5e45445c79cd5 100644 --- a/cmd/tailscale/cli/netcheck.go +++ b/cmd/tailscale/cli/netcheck.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli @@ -10,7 +10,9 @@ import ( "fmt" "io" "log" + "math" "net/http" + "net/netip" "sort" "strings" "time" @@ -26,6 +28,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/util/eventbus" + "tailscale.com/util/set" // The "netcheck" command also wants the portmapper linked. // @@ -41,19 +44,25 @@ var netcheckCmd = &ffcli.Command{ ShortUsage: "tailscale netcheck", ShortHelp: "Print an analysis of local network conditions", Exec: runNetcheck, - FlagSet: (func() *flag.FlagSet { - fs := newFlagSet("netcheck") - fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`) - fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency") - fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs") - return fs - })(), + FlagSet: netcheckFlagSet, } +var netcheckFlagSet = func() *flag.FlagSet { + fs := newFlagSet("netcheck") + fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`) + fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency") + fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs") + fs.StringVar(&netcheckArgs.bindAddress, "bind-address", "", "send and receive connectivity probes using this locally bound IP address; default: OS-assigned") + fs.IntVar(&netcheckArgs.bindPort, "bind-port", 0, "send and receive connectivity probes using this UDP port; default: OS-assigned") + return fs +}() + var netcheckArgs struct { - format string - every time.Duration - verbose bool + format string + every time.Duration + verbose bool + bindAddress string + bindPort int } func runNetcheck(ctx context.Context, args []string) error { @@ -73,6 +82,11 @@ func runNetcheck(ctx context.Context, args []string) error { defer pm.Close() } + flagsProvided := set.Set[string]{} + netcheckFlagSet.Visit(func(f *flag.Flag) { + flagsProvided.Add(f.Name) + }) + c := &netcheck.Client{ NetMon: netMon, PortMapper: pm, @@ -89,7 +103,17 @@ func runNetcheck(ctx context.Context, args []string) error { fmt.Fprintln(Stderr, "# Warning: this JSON format is not yet considered a stable interface") } - if err := c.Standalone(ctx, envknob.String("TS_DEBUG_NETCHECK_UDP_BIND")); err != nil { + bind, err := createNetcheckBindString( + netcheckArgs.bindAddress, + flagsProvided.Contains("bind-address"), + netcheckArgs.bindPort, + flagsProvided.Contains("bind-port"), + envknob.String("TS_DEBUG_NETCHECK_UDP_BIND")) + if err != nil { + return err + } + + if err := c.Standalone(ctx, bind); err != nil { fmt.Fprintln(Stderr, "netcheck: UDP test failure:", err) } @@ -265,3 +289,44 @@ func prodDERPMap(ctx context.Context, httpc *http.Client) (*tailcfg.DERPMap, err } return &derpMap, nil } + +// createNetcheckBindString determines the netcheck socket bind "address:port" string based +// on the CLI args and environment variable values used to invoke the netcheck CLI. +// Arguments cliAddressIsSet and cliPortIsSet explicitly indicate whether the +// corresponding cliAddress and cliPort were set in CLI args, instead of relying +// on in-band sentinel values. +func createNetcheckBindString(cliAddress string, cliAddressIsSet bool, cliPort int, cliPortIsSet bool, envBind string) (string, error) { + // Default to port number 0 but overwrite with a valid CLI value, if set. + var port uint16 = 0 + if cliPortIsSet { + // 0 is valid, results in OS picking port. + if cliPort >= 0 && cliPort <= math.MaxUint16 { + port = uint16(cliPort) + } else { + return "", fmt.Errorf("invalid bind port number: %d", cliPort) + } + } + + // Use CLI address, if set. + if cliAddressIsSet { + addr, err := netip.ParseAddr(cliAddress) + if err != nil { + return "", fmt.Errorf("invalid bind address: %q", cliAddress) + } + return netip.AddrPortFrom(addr, port).String(), nil + } else { + // No CLI address set, but port is set. + if cliPortIsSet { + return fmt.Sprintf(":%d", port), nil + } + } + + // Fall back to the environment variable. + // Intentionally skipping input validation here to avoid breaking legacy usage method. + if envBind != "" { + return envBind, nil + } + + // OS picks both address and port. + return ":0", nil +} diff --git a/cmd/tailscale/cli/netcheck_test.go b/cmd/tailscale/cli/netcheck_test.go new file mode 100644 index 0000000000000..b2c2bceb39dc9 --- /dev/null +++ b/cmd/tailscale/cli/netcheck_test.go @@ -0,0 +1,108 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "testing" +) + +func TestCreateBindStr(t *testing.T) { + // Test all combinations of CLI arg address, CLI arg port, and env var string + // as inputs to create netcheck bind string. + tests := []struct { + name string + cliAddress string + cliAddressIsSet bool + cliPort int + cliPortIsSet bool + envBind string + want string + wantError string + }{ + { + name: "noAddr-noPort-noEnv", + want: ":0", + }, + { + name: "yesAddrv4-noPort-noEnv", + cliAddress: "100.123.123.123", + cliAddressIsSet: true, + want: "100.123.123.123:0", + }, + { + name: "yesAddrv6-noPort-noEnv", + cliAddress: "dead::beef", + cliAddressIsSet: true, + want: "[dead::beef]:0", + }, + { + name: "yesAddr-yesPort-noEnv", + cliAddress: "100.123.123.123", + cliAddressIsSet: true, + cliPort: 456, + cliPortIsSet: true, + want: "100.123.123.123:456", + }, + { + name: "yesAddr-yesPort-yesEnv", + cliAddress: "100.123.123.123", + cliAddressIsSet: true, + cliPort: 456, + cliPortIsSet: true, + envBind: "55.55.55.55:789", + want: "100.123.123.123:456", + }, + { + name: "noAddr-yesPort-noEnv", + cliPort: 456, + cliPortIsSet: true, + want: ":456", + }, + { + name: "noAddr-yesPort-yesEnv", + cliPort: 456, + cliPortIsSet: true, + envBind: "55.55.55.55:789", + want: ":456", + }, + { + name: "noAddr-noPort-yesEnv", + envBind: "55.55.55.55:789", + want: "55.55.55.55:789", + }, + { + name: "badAddr-noPort-noEnv-1", + cliAddress: "678.678.678.678", + cliAddressIsSet: true, + wantError: `invalid bind address: "678.678.678.678"`, + }, + { + name: "badAddr-noPort-noEnv-2", + cliAddress: "lorem ipsum", + cliAddressIsSet: true, + wantError: `invalid bind address: "lorem ipsum"`, + }, + { + name: "noAddr-badPort-noEnv", + cliPort: -1, + cliPortIsSet: true, + wantError: "invalid bind port number: -1", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotErr := createNetcheckBindString(tt.cliAddress, tt.cliAddressIsSet, tt.cliPort, tt.cliPortIsSet, tt.envBind) + var gotErrStr string + if gotErr != nil { + gotErrStr = gotErr.Error() + } + if gotErrStr != tt.wantError { + t.Errorf("got error %q; want error %q", gotErrStr, tt.wantError) + } + if got != tt.want { + t.Errorf("got result %q; want result %q", got, tt.want) + } + }) + } +} diff --git a/cmd/tailscale/cli/network-lock.go b/cmd/tailscale/cli/network-lock.go index 3b374ece2543f..9ec0e1d7fe819 100644 --- a/cmd/tailscale/cli/network-lock.go +++ b/cmd/tailscale/cli/network-lock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock @@ -224,7 +224,7 @@ func runNetworkLockStatus(ctx context.Context, args []string) error { if nlStatusArgs.json.Value == 1 { return jsonoutput.PrintNetworkLockStatusJSONV1(os.Stdout, st) } else { - return fmt.Errorf("unrecognised version: %q", nlStatusArgs.json.Value) + return fmt.Errorf("unrecognised version: %d", nlStatusArgs.json.Value) } } @@ -717,7 +717,7 @@ func printNetworkLockLog(updates []ipnstate.NetworkLockUpdate, out io.Writer, js if jsonSchema.Value == 1 { return jsonoutput.PrintNetworkLockLogJSONV1(out, updates) } else { - return fmt.Errorf("unrecognised version: %q", jsonSchema.Value) + return fmt.Errorf("unrecognised version: %d", jsonSchema.Value) } } diff --git a/cmd/tailscale/cli/network-lock_test.go b/cmd/tailscale/cli/network-lock_test.go index aa777ff922ba1..596a51b8a2deb 100644 --- a/cmd/tailscale/cli/network-lock_test.go +++ b/cmd/tailscale/cli/network-lock_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/open_browser.go b/cmd/tailscale/cli/open_browser.go new file mode 100644 index 0000000000000..a006b9765da7a --- /dev/null +++ b/cmd/tailscale/cli/open_browser.go @@ -0,0 +1,12 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_webbrowser + +package cli + +import "github.com/toqueteos/webbrowser" + +func init() { + hookOpenURL.Set(webbrowser.Open) +} diff --git a/cmd/tailscale/cli/ping.go b/cmd/tailscale/cli/ping.go index 8ece7c93d2311..1e8bbd23f15e8 100644 --- a/cmd/tailscale/cli/ping.go +++ b/cmd/tailscale/cli/ping.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/risks.go b/cmd/tailscale/cli/risks.go index d4572842bf758..1bd128d566125 100644 --- a/cmd/tailscale/cli/risks.go +++ b/cmd/tailscale/cli/risks.go @@ -1,16 +1,13 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli import ( - "context" "errors" "flag" - "runtime" "strings" - "tailscale.com/ipn" "tailscale.com/util/prompt" "tailscale.com/util/testenv" ) @@ -19,7 +16,6 @@ var ( riskTypes []string riskLoseSSH = registerRiskType("lose-ssh") riskMacAppConnector = registerRiskType("mac-app-connector") - riskStrictRPFilter = registerRiskType("linux-strict-rp-filter") riskAll = registerRiskType("all") ) @@ -72,18 +68,3 @@ func presentRiskToUser(riskType, riskMessage, acceptedRisks string) error { return errAborted } - -// checkExitNodeRisk checks if the user is using an exit node on Linux and -// whether reverse path filtering is enabled. If so, it presents a risk message. -func checkExitNodeRisk(ctx context.Context, prefs *ipn.Prefs, acceptedRisks string) error { - if runtime.GOOS != "linux" { - return nil - } - if !prefs.ExitNodeIP.IsValid() && prefs.ExitNodeID == "" { - return nil - } - if err := localClient.CheckReversePathFiltering(ctx); err != nil { - return presentRiskToUser(riskStrictRPFilter, err.Error(), acceptedRisks) - } - return nil -} diff --git a/cmd/tailscale/cli/serve_legacy.go b/cmd/tailscale/cli/serve_legacy.go index 0e9b7d0227ccf..837d8851368e4 100644 --- a/cmd/tailscale/cli/serve_legacy.go +++ b/cmd/tailscale/cli/serve_legacy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/cmd/tailscale/cli/serve_legacy_test.go b/cmd/tailscale/cli/serve_legacy_test.go index 819017ad81bb5..27cbb5712dd0f 100644 --- a/cmd/tailscale/cli/serve_legacy_test.go +++ b/cmd/tailscale/cli/serve_legacy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 6a29074817a59..840c47ac66dd1 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve @@ -219,7 +219,7 @@ var errHelpFunc = func(m serveMode) error { // newServeV2Command returns a new "serve" subcommand using e as its environment. func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command { if subcmd != serve && subcmd != funnel { - log.Fatalf("newServeDevCommand called with unknown subcmd %q", subcmd) + log.Fatalf("newServeDevCommand called with unknown subcmd %v", subcmd) } info := infoMap[subcmd] diff --git a/cmd/tailscale/cli/serve_v2_test.go b/cmd/tailscale/cli/serve_v2_test.go index a56fece3e8c59..7b27de6f2eb26 100644 --- a/cmd/tailscale/cli/serve_v2_test.go +++ b/cmd/tailscale/cli/serve_v2_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/serve_v2_unix_test.go b/cmd/tailscale/cli/serve_v2_unix_test.go index 9064655981288..671cdfbfbd1bc 100644 --- a/cmd/tailscale/cli/serve_v2_unix_test.go +++ b/cmd/tailscale/cli/serve_v2_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/cmd/tailscale/cli/set.go b/cmd/tailscale/cli/set.go index 31662392f8437..22d78641f38a9 100644 --- a/cmd/tailscale/cli/set.go +++ b/cmd/tailscale/cli/set.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli @@ -193,9 +193,7 @@ func runSet(ctx context.Context, args []string) (retErr error) { } warnOnAdvertiseRoutes(ctx, &maskedPrefs.Prefs) - if err := checkExitNodeRisk(ctx, &maskedPrefs.Prefs, setArgs.acceptedRisks); err != nil { - return err - } + var advertiseExitNodeSet, advertiseRoutesSet bool setFlagSet.Visit(func(f *flag.Flag) { updateMaskedPrefsFromUpOrSetFlag(maskedPrefs, f.Name) diff --git a/cmd/tailscale/cli/set_test.go b/cmd/tailscale/cli/set_test.go index a2f211f8cdc36..63fa3c05c48b3 100644 --- a/cmd/tailscale/cli/set_test.go +++ b/cmd/tailscale/cli/set_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ssh.go b/cmd/tailscale/cli/ssh.go index 9275c9a1c2814..bea18f7abf6ac 100644 --- a/cmd/tailscale/cli/ssh.go +++ b/cmd/tailscale/cli/ssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ssh_exec.go b/cmd/tailscale/cli/ssh_exec.go index 10e52903dea64..ecfd3c4e6052a 100644 --- a/cmd/tailscale/cli/ssh_exec.go +++ b/cmd/tailscale/cli/ssh_exec.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !windows diff --git a/cmd/tailscale/cli/ssh_exec_js.go b/cmd/tailscale/cli/ssh_exec_js.go index 40effc7cafc7e..bf631c3b82d24 100644 --- a/cmd/tailscale/cli/ssh_exec_js.go +++ b/cmd/tailscale/cli/ssh_exec_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ssh_exec_windows.go b/cmd/tailscale/cli/ssh_exec_windows.go index e249afe667401..85e1518175609 100644 --- a/cmd/tailscale/cli/ssh_exec_windows.go +++ b/cmd/tailscale/cli/ssh_exec_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ssh_unix.go b/cmd/tailscale/cli/ssh_unix.go index 71c0caaa69ad5..768d71116cf2c 100644 --- a/cmd/tailscale/cli/ssh_unix.go +++ b/cmd/tailscale/cli/ssh_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !wasm && !windows && !plan9 diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 89b18335b4ee0..9ce4debda8dea 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli @@ -18,7 +18,6 @@ import ( "text/tabwriter" "github.com/peterbourgon/ff/v3/ffcli" - "github.com/toqueteos/webbrowser" "golang.org/x/net/idna" "tailscale.com/feature" "tailscale.com/ipn" @@ -113,7 +112,9 @@ func runStatus(ctx context.Context, args []string) error { ln.Close() }() if statusArgs.browser { - go webbrowser.Open(statusURL) + if f, ok := hookOpenURL.GetOk(); ok { + go f(statusURL) + } } err = http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.RequestURI != "/" { @@ -175,13 +176,13 @@ func runStatus(ctx context.Context, args []string) error { } if !ps.Active { if ps.ExitNode { - f("idle; exit node" + offline) + f("idle; exit node%s", offline) } else if ps.ExitNodeOption { - f("idle; offers exit node" + offline) + f("idle; offers exit node%s", offline) } else if anyTraffic { - f("idle" + offline) + f("idle%s", offline) } else if !ps.Online { - f("offline" + lastSeenFmt(ps.LastSeen)) + f("offline%s", lastSeenFmt(ps.LastSeen)) } else { f("-") } @@ -200,7 +201,7 @@ func runStatus(ctx context.Context, args []string) error { f("peer-relay %s", ps.PeerRelay) } if !ps.Online { - f(offline) + f("%s", offline) } } if anyTraffic { @@ -252,6 +253,8 @@ func runStatus(ctx context.Context, args []string) error { return nil } +var hookOpenURL feature.Hook[func(string) error] + var hookPrintFunnelStatus feature.Hook[func(context.Context)] // isRunningOrStarting reports whether st is in state Running or Starting. diff --git a/cmd/tailscale/cli/switch.go b/cmd/tailscale/cli/switch.go index b315a21e7437f..bd90c522e3393 100644 --- a/cmd/tailscale/cli/switch.go +++ b/cmd/tailscale/cli/switch.go @@ -1,10 +1,11 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli import ( "context" + "encoding/json" "flag" "fmt" "os" @@ -18,9 +19,12 @@ import ( ) var switchCmd = &ffcli.Command{ - Name: "switch", - ShortUsage: "tailscale switch ", - ShortHelp: "Switch to a different Tailscale account", + Name: "switch", + ShortUsage: strings.Join([]string{ + "tailscale switch ", + "tailscale switch --list [--json]", + }, "\n"), + ShortHelp: "Switch to a different Tailscale account", LongHelp: `"tailscale switch" switches between logged in accounts. You can use the ID that's returned from 'tailnet switch -list' to pick which profile you want to switch to. Alternatively, you @@ -31,6 +35,7 @@ This command is currently in alpha and may change in the future.`, FlagSet: func() *flag.FlagSet { fs := flag.NewFlagSet("switch", flag.ExitOnError) fs.BoolVar(&switchArgs.list, "list", false, "list available accounts") + fs.BoolVar(&switchArgs.json, "json", false, "list available accounts in JSON format") return fs }(), Exec: switchProfile, @@ -82,6 +87,7 @@ func init() { var switchArgs struct { list bool + json bool } func listProfiles(ctx context.Context) error { @@ -109,10 +115,48 @@ func listProfiles(ctx context.Context) error { return nil } +type switchProfileJSON struct { + ID string `json:"id"` + Nickname string `json:"nickname"` + Tailnet string `json:"tailnet"` + Account string `json:"account"` + Selected bool `json:"selected"` +} + +func listProfilesJSON(ctx context.Context) error { + curP, all, err := localClient.ProfileStatus(ctx) + if err != nil { + return err + } + profiles := make([]switchProfileJSON, 0, len(all)) + for _, prof := range all { + profiles = append(profiles, switchProfileJSON{ + ID: string(prof.ID), + Tailnet: prof.NetworkProfile.DisplayNameOrDefault(), + Account: prof.UserProfile.LoginName, + Nickname: prof.Name, + Selected: prof.ID == curP.ID, + }) + } + profilesJSON, err := json.MarshalIndent(profiles, "", " ") + if err != nil { + return err + } + printf("%s\n", profilesJSON) + return nil +} + func switchProfile(ctx context.Context, args []string) error { if switchArgs.list { + if switchArgs.json { + return listProfilesJSON(ctx) + } return listProfiles(ctx) } + if switchArgs.json { + outln("--json argument cannot be used with tailscale switch NAME") + os.Exit(1) + } if len(args) != 1 { outln("usage: tailscale switch NAME") os.Exit(1) diff --git a/cmd/tailscale/cli/syspolicy.go b/cmd/tailscale/cli/syspolicy.go index 97f3f2122b40c..e44b01d5ffa15 100644 --- a/cmd/tailscale/cli/syspolicy.go +++ b/cmd/tailscale/cli/syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/cmd/tailscale/cli/systray.go b/cmd/tailscale/cli/systray.go index 827e8a9a40a30..ca0840fe9271e 100644 --- a/cmd/tailscale/cli/systray.go +++ b/cmd/tailscale/cli/systray.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_systray diff --git a/cmd/tailscale/cli/systray_omit.go b/cmd/tailscale/cli/systray_omit.go index 8d93fd84b52a9..83ec199a7d895 100644 --- a/cmd/tailscale/cli/systray_omit.go +++ b/cmd/tailscale/cli/systray_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || ts_omit_systray diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index bf0315860fbeb..79cc60ca2347f 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli @@ -24,6 +24,7 @@ import ( shellquote "github.com/kballard/go-shellquote" "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/feature/buildfeatures" + _ "tailscale.com/feature/condregister/awsparamstore" _ "tailscale.com/feature/condregister/identityfederation" _ "tailscale.com/feature/condregister/oauthkey" "tailscale.com/health/healthmsg" @@ -220,16 +221,39 @@ func resolveValueFromFile(v string) (string, error) { return v, nil } -func (a upArgsT) getAuthKey() (string, error) { - return resolveValueFromFile(a.authKeyOrFile) +// resolveValueFromParameterStore resolves a value from AWS Parameter Store if +// the value looks like an SSM ARN. If the hook is not available or the value +// is not an SSM ARN, it returns the value unchanged. +func resolveValueFromParameterStore(ctx context.Context, v string) (string, error) { + if f, ok := tailscale.HookResolveValueFromParameterStore.GetOk(); ok { + return f(ctx, v) + } + return v, nil +} + +// resolveValue will take the given value (e.g. as passed to --auth-key), and +// depending on the prefix, resolve the value from either a file or AWS +// Parameter Store. Values with an unknown prefix are returned as-is. +func resolveValue(ctx context.Context, v string) (string, error) { + switch { + case strings.HasPrefix(v, "file:"): + return resolveValueFromFile(v) + case strings.HasPrefix(v, tailscale.ResolvePrefixAWSParameterStore): + return resolveValueFromParameterStore(ctx, v) + } + return v, nil +} + +func (a upArgsT) getAuthKey(ctx context.Context) (string, error) { + return resolveValue(ctx, a.authKeyOrFile) } -func (a upArgsT) getClientSecret() (string, error) { - return resolveValueFromFile(a.clientSecretOrFile) +func (a upArgsT) getClientSecret(ctx context.Context) (string, error) { + return resolveValue(ctx, a.clientSecretOrFile) } -func (a upArgsT) getIDToken() (string, error) { - return resolveValueFromFile(a.idTokenOrFile) +func (a upArgsT) getIDToken(ctx context.Context) (string, error) { + return resolveValue(ctx, a.idTokenOrFile) } var upArgsGlobal upArgsT @@ -519,9 +543,6 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE } warnOnAdvertiseRoutes(ctx, prefs) - if err := checkExitNodeRisk(ctx, prefs, upArgs.acceptedRisks); err != nil { - return err - } curPrefs, err := localClient.GetPrefs(ctx) if err != nil { @@ -602,7 +623,7 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE return err } - authKey, err := upArgs.getAuthKey() + authKey, err := upArgs.getAuthKey(ctx) if err != nil { return err } @@ -611,13 +632,13 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE if f, ok := tailscale.HookResolveAuthKey.GetOk(); ok { clientSecret := authKey // the authkey argument accepts client secrets, if both arguments are provided authkey has precedence if clientSecret == "" { - clientSecret, err = upArgs.getClientSecret() + clientSecret, err = upArgs.getClientSecret(ctx) if err != nil { return err } } - authKey, err = f(ctx, clientSecret, strings.Split(upArgs.advertiseTags, ",")) + authKey, err = f(ctx, clientSecret, prefs.AdvertiseTags) if err != nil { return err } @@ -625,12 +646,12 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE // Try to resolve the auth key via workload identity federation if that functionality // is available and no auth key is yet determined. if f, ok := tailscale.HookResolveAuthKeyViaWIF.GetOk(); ok && authKey == "" { - idToken, err := upArgs.getIDToken() + idToken, err := upArgs.getIDToken(ctx) if err != nil { return err } - authKey, err = f(ctx, prefs.ControlURL, upArgs.clientID, idToken, upArgs.audience, strings.Split(upArgs.advertiseTags, ",")) + authKey, err = f(ctx, prefs.ControlURL, upArgs.clientID, idToken, upArgs.audience, prefs.AdvertiseTags) if err != nil { return err } @@ -810,7 +831,6 @@ func upWorthyWarning(s string) bool { return strings.Contains(s, healthmsg.TailscaleSSHOnBut) || strings.Contains(s, healthmsg.WarnAcceptRoutesOff) || strings.Contains(s, healthmsg.LockedOut) || - strings.Contains(s, healthmsg.WarnExitNodeUsage) || strings.Contains(s, healthmsg.InMemoryTailnetLockState) || strings.Contains(strings.ToLower(s), "update available: ") } diff --git a/cmd/tailscale/cli/up_test.go b/cmd/tailscale/cli/up_test.go index bb172f9063f59..9af8eae7d9994 100644 --- a/cmd/tailscale/cli/up_test.go +++ b/cmd/tailscale/cli/up_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/update.go b/cmd/tailscale/cli/update.go index 7eb0dccace7a8..47177347dba85 100644 --- a/cmd/tailscale/cli/update.go +++ b/cmd/tailscale/cli/update.go @@ -1,6 +1,8 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause +//go:build !ts_omit_clientupdate + package cli import ( @@ -17,6 +19,17 @@ import ( "tailscale.com/version/distro" ) +func init() { + maybeUpdateCmd = func() *ffcli.Command { return updateCmd } + + clientupdateLatestTailscaleVersion.Set(func(track string) (string, error) { + if track == "" { + return clientupdate.LatestTailscaleVersion(clientupdate.CurrentTrack) + } + return clientupdate.LatestTailscaleVersion(track) + }) +} + var updateCmd = &ffcli.Command{ Name: "update", ShortUsage: "tailscale update", @@ -40,7 +53,7 @@ var updateCmd = &ffcli.Command{ distro.Get() != distro.Synology && runtime.GOOS != "freebsd" && runtime.GOOS != "darwin" { - fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable" or "unstable" (dev); empty means same as current`) + fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable", "release-candidate", or "unstable" (dev); empty means same as current`) fs.StringVar(&updateArgs.version, "version", "", `explicit version to update/downgrade to`) } return fs diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go index b25502d5a4be5..3d6590a39bf2e 100644 --- a/cmd/tailscale/cli/version.go +++ b/cmd/tailscale/cli/version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli @@ -10,7 +10,7 @@ import ( "fmt" "github.com/peterbourgon/ff/v3/ffcli" - "tailscale.com/clientupdate" + "tailscale.com/feature" "tailscale.com/ipn/ipnstate" "tailscale.com/version" ) @@ -24,6 +24,7 @@ var versionCmd = &ffcli.Command{ fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version") fs.BoolVar(&versionArgs.json, "json", false, "output in JSON format") fs.BoolVar(&versionArgs.upstream, "upstream", false, "fetch and print the latest upstream release version from pkgs.tailscale.com") + fs.StringVar(&versionArgs.track, "track", "", `which track to check for updates: "stable", "release-candidate", or "unstable" (dev); empty means same as current`) return fs })(), Exec: runVersion, @@ -33,8 +34,11 @@ var versionArgs struct { daemon bool // also check local node's daemon version json bool upstream bool + track string } +var clientupdateLatestTailscaleVersion feature.Hook[func(string) (string, error)] + func runVersion(ctx context.Context, args []string) error { if len(args) > 0 { return fmt.Errorf("too many non-flag arguments: %q", args) @@ -51,7 +55,11 @@ func runVersion(ctx context.Context, args []string) error { var upstreamVer string if versionArgs.upstream { - upstreamVer, err = clientupdate.LatestTailscaleVersion(clientupdate.CurrentTrack) + f, ok := clientupdateLatestTailscaleVersion.GetOk() + if !ok { + return fmt.Errorf("fetching latest version not supported in this build") + } + upstreamVer, err = f(versionArgs.track) if err != nil { return err } diff --git a/cmd/tailscale/cli/wait.go b/cmd/tailscale/cli/wait.go new file mode 100644 index 0000000000000..ce9c6aba65b40 --- /dev/null +++ b/cmd/tailscale/cli/wait.go @@ -0,0 +1,157 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "context" + "flag" + "fmt" + "net" + "net/netip" + "strings" + "time" + + "github.com/peterbourgon/ff/v3/ffcli" + "tailscale.com/ipn" + "tailscale.com/types/logger" + "tailscale.com/util/backoff" +) + +var waitCmd = &ffcli.Command{ + Name: "wait", + ShortHelp: "Wait for Tailscale interface/IPs to be ready for binding", + LongHelp: strings.TrimSpace(` +Wait for Tailscale resources to be available. As of 2026-01-02, the only +resource that's available to wait for by is the Tailscale interface and its +IPs. + +With no arguments, this command will block until tailscaled is up, its backend is running, +and the Tailscale interface is up and has a Tailscale IP address assigned to it. + +If running in userspace-networking mode, this command only waits for tailscaled and +the Running state, as no physical network interface exists. + +A future version of this command may support waiting for other types of resources. + +The command returns exit code 0 on success, and non-zero on failure or timeout. + +To wait on a specific type of IP address, use 'tailscale ip' in combination with +the 'tailscale wait' command. For example, to wait for an IPv4 address: + + tailscale wait && tailscale ip --assert= + +Linux systemd users can wait for the "tailscale-online.target" target, which runs +this command. + +More generally, a service that wants to bind to (listen on) a Tailscale interface or IP address +can run it like 'tailscale wait && /path/to/service [...]' to ensure that Tailscale is ready +before the program starts. +`), + + ShortUsage: "tailscale wait", + Exec: runWait, + FlagSet: (func() *flag.FlagSet { + fs := newFlagSet("wait") + fs.DurationVar(&waitArgs.timeout, "timeout", 0, "how long to wait before giving up (0 means wait indefinitely)") + return fs + })(), +} + +var waitArgs struct { + timeout time.Duration +} + +func runWait(ctx context.Context, args []string) error { + if len(args) > 0 { + return fmt.Errorf("unexpected arguments: %q", args) + } + if waitArgs.timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, waitArgs.timeout) + defer cancel() + } + + bo := backoff.NewBackoff("wait", logger.Discard, 2*time.Second) + for { + _, err := localClient.StatusWithoutPeers(ctx) + bo.BackOff(ctx, err) + if err == nil { + break + } + if ctx.Err() != nil { + return ctx.Err() + } + } + + watcher, err := localClient.WatchIPNBus(ctx, ipn.NotifyInitialState) + if err != nil { + return err + } + defer watcher.Close() + var firstIP netip.Addr + for { + not, err := watcher.Next() + if err != nil { + return err + } + if not.State != nil && *not.State == ipn.Running { + + st, err := localClient.StatusWithoutPeers(ctx) + if err != nil { + return err + } + if len(st.TailscaleIPs) > 0 { + firstIP = st.TailscaleIPs[0] + break + } + } + } + + st, err := localClient.StatusWithoutPeers(ctx) + if err != nil { + return err + } + if !st.TUN { + // No TUN; nothing more to wait for. + return nil + } + + // Verify we have an interface using that IP. + for { + err := checkForInterfaceIP(firstIP) + if err == nil { + return nil + } + bo.BackOff(ctx, err) + if ctx.Err() != nil { + return ctx.Err() + } + } +} + +func checkForInterfaceIP(ip netip.Addr) error { + ifs, err := net.Interfaces() + if err != nil { + return err + } + for _, ifi := range ifs { + addrs, err := ifi.Addrs() + if err != nil { + return err + } + for _, addr := range addrs { + var aip netip.Addr + switch v := addr.(type) { + case *net.IPNet: + aip, _ = netip.AddrFromSlice(v.IP) + case *net.IPAddr: + aip, _ = netip.AddrFromSlice(v.IP) + } + if aip.Unmap() == ip { + return nil + } + } + } + return fmt.Errorf("no interface has IP %v", ip) +} diff --git a/cmd/tailscale/cli/web.go b/cmd/tailscale/cli/web.go index 2713f730bf600..c13cad2d645ce 100644 --- a/cmd/tailscale/cli/web.go +++ b/cmd/tailscale/cli/web.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_webclient diff --git a/cmd/tailscale/cli/web_test.go b/cmd/tailscale/cli/web_test.go index f2470b364c41e..727c5644be0b4 100644 --- a/cmd/tailscale/cli/web_test.go +++ b/cmd/tailscale/cli/web_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/whois.go b/cmd/tailscale/cli/whois.go index 44ff68dec8777..b2ad74149635b 100644 --- a/cmd/tailscale/cli/whois.go +++ b/cmd/tailscale/cli/whois.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 67ffa4fbc0fda..b4605f9f2e926 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/depaware) + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 L fyne.io/systray from tailscale.com/client/systray @@ -11,6 +12,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W đŸ’Ŗ github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy L github.com/atotto/clipboard from tailscale.com/client/systray github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/defaults+ + L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/feature/awsparamstore github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/sso+ github.com/aws/aws-sdk-go-v2/aws/middleware from github.com/aws/aws-sdk-go-v2/aws/retry+ github.com/aws/aws-sdk-go-v2/aws/protocol/query from github.com/aws/aws-sdk-go-v2/service/sts @@ -21,7 +23,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4 from github.com/aws/aws-sdk-go-v2/aws/signer/v4 github.com/aws/aws-sdk-go-v2/aws/signer/v4 from github.com/aws/aws-sdk-go-v2/internal/auth/smithy+ github.com/aws/aws-sdk-go-v2/aws/transport/http from github.com/aws/aws-sdk-go-v2/config+ - github.com/aws/aws-sdk-go-v2/config from tailscale.com/wif + github.com/aws/aws-sdk-go-v2/config from tailscale.com/wif+ github.com/aws/aws-sdk-go-v2/credentials from github.com/aws/aws-sdk-go-v2/config github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds from github.com/aws/aws-sdk-go-v2/config github.com/aws/aws-sdk-go-v2/credentials/endpointcreds from github.com/aws/aws-sdk-go-v2/config @@ -49,6 +51,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/aws/aws-sdk-go-v2/internal/timeconv from github.com/aws/aws-sdk-go-v2/aws/retry github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding from github.com/aws/aws-sdk-go-v2/service/sts github.com/aws/aws-sdk-go-v2/service/internal/presigned-url from github.com/aws/aws-sdk-go-v2/service/sts + L github.com/aws/aws-sdk-go-v2/service/ssm from tailscale.com/feature/awsparamstore + L github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssm + L github.com/aws/aws-sdk-go-v2/service/ssm/types from github.com/aws/aws-sdk-go-v2/service/ssm github.com/aws/aws-sdk-go-v2/service/sso from github.com/aws/aws-sdk-go-v2/config+ github.com/aws/aws-sdk-go-v2/service/sso/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sso github.com/aws/aws-sdk-go-v2/service/sso/types from github.com/aws/aws-sdk-go-v2/service/sso @@ -65,7 +70,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/aws/smithy-go/document from github.com/aws/aws-sdk-go-v2/service/sso+ github.com/aws/smithy-go/encoding from github.com/aws/smithy-go/encoding/json+ github.com/aws/smithy-go/encoding/httpbinding from github.com/aws/aws-sdk-go-v2/aws/protocol/query+ - github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssooidc + github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssooidc+ github.com/aws/smithy-go/encoding/xml from github.com/aws/aws-sdk-go-v2/service/sts github.com/aws/smithy-go/endpoints from github.com/aws/aws-sdk-go-v2/service/sso+ github.com/aws/smithy-go/endpoints/private/rulesfn from github.com/aws/aws-sdk-go-v2/service/sts @@ -76,11 +81,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/aws/smithy-go/middleware from github.com/aws/aws-sdk-go-v2/aws+ github.com/aws/smithy-go/private/requestcompression from github.com/aws/aws-sdk-go-v2/config github.com/aws/smithy-go/ptr from github.com/aws/aws-sdk-go-v2/aws+ - github.com/aws/smithy-go/rand from github.com/aws/aws-sdk-go-v2/aws/middleware + github.com/aws/smithy-go/rand from github.com/aws/aws-sdk-go-v2/aws/middleware+ github.com/aws/smithy-go/time from github.com/aws/aws-sdk-go-v2/service/sso+ github.com/aws/smithy-go/tracing from github.com/aws/aws-sdk-go-v2/aws/middleware+ github.com/aws/smithy-go/transport/http from github.com/aws/aws-sdk-go-v2/aws+ 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 github.com/coder/websocket from tailscale.com/util/eventbus github.com/coder/websocket/internal/errd from github.com/coder/websocket github.com/coder/websocket/internal/util from github.com/coder/websocket @@ -90,8 +96,13 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep L github.com/fogleman/gg from tailscale.com/client/systray github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/gaissmai/bart from tailscale.com/net/tsdial + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ @@ -112,6 +123,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/huin/goupnp/scpd from github.com/huin/goupnp github.com/huin/goupnp/soap from github.com/huin/goupnp+ github.com/huin/goupnp/ssdp from github.com/huin/goupnp + L github.com/jmespath/go-jmespath from github.com/aws/aws-sdk-go-v2/service/ssm L đŸ’Ŗ github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli @@ -138,7 +150,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/x448/float16 from github.com/fxamacker/cbor/v2 go.yaml.in/yaml/v2 from sigs.k8s.io/yaml đŸ’Ŗ go4.org/mem from tailscale.com/client/local+ - go4.org/netipx from tailscale.com/net/tsaddr + go4.org/netipx from tailscale.com/net/tsaddr+ W đŸ’Ŗ golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/netmon+ k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli sigs.k8s.io/yaml from tailscale.com/cmd/tailscale/cli @@ -146,6 +158,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep software.sslmate.com/src/go-pkcs12/internal/rc2 from software.sslmate.com/src/go-pkcs12 tailscale.com from tailscale.com/version đŸ’Ŗ tailscale.com/atomicfile from tailscale.com/cmd/tailscale/cli+ + L tailscale.com/client/freedesktop from tailscale.com/client/systray tailscale.com/client/local from tailscale.com/client/tailscale+ L tailscale.com/client/systray from tailscale.com/cmd/tailscale/cli tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale @@ -168,8 +181,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/envknob from tailscale.com/client/local+ tailscale.com/envknob/featureknob from tailscale.com/client/web tailscale.com/feature from tailscale.com/tsweb+ + L tailscale.com/feature/awsparamstore from tailscale.com/feature/condregister/awsparamstore tailscale.com/feature/buildfeatures from tailscale.com/cmd/tailscale/cli+ tailscale.com/feature/capture/dissector from tailscale.com/cmd/tailscale/cli + tailscale.com/feature/condregister/awsparamstore from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/identityfederation from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/oauthkey from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/portmapper from tailscale.com/cmd/tailscale/cli @@ -243,6 +258,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/structs from tailscale.com/ipn+ tailscale.com/types/tkatype from tailscale.com/types/key+ tailscale.com/types/views from tailscale.com/tailcfg+ + tailscale.com/util/backoff from tailscale.com/cmd/tailscale/cli tailscale.com/util/cibuild from tailscale.com/health+ tailscale.com/util/clientmetric from tailscale.com/net/netcheck+ tailscale.com/util/cloudenv from tailscale.com/net/dnscache+ @@ -337,7 +353,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/cmd/tailscale/cli+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -362,7 +378,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -370,13 +386,14 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -391,7 +408,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -405,19 +422,21 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from net/http+ @@ -467,9 +486,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from archive/tar+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -482,14 +500,17 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync diff --git a/cmd/tailscale/deps_test.go b/cmd/tailscale/deps_test.go index 132940e3cc937..ea7bb15d3a895 100644 --- a/cmd/tailscale/deps_test.go +++ b/cmd/tailscale/deps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscale/generate.go b/cmd/tailscale/generate.go index 5c2e9be915980..36a4fa671dddb 100644 --- a/cmd/tailscale/generate.go +++ b/cmd/tailscale/generate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscale/tailscale.go b/cmd/tailscale/tailscale.go index f6adb6c197071..57a51840832b5 100644 --- a/cmd/tailscale/tailscale.go +++ b/cmd/tailscale/tailscale.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tailscale command is the Tailscale command-line client. It interacts diff --git a/cmd/tailscale/tailscale_test.go b/cmd/tailscale/tailscale_test.go index a7a3c2323cb8f..ca064b6b7a28a 100644 --- a/cmd/tailscale/tailscale_test.go +++ b/cmd/tailscale/tailscale_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscaled/childproc/childproc.go b/cmd/tailscaled/childproc/childproc.go index cc83a06c6ee7c..7d89b314af820 100644 --- a/cmd/tailscaled/childproc/childproc.go +++ b/cmd/tailscaled/childproc/childproc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package childproc allows other packages to register "tailscaled be-child" diff --git a/cmd/tailscaled/debug.go b/cmd/tailscaled/debug.go index 8208a6e3c6354..360075f5b0e2b 100644 --- a/cmd/tailscaled/debug.go +++ b/cmd/tailscaled/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debug diff --git a/cmd/tailscaled/debug_forcereflect.go b/cmd/tailscaled/debug_forcereflect.go index 7378753ceb64c..088010d7db29a 100644 --- a/cmd/tailscaled/debug_forcereflect.go +++ b/cmd/tailscaled/debug_forcereflect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_debug_forcereflect diff --git a/cmd/tailscaled/depaware-min.txt b/cmd/tailscaled/depaware-min.txt index a2d20dedaf243..2ad5cbca7b3af 100644 --- a/cmd/tailscaled/depaware-min.txt +++ b/cmd/tailscaled/depaware-min.txt @@ -1,8 +1,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/drive+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ @@ -35,7 +41,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de đŸ’Ŗ go4.org/mem from tailscale.com/control/controlbase+ go4.org/netipx from tailscale.com/ipn/ipnlocal+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnauth+ tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled @@ -58,15 +64,15 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled tailscale.com/feature/condregister/portmapper from tailscale.com/feature/condregister tailscale.com/feature/condregister/useproxy from tailscale.com/feature/condregister - tailscale.com/feature/conn25 from tailscale.com/feature/condregister tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/cmd/tailscaled+ tailscale.com/ipn from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/conffile from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ - tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal+ + tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+ tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver @@ -225,7 +231,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/derp vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -243,12 +249,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de bufio from compress/flate+ bytes from bufio+ cmp from encoding/json+ - compress/flate from compress/gzip + compress/flate from compress/gzip+ compress/gzip from net/http container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -256,13 +262,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/fips140+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -277,7 +284,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/hkdf+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/ecdsa+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -291,19 +298,21 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from net/http+ @@ -339,9 +348,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from runtime internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -352,14 +360,16 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/runtime/atomic from internal/runtime/exithook+ internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime internal/runtime/sys from crypto/subtle+ - internal/runtime/syscall from internal/runtime/cgroup+ + internal/runtime/syscall/linux from internal/runtime/cgroup+ internal/saferio from encoding/asn1 internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -397,7 +407,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de os/user from tailscale.com/ipn/ipnauth+ path from io/fs+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from encoding/asn1+ runtime from crypto/internal/fips140+ runtime/debug from github.com/klauspost/compress/zstd+ slices from crypto/tls+ diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 4b2f71983d441..64911d9318f03 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -1,10 +1,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) - filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus - filippo.io/edwards25519/field from filippo.io/edwards25519 + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/drive+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ @@ -12,7 +16,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+ github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign đŸ’Ŗ github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli @@ -24,8 +27,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd from tailscale.com/util/zstdframe github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd - github.com/mattn/go-colorable from tailscale.com/cmd/tailscale/cli - github.com/mattn/go-isatty from github.com/mattn/go-colorable+ + github.com/mattn/go-isatty from tailscale.com/util/prompt đŸ’Ŗ github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ đŸ’Ŗ github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+ đŸ’Ŗ github.com/mdlayher/socket from github.com/mdlayher/netlink @@ -41,20 +43,18 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+ github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device đŸ’Ŗ github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+ - github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli đŸ’Ŗ go4.org/mem from tailscale.com/control/controlbase+ go4.org/netipx from tailscale.com/ipn/ipnlocal+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/local from tailscale.com/client/tailscale+ tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnauth+ - tailscale.com/clientupdate from tailscale.com/cmd/tailscale/cli - tailscale.com/clientupdate/distsign from tailscale.com/clientupdate tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscaled tailscale.com/cmd/tailscale/cli/ffcomplete from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscale/cli/ffcomplete/internal from tailscale.com/cmd/tailscale/cli/ffcomplete + tailscale.com/cmd/tailscale/cli/jsonoutput from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled tailscale.com/control/controlbase from tailscale.com/control/controlhttp+ tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+ @@ -73,11 +73,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/feature/buildfeatures from tailscale.com/ipn/ipnlocal+ tailscale.com/feature/condlite/expvar from tailscale.com/wgengine/magicsock tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled + tailscale.com/feature/condregister/awsparamstore from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/identityfederation from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/oauthkey from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/portmapper from tailscale.com/feature/condregister+ tailscale.com/feature/condregister/useproxy from tailscale.com/cmd/tailscale/cli+ - tailscale.com/feature/conn25 from tailscale.com/feature/condregister tailscale.com/health from tailscale.com/control/controlclient+ tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ tailscale.com/hostinfo from tailscale.com/cmd/tailscaled+ @@ -85,8 +85,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/ipn from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/conffile from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ - tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal+ + tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+ tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver @@ -174,7 +175,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/clientmetric from tailscale.com/appc+ tailscale.com/util/cloudenv from tailscale.com/hostinfo+ tailscale.com/util/cloudinfo from tailscale.com/wgengine/magicsock - tailscale.com/util/cmpver from tailscale.com/clientupdate tailscale.com/util/ctxkey from tailscale.com/client/tailscale/apitype+ tailscale.com/util/dnsname from tailscale.com/appc+ tailscale.com/util/eventbus from tailscale.com/client/local+ @@ -252,7 +252,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/derp vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -267,16 +267,15 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de vendor/golang.org/x/text/transform from vendor/golang.org/x/text/secure/bidirule+ vendor/golang.org/x/text/unicode/bidi from vendor/golang.org/x/net/idna+ vendor/golang.org/x/text/unicode/norm from vendor/golang.org/x/net/idna - archive/tar from tailscale.com/clientupdate bufio from compress/flate+ bytes from bufio+ cmp from encoding/json+ - compress/flate from compress/gzip + compress/flate from compress/gzip+ compress/gzip from net/http+ container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -284,13 +283,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/fips140+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -305,7 +305,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/hkdf+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/ecdsa+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -319,19 +319,21 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from net/http+ @@ -367,9 +369,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from runtime internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -380,14 +381,16 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/runtime/atomic from internal/runtime/exithook+ internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime internal/runtime/sys from crypto/subtle+ - internal/runtime/syscall from internal/runtime/cgroup+ + internal/runtime/syscall/linux from internal/runtime/cgroup+ internal/saferio from encoding/asn1 internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -426,9 +429,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de os/user from tailscale.com/ipn/ipnauth+ path from io/fs+ path/filepath from crypto/x509+ - reflect from crypto/x509+ - regexp from tailscale.com/clientupdate - regexp/syntax from regexp + reflect from encoding/asn1+ runtime from crypto/internal/fips140+ runtime/debug from github.com/klauspost/compress/zstd+ slices from crypto/tls+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 43165ea36c6d3..207d86243b607 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 W đŸ’Ŗ github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ @@ -98,8 +99,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de đŸ’Ŗ github.com/djherbis/times from tailscale.com/drive/driveimpl github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/gaissmai/bart from tailscale.com/net/tstun+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+ @@ -110,7 +116,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W đŸ’Ŗ github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet L đŸ’Ŗ github.com/godbus/dbus/v5 from tailscale.com/net/dns+ github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ + github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/transport/tcp github.com/google/go-tpm/legacy/tpm2 from github.com/google/go-tpm/tpm2/transport+ github.com/google/go-tpm/tpm2 from tailscale.com/feature/tpm github.com/google/go-tpm/tpm2/transport from github.com/google/go-tpm/tpm2/transport/linuxtpm+ @@ -145,7 +151,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/klauspost/compress from github.com/klauspost/compress/zstd github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0 github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd - github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/huff0+ + github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd+ đŸ’Ŗ github.com/klauspost/compress/internal/le from github.com/klauspost/compress/huff0+ github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd from tailscale.com/util/zstdframe @@ -215,7 +221,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de đŸ’Ŗ gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+ gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state đŸ’Ŗ gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+ - đŸ’Ŗ gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack + đŸ’Ŗ gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack+ gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack đŸ’Ŗ gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+ @@ -244,7 +250,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de gvisor.dev/gvisor/pkg/tcpip/transport/udp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal đŸ’Ŗ tailscale.com/atomicfile from tailscale.com/ipn+ LD tailscale.com/chirp from tailscale.com/cmd/tailscaled tailscale.com/client/local from tailscale.com/client/web+ @@ -304,7 +310,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/feature/useproxy from tailscale.com/feature/condregister/useproxy tailscale.com/feature/wakeonlan from tailscale.com/feature/condregister tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/ipn from tailscale.com/client/local+ W tailscale.com/ipn/auditlog from tailscale.com/cmd/tailscaled @@ -313,6 +319,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de đŸ’Ŗ tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn/ipnext from tailscale.com/ipn/auditlog+ tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/client/local+ tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver+ @@ -423,7 +430,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/cibuild from tailscale.com/health+ tailscale.com/util/clientmetric from tailscale.com/control/controlclient+ tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+ - tailscale.com/util/cloudinfo from tailscale.com/wgengine/magicsock + tailscale.com/util/cloudinfo from tailscale.com/wgengine/magicsock+ tailscale.com/util/cmpver from tailscale.com/net/dns+ tailscale.com/util/ctxkey from tailscale.com/ipn/ipnlocal+ đŸ’Ŗ tailscale.com/util/deephash from tailscale.com/util/syspolicy/setting @@ -467,7 +474,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/testenv from tailscale.com/ipn/ipnlocal+ tailscale.com/util/truncate from tailscale.com/logtail tailscale.com/util/usermetric from tailscale.com/health+ - tailscale.com/util/vizerror from tailscale.com/tailcfg+ + tailscale.com/util/vizerror from tailscale.com/tsweb+ đŸ’Ŗ tailscale.com/util/winutil from tailscale.com/clientupdate+ W đŸ’Ŗ tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+ W đŸ’Ŗ tailscale.com/util/winutil/gp from tailscale.com/net/dns+ @@ -475,7 +482,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W đŸ’Ŗ tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+ tailscale.com/util/zstdframe from tailscale.com/control/controlclient+ tailscale.com/version from tailscale.com/client/web+ - tailscale.com/version/distro from tailscale.com/client/web+ + tailscale.com/version/distro from tailscale.com/hostinfo+ W tailscale.com/wf from tailscale.com/cmd/tailscaled tailscale.com/wgengine from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ @@ -540,7 +547,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -566,7 +573,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509+ @@ -575,12 +582,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ crypto/fips140 from crypto/tls/internal/fips140tls+ - crypto/hkdf from crypto/internal/hpke+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -595,7 +603,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls+ + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -609,20 +617,21 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ - LD crypto/mlkem from golang.org/x/crypto/ssh + crypto/mlkem from golang.org/x/crypto/ssh+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls+ crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from github.com/aws/aws-sdk-go-v2/aws/transport/http+ @@ -666,9 +675,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from archive/tar+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -681,14 +689,17 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync diff --git a/cmd/tailscaled/deps_test.go b/cmd/tailscaled/deps_test.go index 64d1beca7cd75..be4f65a7dd576 100644 --- a/cmd/tailscaled/deps_test.go +++ b/cmd/tailscaled/deps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main @@ -265,7 +265,6 @@ func TestMinTailscaledWithCLI(t *testing.T) { badSubstrs := []string{ "cbor", "hujson", - "pprof", "multierr", // https://github.com/tailscale/tailscale/pull/17379 "tailscale.com/metrics", "tailscale.com/tsweb/varz", @@ -285,9 +284,20 @@ func TestMinTailscaledWithCLI(t *testing.T) { } }, BadDeps: map[string]string{ - "golang.org/x/net/http2": "unexpected x/net/http2 dep; tailscale/tailscale#17305", - "expvar": "unexpected expvar dep", - "github.com/mdlayher/genetlink": "unexpected genetlink dep", + "golang.org/x/net/http2": "unexpected x/net/http2 dep; tailscale/tailscale#17305", + "expvar": "unexpected expvar dep", + "runtime/pprof": "unexpected runtime/pprof dep", + "net/http/pprof": "unexpected net/http/pprof dep", + "github.com/mdlayher/genetlink": "unexpected genetlink dep", + "tailscale.com/clientupdate": "unexpected clientupdate dep", + "filippo.io/edwards25519": "unexpected edwards25519 dep", + "github.com/hdevalence/ed25519consensus": "unexpected ed25519consensus dep", + "tailscale.com/clientupdate/distsign": "unexpected distsign dep", + "archive/tar": "unexpected archive/tar dep", + "tailscale.com/feature/conn25": "unexpected conn25 dep", + "regexp": "unexpected regexp dep; bloats binary", + "github.com/toqueteos/webbrowser": "unexpected webbrowser dep with ts_omit_webbrowser", + "github.com/mattn/go-colorable": "unexpected go-colorable dep with ts_omit_colorable", }, }.Check(t) } diff --git a/cmd/tailscaled/flag.go b/cmd/tailscaled/flag.go index f640aceed45d8..357210a29c426 100644 --- a/cmd/tailscaled/flag.go +++ b/cmd/tailscaled/flag.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscaled/generate.go b/cmd/tailscaled/generate.go index 5c2e9be915980..36a4fa671dddb 100644 --- a/cmd/tailscaled/generate.go +++ b/cmd/tailscaled/generate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscaled/install_darwin.go b/cmd/tailscaled/install_darwin.go index 05e5eaed8af90..15d9e54621181 100644 --- a/cmd/tailscaled/install_darwin.go +++ b/cmd/tailscaled/install_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/cmd/tailscaled/install_windows.go b/cmd/tailscaled/install_windows.go index 6013660f5aa20..d0f40b37d1156 100644 --- a/cmd/tailscaled/install_windows.go +++ b/cmd/tailscaled/install_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/cmd/tailscaled/netstack.go b/cmd/tailscaled/netstack.go index c0b34ed411c78..d896f384fcc98 100644 --- a/cmd/tailscaled/netstack.go +++ b/cmd/tailscaled/netstack.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netstack diff --git a/cmd/tailscaled/proxy.go b/cmd/tailscaled/proxy.go index 85c3d91f9de96..ea9f54a479dc5 100644 --- a/cmd/tailscaled/proxy.go +++ b/cmd/tailscaled/proxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_outboundproxy diff --git a/cmd/tailscaled/required_version.go b/cmd/tailscaled/required_version.go index 3acb3d52e4d8c..bfde77cd8474b 100644 --- a/cmd/tailscaled/required_version.go +++ b/cmd/tailscaled/required_version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !go1.23 diff --git a/cmd/tailscaled/sigpipe.go b/cmd/tailscaled/sigpipe.go index 2fcdab2a4660e..ba69fcd2a0632 100644 --- a/cmd/tailscaled/sigpipe.go +++ b/cmd/tailscaled/sigpipe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.21 && !plan9 diff --git a/cmd/tailscaled/ssh.go b/cmd/tailscaled/ssh.go index 59a1ddd0df461..e69cbd5dce086 100644 --- a/cmd/tailscaled/ssh.go +++ b/cmd/tailscaled/ssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux || darwin || freebsd || openbsd || plan9) && !ts_omit_ssh diff --git a/cmd/tailscaled/tailscale-online.target b/cmd/tailscaled/tailscale-online.target new file mode 100644 index 0000000000000..a8ee7db475378 --- /dev/null +++ b/cmd/tailscaled/tailscale-online.target @@ -0,0 +1,4 @@ +[Unit] +Description=Tailscale is online +Requires=tailscale-wait-online.service +After=tailscale-wait-online.service diff --git a/cmd/tailscaled/tailscale-wait-online.service b/cmd/tailscaled/tailscale-wait-online.service new file mode 100644 index 0000000000000..eb46a18bf92d2 --- /dev/null +++ b/cmd/tailscaled/tailscale-wait-online.service @@ -0,0 +1,12 @@ +[Unit] +Description=Wait for Tailscale to be online +After=tailscaled.service +Requires=tailscaled.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/tailscale wait +RemainAfterExit=yes + +[Install] +WantedBy=tailscale-online.target diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 410ae00bc0716..df0d68e077b2b 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.23 diff --git a/cmd/tailscaled/tailscaled.service b/cmd/tailscaled/tailscaled.service index 719a3c0c96398..9950891a35fa4 100644 --- a/cmd/tailscaled/tailscaled.service +++ b/cmd/tailscaled/tailscaled.service @@ -1,6 +1,6 @@ [Unit] Description=Tailscale node agent -Documentation=https://tailscale.com/kb/ +Documentation=https://tailscale.com/docs/ Wants=network-pre.target After=network-pre.target NetworkManager.service systemd-resolved.service diff --git a/cmd/tailscaled/tailscaled_bird.go b/cmd/tailscaled/tailscaled_bird.go index c76f77bec6e36..c1c32d2bb493d 100644 --- a/cmd/tailscaled/tailscaled_bird.go +++ b/cmd/tailscaled/tailscaled_bird.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && (linux || darwin || freebsd || openbsd) && !ts_omit_bird diff --git a/cmd/tailscaled/tailscaled_drive.go b/cmd/tailscaled/tailscaled_drive.go index 49f35a3811404..6a8590bb82217 100644 --- a/cmd/tailscaled/tailscaled_drive.go +++ b/cmd/tailscaled/tailscaled_drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/cmd/tailscaled/tailscaled_notwindows.go b/cmd/tailscaled/tailscaled_notwindows.go index d5361cf286d3d..735facc37b861 100644 --- a/cmd/tailscaled/tailscaled_notwindows.go +++ b/cmd/tailscaled/tailscaled_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && go1.19 diff --git a/cmd/tailscaled/tailscaled_test.go b/cmd/tailscaled/tailscaled_test.go index 36327cccc7bc7..7d76e7683a623 100644 --- a/cmd/tailscaled/tailscaled_test.go +++ b/cmd/tailscaled/tailscaled_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main // import "tailscale.com/cmd/tailscaled" diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 3019bbaf9695b..63c8b30c99348 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/cmd/tailscaled/tailscaledhooks/tailscaledhooks.go b/cmd/tailscaled/tailscaledhooks/tailscaledhooks.go index 6ea662d39230c..42009d02bf6af 100644 --- a/cmd/tailscaled/tailscaledhooks/tailscaledhooks.go +++ b/cmd/tailscaled/tailscaledhooks/tailscaledhooks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tailscaledhooks provides hooks for optional features diff --git a/cmd/tailscaled/webclient.go b/cmd/tailscaled/webclient.go index 672ba7126d2a7..e031277abfc27 100644 --- a/cmd/tailscaled/webclient.go +++ b/cmd/tailscaled/webclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_webclient diff --git a/cmd/tailscaled/with_cli.go b/cmd/tailscaled/with_cli.go index a8554eb8ce9dc..33da1f448e727 100644 --- a/cmd/tailscaled/with_cli.go +++ b/cmd/tailscaled/with_cli.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_include_cli diff --git a/cmd/testcontrol/testcontrol.go b/cmd/testcontrol/testcontrol.go index b05b3128df0ef..49e7e429e63e9 100644 --- a/cmd/testcontrol/testcontrol.go +++ b/cmd/testcontrol/testcontrol.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Program testcontrol runs a simple test control server. diff --git a/cmd/testwrapper/args.go b/cmd/testwrapper/args.go index 95157bc34efee..22e5d4c902f8e 100644 --- a/cmd/testwrapper/args.go +++ b/cmd/testwrapper/args.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main @@ -8,6 +8,7 @@ import ( "io" "os" "slices" + "strconv" "strings" "testing" ) @@ -60,6 +61,9 @@ func splitArgs(args []string) (pre, pkgs, post []string, _ error) { return nil, nil, nil, err } fs.Visit(func(f *flag.Flag) { + if f.Name == "cachelink" && !cacheLink.enabled { + return + } if f.Value.String() != f.DefValue && f.DefValue != "false" { pre = append(pre, "-"+f.Name, f.Value.String()) } else { @@ -79,6 +83,37 @@ func splitArgs(args []string) (pre, pkgs, post []string, _ error) { return pre, pkgs, post, nil } +// cacheLink is whether the -cachelink flag is enabled. +// +// The -cachelink flag is Tailscale-specific addition to the "go test" command; +// see https://github.com/tailscale/go/issues/149 and +// https://github.com/golang/go/issues/77349. +// +// In that PR, it's only a boolean, but we implement a custom flag type +// so we can support -cachelink=auto, which enables cachelink if GOCACHEPROG +// is set, which is a behavior we want in our CI environment. +var cacheLink cacheLinkVal + +type cacheLinkVal struct { + enabled bool +} + +func (c *cacheLinkVal) String() string { + return strconv.FormatBool(c.enabled) +} + +func (c *cacheLinkVal) Set(s string) error { + if s == "auto" { + c.enabled = os.Getenv("GOCACHEPROG") != "" + return nil + } + var err error + c.enabled, err = strconv.ParseBool(s) + return err +} + +func (*cacheLinkVal) IsBoolFlag() bool { return true } + func newTestFlagSet() *flag.FlagSet { fs := flag.NewFlagSet("testwrapper", flag.ContinueOnError) fs.SetOutput(io.Discard) @@ -89,6 +124,9 @@ func newTestFlagSet() *flag.FlagSet { // TODO(maisem): figure out what other flags we need to register explicitly. fs.String("exec", "", "Command to run tests with") fs.Bool("race", false, "build with race detector") + fs.String("vet", "", "vet checks to run, or 'off' or 'all'") + + fs.Var(&cacheLink, "cachelink", "Go -cachelink value (bool); or 'auto' to enable if GOCACHEPROG is set") return fs } diff --git a/cmd/testwrapper/args_test.go b/cmd/testwrapper/args_test.go index 10063d7bcf6e1..25364fb96d6a1 100644 --- a/cmd/testwrapper/args_test.go +++ b/cmd/testwrapper/args_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/testwrapper/flakytest/flakytest.go b/cmd/testwrapper/flakytest/flakytest.go index 856cb28ef275a..5e1591e817e00 100644 --- a/cmd/testwrapper/flakytest/flakytest.go +++ b/cmd/testwrapper/flakytest/flakytest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package flakytest contains test helpers for marking a test as flaky. For @@ -11,6 +11,7 @@ import ( "os" "path" "regexp" + "strconv" "sync" "testing" @@ -60,6 +61,10 @@ func Mark(t testing.TB, issue string) { // And then remove this Logf a month or so after that. t.Logf("flakytest: issue tracking this flaky test: %s", issue) + if boolEnv("TS_SKIP_FLAKY_TESTS") { + t.Skipf("skipping due to TS_SKIP_FLAKY_TESTS") + } + // Record the root test name as flakey. rootFlakesMu.Lock() defer rootFlakesMu.Unlock() @@ -80,3 +85,12 @@ func Marked(t testing.TB) bool { } return false } + +func boolEnv(k string) bool { + s := os.Getenv(k) + if s == "" { + return false + } + v, _ := strconv.ParseBool(s) + return v +} diff --git a/cmd/testwrapper/flakytest/flakytest_test.go b/cmd/testwrapper/flakytest/flakytest_test.go index 9b744de13d446..54dd2121bd1f3 100644 --- a/cmd/testwrapper/flakytest/flakytest_test.go +++ b/cmd/testwrapper/flakytest/flakytest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package flakytest diff --git a/cmd/testwrapper/testwrapper.go b/cmd/testwrapper/testwrapper.go index 173edee733f04..e35b83407bbb8 100644 --- a/cmd/testwrapper/testwrapper.go +++ b/cmd/testwrapper/testwrapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // testwrapper is a wrapper for retrying flaky tests. It is an alternative to @@ -36,6 +36,7 @@ type testAttempt struct { pkg string // "tailscale.com/types/key" testName string // "TestFoo" outcome string // "pass", "fail", "skip" + cached bool // whether package-level (non-testName specific) was pass due to being cached logs bytes.Buffer start, end time.Time isMarkedFlaky bool // set if the test is marked as flaky @@ -108,6 +109,8 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te os.Exit(1) } + pkgCached := map[string]bool{} + s := bufio.NewScanner(r) resultMap := make(map[string]map[string]*testAttempt) // pkg -> test -> testAttempt for s.Scan() { @@ -127,6 +130,12 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te resultMap[pkg] = pkgTests } if goOutput.Test == "" { + // Detect output lines like: + // ok \ttailscale.com/cmd/testwrapper\t(cached) + // ok \ttailscale.com/cmd/testwrapper\t(cached)\tcoverage: 17.0% of statements + if goOutput.Package != "" && strings.Contains(goOutput.Output, fmt.Sprintf("%s\t(cached)", goOutput.Package)) { + pkgCached[goOutput.Package] = true + } switch goOutput.Action { case "start": pkgTests[""].start = goOutput.Time @@ -151,6 +160,7 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te end: goOutput.Time, logs: pkgTests[""].logs, pkgFinished: true, + cached: pkgCached[goOutput.Package], } case "output": // Capture all output from the package except for the final @@ -235,7 +245,7 @@ func main() { firstRun.tests = append(firstRun.tests, &packageTests{Pattern: pkg}) } toRun := []*nextRun{firstRun} - printPkgOutcome := func(pkg, outcome string, attempt int, runtime time.Duration) { + printPkgOutcome := func(pkg, outcome string, cached bool, attempt int, testDur time.Duration) { if pkg == "" { return // We reach this path on a build error. } @@ -250,10 +260,16 @@ func main() { outcome = "FAIL" } if attempt > 1 { - fmt.Printf("%s\t%s\t%.3fs\t[attempt=%d]\n", outcome, pkg, runtime.Seconds(), attempt) + fmt.Printf("%s\t%s\t%.3fs\t[attempt=%d]\n", outcome, pkg, testDur.Seconds(), attempt) return } - fmt.Printf("%s\t%s\t%.3fs\n", outcome, pkg, runtime.Seconds()) + var lastCol string + if cached { + lastCol = "(cached)" + } else { + lastCol = fmt.Sprintf("%.3f", testDur.Seconds()) + } + fmt.Printf("%s\t%s\t%v\n", outcome, pkg, lastCol) } for len(toRun) > 0 { @@ -300,7 +316,7 @@ func main() { // panics outside tests will be printed io.Copy(os.Stdout, &tr.logs) } - printPkgOutcome(tr.pkg, tr.outcome, thisRun.attempt, tr.end.Sub(tr.start)) + printPkgOutcome(tr.pkg, tr.outcome, tr.cached, thisRun.attempt, tr.end.Sub(tr.start)) continue } if testingVerbose || tr.outcome == "fail" { diff --git a/cmd/testwrapper/testwrapper_test.go b/cmd/testwrapper/testwrapper_test.go index ace53ccd0e09a..cf023f4367483 100644 --- a/cmd/testwrapper/testwrapper_test.go +++ b/cmd/testwrapper/testwrapper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main_test @@ -11,6 +11,7 @@ import ( "os/exec" "path/filepath" "regexp" + "runtime" "strings" "sync" "testing" @@ -214,6 +215,63 @@ func TestTimeout(t *testing.T) { } } +func TestCached(t *testing.T) { + t.Parallel() + + // Construct our trivial package. + pkgDir := t.TempDir() + goMod := fmt.Sprintf(`module example.com + +go %s +`, runtime.Version()[2:]) // strip leading "go" + + test := `package main +import "testing" + +func TestCached(t *testing.T) {} +` + + for f, c := range map[string]string{ + "go.mod": goMod, + "cached_test.go": test, + } { + err := os.WriteFile(filepath.Join(pkgDir, f), []byte(c), 0o644) + if err != nil { + t.Fatalf("writing package: %s", err) + } + } + + for name, args := range map[string][]string{ + "without_flags": {"./..."}, + "with_short": {"./...", "-short"}, + "with_coverprofile": {"./...", "-coverprofile=" + filepath.Join(t.TempDir(), "coverage.out")}, + } { + t.Run(name, func(t *testing.T) { + var ( + out []byte + err error + ) + for range 2 { + cmd := cmdTestwrapper(t, args...) + cmd.Dir = pkgDir + out, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("testwrapper ./...: expected no error but got: %v; output was:\n%s", err, out) + } + } + + want := []byte("ok\texample.com\t(cached)") + if !bytes.Contains(out, want) { + t.Fatalf("wanted output containing %q but got:\n%s", want, out) + } + + if testing.Verbose() { + t.Logf("success - output:\n%s", out) + } + }) + } +} + func errExitCode(err error) (int, bool) { var exit *exec.ExitError if errors.As(err, &exit) { diff --git a/cmd/tl-longchain/tl-longchain.go b/cmd/tl-longchain/tl-longchain.go index 384d24222e6d5..33d0df3011018 100644 --- a/cmd/tl-longchain/tl-longchain.go +++ b/cmd/tl-longchain/tl-longchain.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Program tl-longchain prints commands to re-sign Tailscale nodes that have diff --git a/cmd/tsconnect/build-pkg.go b/cmd/tsconnect/build-pkg.go index 047504858ae0c..53aacc02ec8ea 100644 --- a/cmd/tsconnect/build-pkg.go +++ b/cmd/tsconnect/build-pkg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/build.go b/cmd/tsconnect/build.go index 364ebf5366dfe..64b6b3582bd62 100644 --- a/cmd/tsconnect/build.go +++ b/cmd/tsconnect/build.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/common.go b/cmd/tsconnect/common.go index ff10e4efbb5d3..bc9e1ed4ff532 100644 --- a/cmd/tsconnect/common.go +++ b/cmd/tsconnect/common.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -269,7 +269,7 @@ func runWasmOpt(path string) error { return fmt.Errorf("Cannot stat %v: %w", path, err) } startSize := stat.Size() - cmd := exec.Command("../../tool/wasm-opt", "--enable-bulk-memory", "-Oz", path, "-o", path) + cmd := exec.Command("../../tool/wasm-opt", "--enable-bulk-memory", "--enable-nontrapping-float-to-int", "-Oz", path, "-o", path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() diff --git a/cmd/tsconnect/dev-pkg.go b/cmd/tsconnect/dev-pkg.go index de534c3b20625..3e1018c00df0e 100644 --- a/cmd/tsconnect/dev-pkg.go +++ b/cmd/tsconnect/dev-pkg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/dev.go b/cmd/tsconnect/dev.go index 87b10adaf49c8..2d0eb1036a5ad 100644 --- a/cmd/tsconnect/dev.go +++ b/cmd/tsconnect/dev.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/package.json.tmpl b/cmd/tsconnect/package.json.tmpl index 404b896eaf89e..883d794cacb8b 100644 --- a/cmd/tsconnect/package.json.tmpl +++ b/cmd/tsconnect/package.json.tmpl @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Template for the package.json that is generated by the build-pkg command. diff --git a/cmd/tsconnect/serve.go b/cmd/tsconnect/serve.go index d780bdd57c3e3..3e9f097a248a4 100644 --- a/cmd/tsconnect/serve.go +++ b/cmd/tsconnect/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/src/app/app.tsx b/cmd/tsconnect/src/app/app.tsx index ee538eaeac506..8d25b227437dd 100644 --- a/cmd/tsconnect/src/app/app.tsx +++ b/cmd/tsconnect/src/app/app.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { render, Component } from "preact" diff --git a/cmd/tsconnect/src/app/go-panic-display.tsx b/cmd/tsconnect/src/app/go-panic-display.tsx index 5dd7095a27c7d..e15c58cd183ff 100644 --- a/cmd/tsconnect/src/app/go-panic-display.tsx +++ b/cmd/tsconnect/src/app/go-panic-display.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause export function GoPanicDisplay({ diff --git a/cmd/tsconnect/src/app/header.tsx b/cmd/tsconnect/src/app/header.tsx index 099ff2f8c2f7d..640474090e3a4 100644 --- a/cmd/tsconnect/src/app/header.tsx +++ b/cmd/tsconnect/src/app/header.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause export function Header({ state, ipn }: { state: IPNState; ipn?: IPN }) { diff --git a/cmd/tsconnect/src/app/index.css b/cmd/tsconnect/src/app/index.css index 751b313d9f362..2c9c4c0d3247b 100644 --- a/cmd/tsconnect/src/app/index.css +++ b/cmd/tsconnect/src/app/index.css @@ -1,4 +1,4 @@ -/* Copyright (c) Tailscale Inc & AUTHORS */ +/* Copyright (c) Tailscale Inc & contributors */ /* SPDX-License-Identifier: BSD-3-Clause */ @import "xterm/css/xterm.css"; diff --git a/cmd/tsconnect/src/app/index.ts b/cmd/tsconnect/src/app/index.ts index 24ca4543921ae..bdbcaf3e52d4a 100644 --- a/cmd/tsconnect/src/app/index.ts +++ b/cmd/tsconnect/src/app/index.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import "../wasm_exec" diff --git a/cmd/tsconnect/src/app/ssh.tsx b/cmd/tsconnect/src/app/ssh.tsx index df81745bd3fd7..1faaad6c68220 100644 --- a/cmd/tsconnect/src/app/ssh.tsx +++ b/cmd/tsconnect/src/app/ssh.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useState, useCallback, useMemo, useEffect, useRef } from "preact/hooks" diff --git a/cmd/tsconnect/src/app/url-display.tsx b/cmd/tsconnect/src/app/url-display.tsx index fc82c7fb91b3c..787989ccae018 100644 --- a/cmd/tsconnect/src/app/url-display.tsx +++ b/cmd/tsconnect/src/app/url-display.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useState } from "preact/hooks" diff --git a/cmd/tsconnect/src/lib/js-state-store.ts b/cmd/tsconnect/src/lib/js-state-store.ts index e57dfd98efabd..a17090fa0921d 100644 --- a/cmd/tsconnect/src/lib/js-state-store.ts +++ b/cmd/tsconnect/src/lib/js-state-store.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /** @fileoverview Callbacks used by jsStateStore to persist IPN state. */ diff --git a/cmd/tsconnect/src/lib/ssh.ts b/cmd/tsconnect/src/lib/ssh.ts index 9c6f71aee4b41..5fae4a5b7451f 100644 --- a/cmd/tsconnect/src/lib/ssh.ts +++ b/cmd/tsconnect/src/lib/ssh.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { Terminal, ITerminalOptions } from "xterm" diff --git a/cmd/tsconnect/src/pkg/pkg.css b/cmd/tsconnect/src/pkg/pkg.css index 76ea21f5b53b2..f3b32bb95e808 100644 --- a/cmd/tsconnect/src/pkg/pkg.css +++ b/cmd/tsconnect/src/pkg/pkg.css @@ -1,4 +1,4 @@ -/* Copyright (c) Tailscale Inc & AUTHORS */ +/* Copyright (c) Tailscale Inc & contributors */ /* SPDX-License-Identifier: BSD-3-Clause */ @import "xterm/css/xterm.css"; diff --git a/cmd/tsconnect/src/pkg/pkg.ts b/cmd/tsconnect/src/pkg/pkg.ts index 4d535cb404015..a44c57150ce24 100644 --- a/cmd/tsconnect/src/pkg/pkg.ts +++ b/cmd/tsconnect/src/pkg/pkg.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Type definitions need to be manually imported for dts-bundle-generator to diff --git a/cmd/tsconnect/src/types/esbuild.d.ts b/cmd/tsconnect/src/types/esbuild.d.ts index ef28f7b1cf556..d6bbd9310ead7 100644 --- a/cmd/tsconnect/src/types/esbuild.d.ts +++ b/cmd/tsconnect/src/types/esbuild.d.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /** diff --git a/cmd/tsconnect/src/types/wasm_js.d.ts b/cmd/tsconnect/src/types/wasm_js.d.ts index 492197ccb1a9b..938ec759c7615 100644 --- a/cmd/tsconnect/src/types/wasm_js.d.ts +++ b/cmd/tsconnect/src/types/wasm_js.d.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /** diff --git a/cmd/tsconnect/tsconnect.go b/cmd/tsconnect/tsconnect.go index ef55593b49268..6de1f26ad389f 100644 --- a/cmd/tsconnect/tsconnect.go +++ b/cmd/tsconnect/tsconnect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index c7aa00d1d794f..8a0177d1d66f7 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The wasm package builds a WebAssembly module that provides a subset of diff --git a/cmd/tsconnect/yarn.lock b/cmd/tsconnect/yarn.lock index d9d9db32f66a0..5ab282dcb2ead 100644 --- a/cmd/tsconnect/yarn.lock +++ b/cmd/tsconnect/yarn.lock @@ -89,7 +89,7 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -336,11 +336,11 @@ merge2@^1.3.0: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" minimist@^1.2.6: @@ -348,10 +348,10 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -nanoid@^3.3.4: - version "3.3.8" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" - integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -397,6 +397,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -457,13 +462,13 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.4.14: - version "8.4.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" - integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" preact@^10.10.0: version "10.10.0" @@ -540,10 +545,10 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index e29ae93484c95..bb991383c8a06 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depaware) + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 W đŸ’Ŗ github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ @@ -88,8 +89,13 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar W đŸ’Ŗ github.com/dblohm7/wingoes/pe from tailscale.com/util/osdiag+ github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ @@ -98,7 +104,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+ L đŸ’Ŗ github.com/godbus/dbus/v5 from tailscale.com/net/dns github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ + github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/transport/tcp D github.com/google/uuid from github.com/prometheus-community/pro-bing github.com/hdevalence/ed25519consensus from tailscale.com/tka github.com/huin/goupnp from github.com/huin/goupnp/dcps/internetgateway2+ @@ -165,7 +171,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar đŸ’Ŗ gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+ gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state đŸ’Ŗ gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+ - đŸ’Ŗ gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack + đŸ’Ŗ gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack+ gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack đŸ’Ŗ gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+ @@ -227,7 +233,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar tailscale.com/feature/syspolicy from tailscale.com/logpolicy tailscale.com/feature/useproxy from tailscale.com/feature/condregister/useproxy tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/internal/client/tailscale from tailscale.com/tsnet+ tailscale.com/ipn from tailscale.com/client/local+ @@ -235,6 +241,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar đŸ’Ŗ tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnstate from tailscale.com/client/local+ tailscale.com/ipn/localapi from tailscale.com/tsnet tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+ @@ -442,7 +449,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -467,7 +474,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509+ @@ -476,12 +483,13 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ crypto/fips140 from crypto/tls/internal/fips140tls+ - crypto/hkdf from crypto/internal/hpke+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -496,7 +504,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls+ + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -510,20 +518,21 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ - LD crypto/mlkem from golang.org/x/crypto/ssh + crypto/mlkem from golang.org/x/crypto/ssh+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls+ crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from github.com/prometheus-community/pro-bing+ @@ -567,9 +576,8 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -582,14 +590,17 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -633,7 +644,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar os/user from github.com/godbus/dbus/v5+ path from debug/dwarf+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from database/sql/driver+ regexp from github.com/huin/goupnp/httpu+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/cmd/tsidp/tsidp.go b/cmd/tsidp/tsidp.go index 7093ab9ee193a..d6dfc79009796 100644 --- a/cmd/tsidp/tsidp.go +++ b/cmd/tsidp/tsidp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tsidp command is an OpenID Connect Identity Provider server. diff --git a/cmd/tsidp/tsidp_test.go b/cmd/tsidp/tsidp_test.go index 4f5af9e598e65..26c906fab216b 100644 --- a/cmd/tsidp/tsidp_test.go +++ b/cmd/tsidp/tsidp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package main tests for tsidp focus on OAuth security boundaries and diff --git a/cmd/tsidp/ui.go b/cmd/tsidp/ui.go index d37b64990cac8..f8717d65e8509 100644 --- a/cmd/tsidp/ui.go +++ b/cmd/tsidp/ui.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tsshd/tsshd.go b/cmd/tsshd/tsshd.go index 950eb661cdb23..51765e2e41526 100644 --- a/cmd/tsshd/tsshd.go +++ b/cmd/tsshd/tsshd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/cmd/tta/fw_linux.go b/cmd/tta/fw_linux.go index a4ceabad8bc05..49d8d41ea4b4d 100644 --- a/cmd/tta/fw_linux.go +++ b/cmd/tta/fw_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tta/tta.go b/cmd/tta/tta.go index 9f8f002958d61..377d01c9487f7 100644 --- a/cmd/tta/tta.go +++ b/cmd/tta/tta.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tta server is the Tailscale Test Agent. diff --git a/cmd/vet/jsontags/analyzer.go b/cmd/vet/jsontags/analyzer.go index d799b66cbb583..c69634ecd3e8a 100644 --- a/cmd/vet/jsontags/analyzer.go +++ b/cmd/vet/jsontags/analyzer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsontags checks for incompatible usage of JSON struct tags. diff --git a/cmd/vet/jsontags/iszero.go b/cmd/vet/jsontags/iszero.go index 77520d72c66f3..fd25cc120c530 100644 --- a/cmd/vet/jsontags/iszero.go +++ b/cmd/vet/jsontags/iszero.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsontags diff --git a/cmd/vet/jsontags/report.go b/cmd/vet/jsontags/report.go index 8e5869060799c..702de1c4d1c36 100644 --- a/cmd/vet/jsontags/report.go +++ b/cmd/vet/jsontags/report.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsontags diff --git a/cmd/vet/vet.go b/cmd/vet/vet.go index 45473af48f0ee..babc30d254719 100644 --- a/cmd/vet/vet.go +++ b/cmd/vet/vet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package vet is a tool to statically check Go source code. diff --git a/cmd/viewer/tests/tests.go b/cmd/viewer/tests/tests.go index d1c753db78710..cbffd38845ec3 100644 --- a/cmd/viewer/tests/tests.go +++ b/cmd/viewer/tests/tests.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tests serves a list of tests for tailscale.com/cmd/viewer. diff --git a/cmd/viewer/tests/tests_clone.go b/cmd/viewer/tests/tests_clone.go index 4602b9d887d2b..cbf5ec2653d98 100644 --- a/cmd/viewer/tests/tests_clone.go +++ b/cmd/viewer/tests/tests_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/cmd/viewer/tests/tests_view.go b/cmd/viewer/tests/tests_view.go index 495281c23b3aa..fe073446ea200 100644 --- a/cmd/viewer/tests/tests_view.go +++ b/cmd/viewer/tests/tests_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/cmd/viewer/viewer.go b/cmd/viewer/viewer.go index 3fae737cde692..56b999f5f50fe 100644 --- a/cmd/viewer/viewer.go +++ b/cmd/viewer/viewer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Viewer is a tool to automate the creation of "view" wrapper types that diff --git a/cmd/viewer/viewer_test.go b/cmd/viewer/viewer_test.go index 1e24b705069d7..8bd18d4806ae2 100644 --- a/cmd/viewer/viewer_test.go +++ b/cmd/viewer/viewer_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/vnet/vnet-main.go b/cmd/vnet/vnet-main.go index 9dd4d8cfafe94..8a3afe2035a95 100644 --- a/cmd/vnet/vnet-main.go +++ b/cmd/vnet/vnet-main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The vnet binary runs a virtual network stack in userspace for qemu instances diff --git a/cmd/xdpderper/xdpderper.go b/cmd/xdpderper/xdpderper.go index c127baf54e340..ea25550bb1189 100644 --- a/cmd/xdpderper/xdpderper.go +++ b/cmd/xdpderper/xdpderper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command xdpderper runs the XDP STUN server. diff --git a/control/controlbase/conn.go b/control/controlbase/conn.go index 78ef73f71000b..8f6e5a7717f79 100644 --- a/control/controlbase/conn.go +++ b/control/controlbase/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package controlbase implements the base transport of the Tailscale diff --git a/control/controlbase/conn_test.go b/control/controlbase/conn_test.go index ed4642d3b179c..a1e2b313de5b6 100644 --- a/control/controlbase/conn_test.go +++ b/control/controlbase/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlbase/handshake.go b/control/controlbase/handshake.go index 765a4620b876f..919920c344239 100644 --- a/control/controlbase/handshake.go +++ b/control/controlbase/handshake.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlbase/handshake_test.go b/control/controlbase/handshake_test.go index 242b1f4d7c658..f6b5409a8904f 100644 --- a/control/controlbase/handshake_test.go +++ b/control/controlbase/handshake_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlbase/interop_test.go b/control/controlbase/interop_test.go index c41fbf4dd4950..87ee7d45876d7 100644 --- a/control/controlbase/interop_test.go +++ b/control/controlbase/interop_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlbase/messages.go b/control/controlbase/messages.go index 59073088f5e81..1357432de7ee5 100644 --- a/control/controlbase/messages.go +++ b/control/controlbase/messages.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index 336a8d491bc9c..783ca36c4f45d 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient @@ -674,17 +674,16 @@ func canSkipStatus(s1, s2 *Status) bool { // we can't skip it. return false } - if s1.Err != nil || s1.URL != "" || s1.LoggedIn { - // If s1 has an error, a URL, or LoginFinished set, we shouldn't skip it, - // lest the error go away in s2 or in-between. We want to make sure all - // the subsystems see it. Plus there aren't many of these, so not worth - // skipping. + if s1.Err != nil || s1.URL != "" { + // If s1 has an error or an URL, we shouldn't skip it, lest the error go + // away in s2 or in-between. We want to make sure all the subsystems see + // it. Plus there aren't many of these, so not worth skipping. return false } if !s1.Persist.Equals(s2.Persist) || s1.LoggedIn != s2.LoggedIn || s1.InMapPoll != s2.InMapPoll || s1.URL != s2.URL { - // If s1 has a different Persist, LoginFinished, Synced, or URL than s2, - // don't skip it. We only care about skipping the typical - // entries where the only difference is the NetMap. + // If s1 has a different Persist, has changed login state, changed map + // poll state, or has a new login URL, don't skip it. We only care about + // skipping the typical entries where the only difference is the NetMap. return false } // If nothing above precludes it, and both s1 and s2 have NetMaps, then diff --git a/control/controlclient/client.go b/control/controlclient/client.go index 41b39622b0199..a57c6940a88c4 100644 --- a/control/controlclient/client.go +++ b/control/controlclient/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package controlclient implements the client for the Tailscale @@ -91,9 +91,3 @@ type Client interface { // distinguish one client from another. ClientID() int64 } - -// UserVisibleError is an error that should be shown to users. -type UserVisibleError string - -func (e UserVisibleError) Error() string { return string(e) } -func (e UserVisibleError) UserVisibleError() string { return string(e) } diff --git a/control/controlclient/controlclient_test.go b/control/controlclient/controlclient_test.go index 57d3ca7ca7ae3..dca1d8ddf2f8b 100644 --- a/control/controlclient/controlclient_test.go +++ b/control/controlclient/controlclient_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient @@ -98,6 +98,7 @@ func TestCanSkipStatus(t *testing.T) { nm1 := &netmap.NetworkMap{} nm2 := &netmap.NetworkMap{} + commonPersist := new(persist.Persist).View() tests := []struct { name string s1, s2 *Status @@ -165,8 +166,8 @@ func TestCanSkipStatus(t *testing.T) { }, { name: "skip", - s1: &Status{NetMap: nm1}, - s2: &Status{NetMap: nm2}, + s1: &Status{NetMap: nm1, LoggedIn: true, InMapPoll: true, Persist: commonPersist}, + s2: &Status{NetMap: nm2, LoggedIn: true, InMapPoll: true, Persist: commonPersist}, want: true, }, } diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index d5cd6a13e5120..6f3393b18dfdf 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient @@ -59,6 +59,7 @@ import ( "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/policyclient" "tailscale.com/util/testenv" + "tailscale.com/util/vizerror" "tailscale.com/util/zstdframe" ) @@ -743,7 +744,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new resp.NodeKeyExpired, resp.MachineAuthorized, resp.AuthURL != "") if resp.Error != "" { - return false, "", nil, UserVisibleError(resp.Error) + return false, "", nil, vizerror.New(resp.Error) } if len(resp.NodeKeySignature) > 0 { return true, "", resp.NodeKeySignature, nil @@ -1230,6 +1231,9 @@ func NetmapFromMapResponseForDebug(ctx context.Context, pr persist.PersistView, if resp.Node == nil { return nil, errors.New("MapResponse lacks Node") } + if !pr.Valid() { + return nil, errors.New("PersistView invalid") + } nu := &rememberLastNetmapUpdater{} sess := newMapSession(pr.PrivateNodeKey(), nu, nil) diff --git a/control/controlclient/direct_test.go b/control/controlclient/direct_test.go index 4329fc878ceb3..d10b346ae39a7 100644 --- a/control/controlclient/direct_test.go +++ b/control/controlclient/direct_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/errors.go b/control/controlclient/errors.go index 9b4dab84467b8..a2397cedeaa5c 100644 --- a/control/controlclient/errors.go +++ b/control/controlclient/errors.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/map.go b/control/controlclient/map.go index 9aa8e37107a99..18bd420ebaae3 100644 --- a/control/controlclient/map.go +++ b/control/controlclient/map.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/map_test.go b/control/controlclient/map_test.go index 2be4b6ad70b2d..11d4593f03fae 100644 --- a/control/controlclient/map_test.go +++ b/control/controlclient/map_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/sign.go b/control/controlclient/sign.go index e3a479c283c62..6cee1265fe99b 100644 --- a/control/controlclient/sign.go +++ b/control/controlclient/sign.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/sign_supported.go b/control/controlclient/sign_supported.go index 439e6d36b4fe3..ea6fa28e34479 100644 --- a/control/controlclient/sign_supported.go +++ b/control/controlclient/sign_supported.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/control/controlclient/sign_supported_test.go b/control/controlclient/sign_supported_test.go index e20349a4e82c3..9d4abafbd12f6 100644 --- a/control/controlclient/sign_supported_test.go +++ b/control/controlclient/sign_supported_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows && cgo diff --git a/control/controlclient/sign_unsupported.go b/control/controlclient/sign_unsupported.go index f6c4ddc6288fb..ff830282e4496 100644 --- a/control/controlclient/sign_unsupported.go +++ b/control/controlclient/sign_unsupported.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/control/controlclient/status.go b/control/controlclient/status.go index 65afb7a5011f2..46dc8f773f260 100644 --- a/control/controlclient/status.go +++ b/control/controlclient/status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlhttp/client.go b/control/controlhttp/client.go index 06a2131fdcb2b..e812091745ea5 100644 --- a/control/controlhttp/client.go +++ b/control/controlhttp/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js diff --git a/control/controlhttp/client_common.go b/control/controlhttp/client_common.go index dd94e93cdc3cf..5e49b0bfcc295 100644 --- a/control/controlhttp/client_common.go +++ b/control/controlhttp/client_common.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlhttp diff --git a/control/controlhttp/client_js.go b/control/controlhttp/client_js.go index cc05b5b192766..a3ce7ffe5c765 100644 --- a/control/controlhttp/client_js.go +++ b/control/controlhttp/client_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlhttp diff --git a/control/controlhttp/constants.go b/control/controlhttp/constants.go index 359410ae9d29c..26ace871c1268 100644 --- a/control/controlhttp/constants.go +++ b/control/controlhttp/constants.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlhttp diff --git a/control/controlhttp/controlhttpcommon/controlhttpcommon.go b/control/controlhttp/controlhttpcommon/controlhttpcommon.go index a86b7ca04a7f4..21236b09b5574 100644 --- a/control/controlhttp/controlhttpcommon/controlhttpcommon.go +++ b/control/controlhttp/controlhttpcommon/controlhttpcommon.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package controlhttpcommon contains common constants for used diff --git a/control/controlhttp/controlhttpserver/controlhttpserver.go b/control/controlhttp/controlhttpserver/controlhttpserver.go index af320781069d1..7b413829eff78 100644 --- a/control/controlhttp/controlhttpserver/controlhttpserver.go +++ b/control/controlhttp/controlhttpserver/controlhttpserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios diff --git a/control/controlhttp/http_test.go b/control/controlhttp/http_test.go index 648b9e5ed88d5..c02ac758ebf16 100644 --- a/control/controlhttp/http_test.go +++ b/control/controlhttp/http_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlhttp diff --git a/control/controlknobs/controlknobs.go b/control/controlknobs/controlknobs.go index 09c16b8b12f1e..1861a122e2f9e 100644 --- a/control/controlknobs/controlknobs.go +++ b/control/controlknobs/controlknobs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package controlknobs contains client options configurable from control which can be turned on @@ -107,6 +107,20 @@ type Knobs struct { // of queued netmap.NetworkMap between the controlclient and LocalBackend. // See tailscale/tailscale#14768. DisableSkipStatusQueue atomic.Bool + + // DisableHostsFileUpdates indicates that the node's DNS manager should not create + // hosts file entries when it normally would, such as when we're not the primary + // resolver on Windows or when the host is domain-joined and its primary domain + // takes precedence over MagicDNS. As of 2026-02-13, it is only used on Windows. + DisableHostsFileUpdates atomic.Bool + + // ForceRegisterMagicDNSIPv4Only is whether the node should only register + // its IPv4 MagicDNS service IP and not its IPv6 one. The IPv6 one, + // tsaddr.TailscaleServiceIPv6String, still works in either case. This knob + // controls only whether we tell systemd/etc about the IPv6 one. + // See https://github.com/tailscale/tailscale/issues/15404. + // TODO(bradfitz): remove this a few releases after 2026-02-16. + ForceRegisterMagicDNSIPv4Only atomic.Bool } // UpdateFromNodeAttributes updates k (if non-nil) based on the provided self @@ -137,6 +151,8 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) { disableLocalDNSOverrideViaNRPT = has(tailcfg.NodeAttrDisableLocalDNSOverrideViaNRPT) disableCaptivePortalDetection = has(tailcfg.NodeAttrDisableCaptivePortalDetection) disableSkipStatusQueue = has(tailcfg.NodeAttrDisableSkipStatusQueue) + disableHostsFileUpdates = has(tailcfg.NodeAttrDisableHostsFileUpdates) + forceRegisterMagicDNSIPv4Only = has(tailcfg.NodeAttrForceRegisterMagicDNSIPv4Only) ) if has(tailcfg.NodeAttrOneCGNATEnable) { @@ -163,6 +179,8 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) { k.DisableLocalDNSOverrideViaNRPT.Store(disableLocalDNSOverrideViaNRPT) k.DisableCaptivePortalDetection.Store(disableCaptivePortalDetection) k.DisableSkipStatusQueue.Store(disableSkipStatusQueue) + k.DisableHostsFileUpdates.Store(disableHostsFileUpdates) + k.ForceRegisterMagicDNSIPv4Only.Store(forceRegisterMagicDNSIPv4Only) // If both attributes are present, then "enable" should win. This reflects // the history of seamless key renewal. @@ -202,3 +220,9 @@ func (k *Knobs) AsDebugJSON() map[string]any { } return ret } + +// ShouldForceRegisterMagicDNSIPv4Only reports the value of +// ForceRegisterMagicDNSIPv4Only, or false if k is nil. +func (k *Knobs) ShouldForceRegisterMagicDNSIPv4Only() bool { + return k != nil && k.ForceRegisterMagicDNSIPv4Only.Load() +} diff --git a/control/controlknobs/controlknobs_test.go b/control/controlknobs/controlknobs_test.go index 7618b7121c500..495535b1e2807 100644 --- a/control/controlknobs/controlknobs_test.go +++ b/control/controlknobs/controlknobs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlknobs diff --git a/control/ts2021/client.go b/control/ts2021/client.go index ca10b1d1b5bc6..0f0e7598b5591 100644 --- a/control/ts2021/client.go +++ b/control/ts2021/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ts2021 diff --git a/control/ts2021/client_test.go b/control/ts2021/client_test.go index 72fa1f44264c3..da823fc548593 100644 --- a/control/ts2021/client_test.go +++ b/control/ts2021/client_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ts2021 diff --git a/control/ts2021/conn.go b/control/ts2021/conn.go index 52d663272a8c6..6832f2df12a4f 100644 --- a/control/ts2021/conn.go +++ b/control/ts2021/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ts2021 handles the details of the Tailscale 2021 control protocol diff --git a/derp/client_test.go b/derp/client_test.go index a731ad197f1e7..e1bcaba8bf2c8 100644 --- a/derp/client_test.go +++ b/derp/client_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derp diff --git a/derp/derp.go b/derp/derp.go index e19a99b0025ce..a7d0ea80191a8 100644 --- a/derp/derp.go +++ b/derp/derp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package derp implements the Designated Encrypted Relay for Packets (DERP) diff --git a/derp/derp_client.go b/derp/derp_client.go index d28905cd2c8b2..1e9d48e1456c8 100644 --- a/derp/derp_client.go +++ b/derp/derp_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derp diff --git a/derp/derp_test.go b/derp/derp_test.go index 52793f90fa9f5..cff069dd4470c 100644 --- a/derp/derp_test.go +++ b/derp/derp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derp_test diff --git a/derp/derpconst/derpconst.go b/derp/derpconst/derpconst.go index 74ca09ccb734b..03ef249ce1b28 100644 --- a/derp/derpconst/derpconst.go +++ b/derp/derpconst/derpconst.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package derpconst contains constants used by the DERP client and server. diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go index db56c4a44c682..3c8408e95e1f1 100644 --- a/derp/derphttp/derphttp_client.go +++ b/derp/derphttp/derphttp_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package derphttp implements DERP-over-HTTP. diff --git a/derp/derphttp/derphttp_test.go b/derp/derphttp/derphttp_test.go index 5208481ed7258..ae530c93a31c0 100644 --- a/derp/derphttp/derphttp_test.go +++ b/derp/derphttp/derphttp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derphttp_test diff --git a/derp/derphttp/export_test.go b/derp/derphttp/export_test.go index 59d8324dcba3e..e3f449277fd6a 100644 --- a/derp/derphttp/export_test.go +++ b/derp/derphttp/export_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derphttp diff --git a/derp/derphttp/mesh_client.go b/derp/derphttp/mesh_client.go index c14a9a7e11111..d8fa7cd9aae03 100644 --- a/derp/derphttp/mesh_client.go +++ b/derp/derphttp/mesh_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derphttp diff --git a/derp/derphttp/websocket.go b/derp/derphttp/websocket.go index 9dd640ee37083..295d0a9bd44de 100644 --- a/derp/derphttp/websocket.go +++ b/derp/derphttp/websocket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js || ((linux || darwin) && ts_debug_websockets) diff --git a/derp/derphttp/websocket_stub.go b/derp/derphttp/websocket_stub.go index d84bfba571f80..52d5ed15e3c25 100644 --- a/derp/derphttp/websocket_stub.go +++ b/derp/derphttp/websocket_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(js || ((linux || darwin) && ts_debug_websockets)) diff --git a/derp/derpserver/derpserver.go b/derp/derpserver/derpserver.go index 1879e0c536f3d..f311eb25d9817 100644 --- a/derp/derpserver/derpserver.go +++ b/derp/derpserver/derpserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package derpserver implements a DERP server. diff --git a/derp/derpserver/derpserver_default.go b/derp/derpserver/derpserver_default.go index 874e590d3c812..f664e88d1c3dd 100644 --- a/derp/derpserver/derpserver_default.go +++ b/derp/derpserver/derpserver_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || android diff --git a/derp/derpserver/derpserver_linux.go b/derp/derpserver/derpserver_linux.go index 768e6a2ab6ab7..c6154661c5486 100644 --- a/derp/derpserver/derpserver_linux.go +++ b/derp/derpserver/derpserver_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/derp/derpserver/derpserver_test.go b/derp/derpserver/derpserver_test.go index 1dd86f3146c5c..3a778d59fb009 100644 --- a/derp/derpserver/derpserver_test.go +++ b/derp/derpserver/derpserver_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derpserver diff --git a/derp/derpserver/handler.go b/derp/derpserver/handler.go index 7cd6aa2fd5b95..f639cb7123c73 100644 --- a/derp/derpserver/handler.go +++ b/derp/derpserver/handler.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derpserver diff --git a/derp/export_test.go b/derp/export_test.go index 677a4932d2657..9a73dd13e2798 100644 --- a/derp/export_test.go +++ b/derp/export_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derp diff --git a/derp/xdp/headers/update.go b/derp/xdp/headers/update.go index c41332d077322..a7680c042ae0f 100644 --- a/derp/xdp/headers/update.go +++ b/derp/xdp/headers/update.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The update program fetches the libbpf headers from the libbpf GitHub repository diff --git a/derp/xdp/xdp.go b/derp/xdp/xdp.go index 5b2dbd1c26bcd..5f95b71e50294 100644 --- a/derp/xdp/xdp.go +++ b/derp/xdp/xdp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package xdp contains the XDP STUN program. diff --git a/derp/xdp/xdp_default.go b/derp/xdp/xdp_default.go index 99bc30d2c2ddc..187a112295c22 100644 --- a/derp/xdp/xdp_default.go +++ b/derp/xdp/xdp_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/derp/xdp/xdp_linux.go b/derp/xdp/xdp_linux.go index 309d9ee9a92b4..5d22716be4f16 100644 --- a/derp/xdp/xdp_linux.go +++ b/derp/xdp/xdp_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package xdp diff --git a/derp/xdp/xdp_linux_test.go b/derp/xdp/xdp_linux_test.go index 07f11eff65b09..5c75a69ff3fbb 100644 --- a/derp/xdp/xdp_linux_test.go +++ b/derp/xdp/xdp_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/disco/disco.go b/disco/disco.go index f58bc1b8c1ba1..2147529d175d4 100644 --- a/disco/disco.go +++ b/disco/disco.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package disco contains the discovery message types. diff --git a/disco/disco_fuzzer.go b/disco/disco_fuzzer.go index b9ffabfb00906..99a96ae85e34f 100644 --- a/disco/disco_fuzzer.go +++ b/disco/disco_fuzzer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build gofuzz diff --git a/disco/disco_test.go b/disco/disco_test.go index 71b68338a8c90..07b653ceeb950 100644 --- a/disco/disco_test.go +++ b/disco/disco_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package disco diff --git a/disco/pcap.go b/disco/pcap.go index 71035424868e8..e4a910163f4aa 100644 --- a/disco/pcap.go +++ b/disco/pcap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package disco diff --git a/docs/k8s/Makefile b/docs/k8s/Makefile index 55804c857c049..6397957808e49 100644 --- a/docs/k8s/Makefile +++ b/docs/k8s/Makefile @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause TS_ROUTES ?= "" diff --git a/docs/k8s/proxy.yaml b/docs/k8s/proxy.yaml index 048fd7a5bddf9..bd31b7a97bc83 100644 --- a/docs/k8s/proxy.yaml +++ b/docs/k8s/proxy.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: Pod diff --git a/docs/k8s/role.yaml b/docs/k8s/role.yaml index d7d0846ab29a6..869d71b719118 100644 --- a/docs/k8s/role.yaml +++ b/docs/k8s/role.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/docs/k8s/rolebinding.yaml b/docs/k8s/rolebinding.yaml index 3b18ba8d35e57..1bec3df271e8e 100644 --- a/docs/k8s/rolebinding.yaml +++ b/docs/k8s/rolebinding.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/docs/k8s/sa.yaml b/docs/k8s/sa.yaml index edd3944ba8987..e1d61573c5317 100644 --- a/docs/k8s/sa.yaml +++ b/docs/k8s/sa.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: ServiceAccount diff --git a/docs/k8s/sidecar.yaml b/docs/k8s/sidecar.yaml index 520e4379ad9ee..c119c67bbe5f8 100644 --- a/docs/k8s/sidecar.yaml +++ b/docs/k8s/sidecar.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: Pod diff --git a/docs/k8s/subnet.yaml b/docs/k8s/subnet.yaml index ef4e4748c0ceb..556201deb6500 100644 --- a/docs/k8s/subnet.yaml +++ b/docs/k8s/subnet.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: Pod diff --git a/docs/k8s/userspace-sidecar.yaml b/docs/k8s/userspace-sidecar.yaml index ee19b10a5e5dd..32a949593c040 100644 --- a/docs/k8s/userspace-sidecar.yaml +++ b/docs/k8s/userspace-sidecar.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: Pod diff --git a/docs/sysv/tailscale.init b/docs/sysv/tailscale.init index ca21033df7b27..0168adfdb1041 100755 --- a/docs/sysv/tailscale.init +++ b/docs/sysv/tailscale.init @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause ### BEGIN INIT INFO diff --git a/docs/webhooks/example.go b/docs/webhooks/example.go index 712028362c53e..53ec1c8b74b52 100644 --- a/docs/webhooks/example.go +++ b/docs/webhooks/example.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command webhooks provides example consumer code for Tailscale diff --git a/docs/windows/policy/tailscale.admx b/docs/windows/policy/tailscale.admx index 7bd31ac9c597d..7cc174b06a8cd 100644 --- a/docs/windows/policy/tailscale.admx +++ b/docs/windows/policy/tailscale.admx @@ -231,7 +231,7 @@ never - + diff --git a/doctor/doctor.go b/doctor/doctor.go index 7c3047e12b62d..437df5e756dea 100644 --- a/doctor/doctor.go +++ b/doctor/doctor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package doctor contains more in-depth healthchecks that can be run to aid in diff --git a/doctor/doctor_test.go b/doctor/doctor_test.go index 87250f10ed00a..cd9d00ae6868e 100644 --- a/doctor/doctor_test.go +++ b/doctor/doctor_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package doctor diff --git a/doctor/ethtool/ethtool.go b/doctor/ethtool/ethtool.go index f80b00a51ff65..40f39cc21b49a 100644 --- a/doctor/ethtool/ethtool.go +++ b/doctor/ethtool/ethtool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ethtool provides a doctor.Check that prints diagnostic information diff --git a/doctor/ethtool/ethtool_linux.go b/doctor/ethtool/ethtool_linux.go index f6eaac1df0542..3914158741724 100644 --- a/doctor/ethtool/ethtool_linux.go +++ b/doctor/ethtool/ethtool_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/doctor/ethtool/ethtool_other.go b/doctor/ethtool/ethtool_other.go index 7af74eec8f872..91b5f6fdb9a6f 100644 --- a/doctor/ethtool/ethtool_other.go +++ b/doctor/ethtool/ethtool_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || android diff --git a/doctor/permissions/permissions.go b/doctor/permissions/permissions.go index 77fe526262f0c..a98ad1e0826a1 100644 --- a/doctor/permissions/permissions.go +++ b/doctor/permissions/permissions.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package permissions provides a doctor.Check that prints the process diff --git a/doctor/permissions/permissions_bsd.go b/doctor/permissions/permissions_bsd.go index 8b034cfff1af3..c72e4d5d7a65c 100644 --- a/doctor/permissions/permissions_bsd.go +++ b/doctor/permissions/permissions_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || freebsd || openbsd diff --git a/doctor/permissions/permissions_linux.go b/doctor/permissions/permissions_linux.go index 12bb393d53383..8f8f12161e949 100644 --- a/doctor/permissions/permissions_linux.go +++ b/doctor/permissions/permissions_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/doctor/permissions/permissions_other.go b/doctor/permissions/permissions_other.go index 7e6912b4928cf..e96cf4f16277b 100644 --- a/doctor/permissions/permissions_other.go +++ b/doctor/permissions/permissions_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(linux || darwin || freebsd || openbsd) diff --git a/doctor/permissions/permissions_test.go b/doctor/permissions/permissions_test.go index 941d406ef8318..c7a292f39e783 100644 --- a/doctor/permissions/permissions_test.go +++ b/doctor/permissions/permissions_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package permissions diff --git a/doctor/routetable/routetable.go b/doctor/routetable/routetable.go index 76e4ef949b9af..1751d37448411 100644 --- a/doctor/routetable/routetable.go +++ b/doctor/routetable/routetable.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package routetable provides a doctor.Check that dumps the current system's diff --git a/drive/drive_clone.go b/drive/drive_clone.go index 927f3b81c4e2c..724ebc386273d 100644 --- a/drive/drive_clone.go +++ b/drive/drive_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/drive/drive_view.go b/drive/drive_view.go index b481751bb3bff..253a2955b2161 100644 --- a/drive/drive_view.go +++ b/drive/drive_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/drive/driveimpl/birthtiming.go b/drive/driveimpl/birthtiming.go index d55ea0b83c322..c71bba5b47c77 100644 --- a/drive/driveimpl/birthtiming.go +++ b/drive/driveimpl/birthtiming.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/birthtiming_test.go b/drive/driveimpl/birthtiming_test.go index a43ffa33db92e..2bb1259224ff2 100644 --- a/drive/driveimpl/birthtiming_test.go +++ b/drive/driveimpl/birthtiming_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // BirthTime is not supported on Linux, so only run the test on windows and Mac. diff --git a/drive/driveimpl/compositedav/compositedav.go b/drive/driveimpl/compositedav/compositedav.go index 7c035912b946d..c6ec797726643 100644 --- a/drive/driveimpl/compositedav/compositedav.go +++ b/drive/driveimpl/compositedav/compositedav.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package compositedav provides an http.Handler that composes multiple WebDAV diff --git a/drive/driveimpl/compositedav/rewriting.go b/drive/driveimpl/compositedav/rewriting.go index 704be93d1bf76..47f020461b77d 100644 --- a/drive/driveimpl/compositedav/rewriting.go +++ b/drive/driveimpl/compositedav/rewriting.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package compositedav diff --git a/drive/driveimpl/compositedav/stat_cache.go b/drive/driveimpl/compositedav/stat_cache.go index 36463fe7e137f..2e53c82419795 100644 --- a/drive/driveimpl/compositedav/stat_cache.go +++ b/drive/driveimpl/compositedav/stat_cache.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package compositedav diff --git a/drive/driveimpl/compositedav/stat_cache_test.go b/drive/driveimpl/compositedav/stat_cache_test.go index baa4fdda2c7f7..b982a3aad1d17 100644 --- a/drive/driveimpl/compositedav/stat_cache_test.go +++ b/drive/driveimpl/compositedav/stat_cache_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package compositedav diff --git a/drive/driveimpl/connlistener.go b/drive/driveimpl/connlistener.go index ff60f73404230..8fcc5a6d262d1 100644 --- a/drive/driveimpl/connlistener.go +++ b/drive/driveimpl/connlistener.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/connlistener_test.go b/drive/driveimpl/connlistener_test.go index 6adf15acbd56f..972791c6e530e 100644 --- a/drive/driveimpl/connlistener_test.go +++ b/drive/driveimpl/connlistener_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/dirfs/dirfs.go b/drive/driveimpl/dirfs/dirfs.go index 50a3330a9d751..3c4297264302d 100644 --- a/drive/driveimpl/dirfs/dirfs.go +++ b/drive/driveimpl/dirfs/dirfs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dirfs provides a webdav.FileSystem that looks like a read-only diff --git a/drive/driveimpl/dirfs/dirfs_test.go b/drive/driveimpl/dirfs/dirfs_test.go index 4d83765d9df23..c5f3aed3a99f0 100644 --- a/drive/driveimpl/dirfs/dirfs_test.go +++ b/drive/driveimpl/dirfs/dirfs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/mkdir.go b/drive/driveimpl/dirfs/mkdir.go index 2fb763dd5848a..6ed3ec27ea332 100644 --- a/drive/driveimpl/dirfs/mkdir.go +++ b/drive/driveimpl/dirfs/mkdir.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/openfile.go b/drive/driveimpl/dirfs/openfile.go index 9b678719b5b6c..71b55ab206e24 100644 --- a/drive/driveimpl/dirfs/openfile.go +++ b/drive/driveimpl/dirfs/openfile.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/removeall.go b/drive/driveimpl/dirfs/removeall.go index 8fafc8c92bb04..a01d1dd0493d1 100644 --- a/drive/driveimpl/dirfs/removeall.go +++ b/drive/driveimpl/dirfs/removeall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/rename.go b/drive/driveimpl/dirfs/rename.go index 5049acb895e70..eedb1674318c0 100644 --- a/drive/driveimpl/dirfs/rename.go +++ b/drive/driveimpl/dirfs/rename.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/stat.go b/drive/driveimpl/dirfs/stat.go index 2e4243bedcd20..dd0aa976afb8e 100644 --- a/drive/driveimpl/dirfs/stat.go +++ b/drive/driveimpl/dirfs/stat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/drive_test.go b/drive/driveimpl/drive_test.go index 818e84990baef..db7bfe60bde19 100644 --- a/drive/driveimpl/drive_test.go +++ b/drive/driveimpl/drive_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/fileserver.go b/drive/driveimpl/fileserver.go index d448d83af761d..6aedfef2ce522 100644 --- a/drive/driveimpl/fileserver.go +++ b/drive/driveimpl/fileserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/local_impl.go b/drive/driveimpl/local_impl.go index 871d033431038..ab908c0d31d9c 100644 --- a/drive/driveimpl/local_impl.go +++ b/drive/driveimpl/local_impl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package driveimpl provides an implementation of package drive. diff --git a/drive/driveimpl/remote_impl.go b/drive/driveimpl/remote_impl.go index 2ff98075e3012..df27ba71627df 100644 --- a/drive/driveimpl/remote_impl.go +++ b/drive/driveimpl/remote_impl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/shared/pathutil.go b/drive/driveimpl/shared/pathutil.go index fcadcdd5aa0e0..8c0fb179dcc03 100644 --- a/drive/driveimpl/shared/pathutil.go +++ b/drive/driveimpl/shared/pathutil.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package shared diff --git a/drive/driveimpl/shared/pathutil_test.go b/drive/driveimpl/shared/pathutil_test.go index daee695632ff4..b938f4c1c153f 100644 --- a/drive/driveimpl/shared/pathutil_test.go +++ b/drive/driveimpl/shared/pathutil_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package shared diff --git a/drive/driveimpl/shared/readonlydir.go b/drive/driveimpl/shared/readonlydir.go index a495a2d5a93d6..b0f958231968f 100644 --- a/drive/driveimpl/shared/readonlydir.go +++ b/drive/driveimpl/shared/readonlydir.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package shared contains types and functions shared by different drive diff --git a/drive/driveimpl/shared/stat.go b/drive/driveimpl/shared/stat.go index d8022894c0888..93aad90abc49d 100644 --- a/drive/driveimpl/shared/stat.go +++ b/drive/driveimpl/shared/stat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package shared diff --git a/drive/driveimpl/shared/xml.go b/drive/driveimpl/shared/xml.go index 79fd0885dd500..ffaeb031b7636 100644 --- a/drive/driveimpl/shared/xml.go +++ b/drive/driveimpl/shared/xml.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package shared diff --git a/drive/local.go b/drive/local.go index 052efb3f97ecf..300d142d4445b 100644 --- a/drive/local.go +++ b/drive/local.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package drive provides a filesystem that allows sharing folders between diff --git a/drive/remote.go b/drive/remote.go index 2c6fba894dbff..5f34d0023e6f7 100644 --- a/drive/remote.go +++ b/drive/remote.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package drive diff --git a/drive/remote_nonunix.go b/drive/remote_nonunix.go index d1153c5925419..4186ec0ad46e7 100644 --- a/drive/remote_nonunix.go +++ b/drive/remote_nonunix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !unix diff --git a/drive/remote_permissions.go b/drive/remote_permissions.go index 420eff9a0e743..31ec0caee881d 100644 --- a/drive/remote_permissions.go +++ b/drive/remote_permissions.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package drive diff --git a/drive/remote_permissions_test.go b/drive/remote_permissions_test.go index ff039c80020c8..5d63a503f75d9 100644 --- a/drive/remote_permissions_test.go +++ b/drive/remote_permissions_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package drive diff --git a/drive/remote_test.go b/drive/remote_test.go index e05b23839bade..c0de1723aee59 100644 --- a/drive/remote_test.go +++ b/drive/remote_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package drive diff --git a/drive/remote_unix.go b/drive/remote_unix.go index 0e41524dbd304..4b367ef5ff79a 100644 --- a/drive/remote_unix.go +++ b/drive/remote_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/envknob/envknob.go b/envknob/envknob.go index 17a21387ecaea..2b1461f11f308 100644 --- a/envknob/envknob.go +++ b/envknob/envknob.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package envknob provides access to environment-variable tweakable diff --git a/envknob/envknob_nottest.go b/envknob/envknob_nottest.go index 0dd900cc8104e..4693ceebe746a 100644 --- a/envknob/envknob_nottest.go +++ b/envknob/envknob_nottest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_not_in_tests diff --git a/envknob/envknob_testable.go b/envknob/envknob_testable.go index e7f038336c4f3..5f0beea4f962e 100644 --- a/envknob/envknob_testable.go +++ b/envknob/envknob_testable.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_not_in_tests diff --git a/envknob/featureknob/featureknob.go b/envknob/featureknob/featureknob.go index 5a54a1c42978d..049366549fcb3 100644 --- a/envknob/featureknob/featureknob.go +++ b/envknob/featureknob/featureknob.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package featureknob provides a facility to control whether features diff --git a/envknob/logknob/logknob.go b/envknob/logknob/logknob.go index 93302d0d2bd5c..bc6e8c3627077 100644 --- a/envknob/logknob/logknob.go +++ b/envknob/logknob/logknob.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package logknob provides a helpful wrapper that allows enabling logging diff --git a/envknob/logknob/logknob_test.go b/envknob/logknob/logknob_test.go index aa4fb44214e12..9e7ab8aef6368 100644 --- a/envknob/logknob/logknob_test.go +++ b/envknob/logknob/logknob_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logknob diff --git a/feature/ace/ace.go b/feature/ace/ace.go index b6d36543c5281..b99516657ed25 100644 --- a/feature/ace/ace.go +++ b/feature/ace/ace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ace registers support for Alternate Connectivity Endpoints (ACE). diff --git a/feature/appconnectors/appconnectors.go b/feature/appconnectors/appconnectors.go index 28f5ccde35acb..82d29ce0e6034 100644 --- a/feature/appconnectors/appconnectors.go +++ b/feature/appconnectors/appconnectors.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package appconnectors registers support for Tailscale App Connectors. diff --git a/feature/awsparamstore/awsparamstore.go b/feature/awsparamstore/awsparamstore.go new file mode 100644 index 0000000000000..f63f546ed70ed --- /dev/null +++ b/feature/awsparamstore/awsparamstore.go @@ -0,0 +1,88 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_aws + +// Package awsparamstore registers support for fetching secret values from AWS +// Parameter Store. +package awsparamstore + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ssm" + "tailscale.com/feature" + "tailscale.com/internal/client/tailscale" +) + +func init() { + feature.Register("awsparamstore") + tailscale.HookResolveValueFromParameterStore.Set(ResolveValue) +} + +// parseARN parses and verifies that the input string is an +// ARN for AWS Parameter Store, returning the region and parameter name if so. +// +// If the input is not a valid Parameter Store ARN, it returns ok==false. +func parseARN(s string) (region, parameterName string, ok bool) { + parsed, err := arn.Parse(s) + if err != nil { + return "", "", false + } + + if parsed.Service != "ssm" { + return "", "", false + } + parameterName, ok = strings.CutPrefix(parsed.Resource, "parameter/") + if !ok { + return "", "", false + } + + // NOTE: parameter names must have a leading slash + return parsed.Region, "/" + parameterName, true +} + +// ResolveValue fetches a value from AWS Parameter Store if the input +// looks like an SSM ARN (e.g., arn:aws:ssm:us-east-1:123456789012:parameter/my-secret). +// +// If the input is not a Parameter Store ARN, it returns the value unchanged. +// +// If the input is a Parameter Store ARN and fetching the parameter fails, it +// returns an error. +func ResolveValue(ctx context.Context, valueOrARN string) (string, error) { + // If it doesn't look like an ARN, return as-is + region, parameterName, ok := parseARN(valueOrARN) + if !ok { + return valueOrARN, nil + } + + // Load AWS config with the region from the ARN + cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) + if err != nil { + return "", fmt.Errorf("loading AWS config in region %q: %w", region, err) + } + + // Create SSM client and fetch the parameter + client := ssm.NewFromConfig(cfg) + output, err := client.GetParameter(ctx, &ssm.GetParameterInput{ + // The parameter to fetch. + Name: aws.String(parameterName), + + // If the parameter is a SecureString, decrypt it. + WithDecryption: aws.Bool(true), + }) + if err != nil { + return "", fmt.Errorf("getting SSM parameter %q: %w", parameterName, err) + } + + if output.Parameter == nil || output.Parameter.Value == nil { + return "", fmt.Errorf("SSM parameter %q has no value", parameterName) + } + + return strings.TrimSpace(*output.Parameter.Value), nil +} diff --git a/feature/awsparamstore/awsparamstore_test.go b/feature/awsparamstore/awsparamstore_test.go new file mode 100644 index 0000000000000..9ccea63ec11e1 --- /dev/null +++ b/feature/awsparamstore/awsparamstore_test.go @@ -0,0 +1,83 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_aws + +package awsparamstore + +import ( + "testing" +) + +func TestParseARN(t *testing.T) { + tests := []struct { + name string + input string + wantOk bool + wantRegion string + wantParamName string + }{ + { + name: "non-arn-passthrough", + input: "tskey-abcd1234", + wantOk: false, + }, + { + name: "file-prefix-passthrough", + input: "file:/path/to/key", + wantOk: false, + }, + { + name: "empty-passthrough", + input: "", + wantOk: false, + }, + { + name: "non-ssm-arn-passthrough", + input: "arn:aws:s3:::my-bucket", + wantOk: false, + }, + { + name: "invalid-arn-passthrough", + input: "arn:invalid", + wantOk: false, + }, + { + name: "arn-invalid-resource-passthrough", + input: "arn:aws:ssm:us-east-1:123456789012:document/myDoc", + wantOk: false, + }, + { + name: "valid-arn", + input: "arn:aws:ssm:us-west-2:123456789012:parameter/my-secret", + wantOk: true, + wantRegion: "us-west-2", + wantParamName: "/my-secret", + }, + { + name: "valid-arn-with-path", + input: "arn:aws:ssm:eu-central-1:123456789012:parameter/path/to/secret", + wantOk: true, + wantRegion: "eu-central-1", + wantParamName: "/path/to/secret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotRegion, gotParamName, gotOk := parseARN(tt.input) + if gotOk != tt.wantOk { + t.Errorf("parseARN(%q) got ok=%v, want %v", tt.input, gotOk, tt.wantOk) + } + if !tt.wantOk { + return + } + if gotRegion != tt.wantRegion { + t.Errorf("parseARN(%q) got region=%q, want %q", tt.input, gotRegion, tt.wantRegion) + } + if gotParamName != tt.wantParamName { + t.Errorf("parseARN(%q) got paramName=%q, want %q", tt.input, gotParamName, tt.wantParamName) + } + }) + } +} diff --git a/feature/buildfeatures/buildfeatures.go b/feature/buildfeatures/buildfeatures.go index cdb31dc015673..ca4de74344485 100644 --- a/feature/buildfeatures/buildfeatures.go +++ b/feature/buildfeatures/buildfeatures.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:generate go run gen.go diff --git a/feature/buildfeatures/feature_ace_disabled.go b/feature/buildfeatures/feature_ace_disabled.go index b4808d4976b02..91a7eeb46da0d 100644 --- a/feature/buildfeatures/feature_ace_disabled.go +++ b/feature/buildfeatures/feature_ace_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_ace_enabled.go b/feature/buildfeatures/feature_ace_enabled.go index 4812f9a61cd4c..0d975ec7ffb35 100644 --- a/feature/buildfeatures/feature_ace_enabled.go +++ b/feature/buildfeatures/feature_ace_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_acme_disabled.go b/feature/buildfeatures/feature_acme_disabled.go index 0a7f25a821cc5..0add296a67f0a 100644 --- a/feature/buildfeatures/feature_acme_disabled.go +++ b/feature/buildfeatures/feature_acme_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_acme_enabled.go b/feature/buildfeatures/feature_acme_enabled.go index f074bfb4e1a7e..78182eaa488c2 100644 --- a/feature/buildfeatures/feature_acme_enabled.go +++ b/feature/buildfeatures/feature_acme_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_advertiseexitnode_disabled.go b/feature/buildfeatures/feature_advertiseexitnode_disabled.go index d4fdcec22db3c..aeac607012019 100644 --- a/feature/buildfeatures/feature_advertiseexitnode_disabled.go +++ b/feature/buildfeatures/feature_advertiseexitnode_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_advertiseexitnode_enabled.go b/feature/buildfeatures/feature_advertiseexitnode_enabled.go index 28246143ecb3c..0a7451dc3226f 100644 --- a/feature/buildfeatures/feature_advertiseexitnode_enabled.go +++ b/feature/buildfeatures/feature_advertiseexitnode_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_advertiseroutes_disabled.go b/feature/buildfeatures/feature_advertiseroutes_disabled.go index 59042720f3870..dbb3bb059eb04 100644 --- a/feature/buildfeatures/feature_advertiseroutes_disabled.go +++ b/feature/buildfeatures/feature_advertiseroutes_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_advertiseroutes_enabled.go b/feature/buildfeatures/feature_advertiseroutes_enabled.go index 118fcd55d64e4..3abe33644631d 100644 --- a/feature/buildfeatures/feature_advertiseroutes_enabled.go +++ b/feature/buildfeatures/feature_advertiseroutes_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_appconnectors_disabled.go b/feature/buildfeatures/feature_appconnectors_disabled.go index 64ea8f86b4104..dcb9f24d776e8 100644 --- a/feature/buildfeatures/feature_appconnectors_disabled.go +++ b/feature/buildfeatures/feature_appconnectors_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_appconnectors_enabled.go b/feature/buildfeatures/feature_appconnectors_enabled.go index e00eaffa3e6fc..edbfe5fcf1806 100644 --- a/feature/buildfeatures/feature_appconnectors_enabled.go +++ b/feature/buildfeatures/feature_appconnectors_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_aws_disabled.go b/feature/buildfeatures/feature_aws_disabled.go index 66b670c1fe451..22b611e804a86 100644 --- a/feature/buildfeatures/feature_aws_disabled.go +++ b/feature/buildfeatures/feature_aws_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_aws_enabled.go b/feature/buildfeatures/feature_aws_enabled.go index 30203b2aa6df8..5a640a252f149 100644 --- a/feature/buildfeatures/feature_aws_enabled.go +++ b/feature/buildfeatures/feature_aws_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_bakedroots_disabled.go b/feature/buildfeatures/feature_bakedroots_disabled.go index f203bc1b06d44..c06ebd6ff8c02 100644 --- a/feature/buildfeatures/feature_bakedroots_disabled.go +++ b/feature/buildfeatures/feature_bakedroots_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_bakedroots_enabled.go b/feature/buildfeatures/feature_bakedroots_enabled.go index 69cf2c34ccf6a..8477e00514d84 100644 --- a/feature/buildfeatures/feature_bakedroots_enabled.go +++ b/feature/buildfeatures/feature_bakedroots_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_bird_disabled.go b/feature/buildfeatures/feature_bird_disabled.go index 469aa41f954a9..60ca3eaac61b4 100644 --- a/feature/buildfeatures/feature_bird_disabled.go +++ b/feature/buildfeatures/feature_bird_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_bird_enabled.go b/feature/buildfeatures/feature_bird_enabled.go index 792129f64f567..57203324b2a53 100644 --- a/feature/buildfeatures/feature_bird_enabled.go +++ b/feature/buildfeatures/feature_bird_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_c2n_disabled.go b/feature/buildfeatures/feature_c2n_disabled.go index bc37e9e7bfd23..3fcdd3628cb60 100644 --- a/feature/buildfeatures/feature_c2n_disabled.go +++ b/feature/buildfeatures/feature_c2n_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_c2n_enabled.go b/feature/buildfeatures/feature_c2n_enabled.go index 5950e71571652..41f97157f57f8 100644 --- a/feature/buildfeatures/feature_c2n_enabled.go +++ b/feature/buildfeatures/feature_c2n_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cachenetmap_disabled.go b/feature/buildfeatures/feature_cachenetmap_disabled.go index 22407fe38a57f..d05e9315f2f8d 100644 --- a/feature/buildfeatures/feature_cachenetmap_disabled.go +++ b/feature/buildfeatures/feature_cachenetmap_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cachenetmap_enabled.go b/feature/buildfeatures/feature_cachenetmap_enabled.go index 02663c416bcbb..b1cd51a704152 100644 --- a/feature/buildfeatures/feature_cachenetmap_enabled.go +++ b/feature/buildfeatures/feature_cachenetmap_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_captiveportal_disabled.go b/feature/buildfeatures/feature_captiveportal_disabled.go index 367fef81bdc16..7535da5066ae6 100644 --- a/feature/buildfeatures/feature_captiveportal_disabled.go +++ b/feature/buildfeatures/feature_captiveportal_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_captiveportal_enabled.go b/feature/buildfeatures/feature_captiveportal_enabled.go index bd8e1f6a80ff1..90d70ab1d1556 100644 --- a/feature/buildfeatures/feature_captiveportal_enabled.go +++ b/feature/buildfeatures/feature_captiveportal_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_capture_disabled.go b/feature/buildfeatures/feature_capture_disabled.go index 58535958f26e8..8f46b9c244f1b 100644 --- a/feature/buildfeatures/feature_capture_disabled.go +++ b/feature/buildfeatures/feature_capture_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_capture_enabled.go b/feature/buildfeatures/feature_capture_enabled.go index 7120a3d06fa7d..3e1a2d7aaa70f 100644 --- a/feature/buildfeatures/feature_capture_enabled.go +++ b/feature/buildfeatures/feature_capture_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cliconndiag_disabled.go b/feature/buildfeatures/feature_cliconndiag_disabled.go index 06d8c7935fd4a..d38c4a3d6cf35 100644 --- a/feature/buildfeatures/feature_cliconndiag_disabled.go +++ b/feature/buildfeatures/feature_cliconndiag_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cliconndiag_enabled.go b/feature/buildfeatures/feature_cliconndiag_enabled.go index d6125ef08051c..88775b24de51f 100644 --- a/feature/buildfeatures/feature_cliconndiag_enabled.go +++ b/feature/buildfeatures/feature_cliconndiag_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_clientmetrics_disabled.go b/feature/buildfeatures/feature_clientmetrics_disabled.go index 721908bb079a2..0345ccc609fdf 100644 --- a/feature/buildfeatures/feature_clientmetrics_disabled.go +++ b/feature/buildfeatures/feature_clientmetrics_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_clientmetrics_enabled.go b/feature/buildfeatures/feature_clientmetrics_enabled.go index deaeb6e69b1c3..2e58155bd5261 100644 --- a/feature/buildfeatures/feature_clientmetrics_enabled.go +++ b/feature/buildfeatures/feature_clientmetrics_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_clientupdate_disabled.go b/feature/buildfeatures/feature_clientupdate_disabled.go index 165c9cc9a409d..6662ca2b9cda7 100644 --- a/feature/buildfeatures/feature_clientupdate_disabled.go +++ b/feature/buildfeatures/feature_clientupdate_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_clientupdate_enabled.go b/feature/buildfeatures/feature_clientupdate_enabled.go index 3c3c7878c53a9..041cdf8a53e79 100644 --- a/feature/buildfeatures/feature_clientupdate_enabled.go +++ b/feature/buildfeatures/feature_clientupdate_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cloud_disabled.go b/feature/buildfeatures/feature_cloud_disabled.go index 3b877a9c68d40..b2dc2607ffda2 100644 --- a/feature/buildfeatures/feature_cloud_disabled.go +++ b/feature/buildfeatures/feature_cloud_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cloud_enabled.go b/feature/buildfeatures/feature_cloud_enabled.go index 8fd748de56c7e..5ee91b9ed7c4c 100644 --- a/feature/buildfeatures/feature_cloud_enabled.go +++ b/feature/buildfeatures/feature_cloud_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_colorable_disabled.go b/feature/buildfeatures/feature_colorable_disabled.go new file mode 100644 index 0000000000000..3a7bc54234fe5 --- /dev/null +++ b/feature/buildfeatures/feature_colorable_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_colorable + +package buildfeatures + +// HasColorable is whether the binary was built with support for modular feature "Colorized terminal output". +// Specifically, it's whether the binary was NOT built with the "ts_omit_colorable" build tag. +// It's a const so it can be used for dead code elimination. +const HasColorable = false diff --git a/feature/buildfeatures/feature_colorable_enabled.go b/feature/buildfeatures/feature_colorable_enabled.go new file mode 100644 index 0000000000000..b6a08366eba32 --- /dev/null +++ b/feature/buildfeatures/feature_colorable_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_colorable + +package buildfeatures + +// HasColorable is whether the binary was built with support for modular feature "Colorized terminal output". +// Specifically, it's whether the binary was NOT built with the "ts_omit_colorable" build tag. +// It's a const so it can be used for dead code elimination. +const HasColorable = true diff --git a/feature/buildfeatures/feature_completion_disabled.go b/feature/buildfeatures/feature_completion_disabled.go index ea319beb0af3e..aa46c9c6157e5 100644 --- a/feature/buildfeatures/feature_completion_disabled.go +++ b/feature/buildfeatures/feature_completion_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_completion_enabled.go b/feature/buildfeatures/feature_completion_enabled.go index 6db41c97b3e76..561a377edea15 100644 --- a/feature/buildfeatures/feature_completion_enabled.go +++ b/feature/buildfeatures/feature_completion_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_completion_scripts_disabled.go b/feature/buildfeatures/feature_completion_scripts_disabled.go new file mode 100644 index 0000000000000..e22ce69fc708a --- /dev/null +++ b/feature/buildfeatures/feature_completion_scripts_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_completion_scripts + +package buildfeatures + +// HasCompletionScripts is whether the binary was built with support for modular feature "embed CLI shell completion scripts". +// Specifically, it's whether the binary was NOT built with the "ts_omit_completion_scripts" build tag. +// It's a const so it can be used for dead code elimination. +const HasCompletionScripts = false diff --git a/feature/buildfeatures/feature_completion_scripts_enabled.go b/feature/buildfeatures/feature_completion_scripts_enabled.go new file mode 100644 index 0000000000000..c3ecd83cac661 --- /dev/null +++ b/feature/buildfeatures/feature_completion_scripts_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_completion_scripts + +package buildfeatures + +// HasCompletionScripts is whether the binary was built with support for modular feature "embed CLI shell completion scripts". +// Specifically, it's whether the binary was NOT built with the "ts_omit_completion_scripts" build tag. +// It's a const so it can be used for dead code elimination. +const HasCompletionScripts = true diff --git a/feature/buildfeatures/feature_conn25_disabled.go b/feature/buildfeatures/feature_conn25_disabled.go new file mode 100644 index 0000000000000..29d6452400cb3 --- /dev/null +++ b/feature/buildfeatures/feature_conn25_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_conn25 + +package buildfeatures + +// HasConn25 is whether the binary was built with support for modular feature "Route traffic for configured domains through connector devices". +// Specifically, it's whether the binary was NOT built with the "ts_omit_conn25" build tag. +// It's a const so it can be used for dead code elimination. +const HasConn25 = false diff --git a/feature/buildfeatures/feature_conn25_enabled.go b/feature/buildfeatures/feature_conn25_enabled.go new file mode 100644 index 0000000000000..a0d95477cf2d1 --- /dev/null +++ b/feature/buildfeatures/feature_conn25_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_conn25 + +package buildfeatures + +// HasConn25 is whether the binary was built with support for modular feature "Route traffic for configured domains through connector devices". +// Specifically, it's whether the binary was NOT built with the "ts_omit_conn25" build tag. +// It's a const so it can be used for dead code elimination. +const HasConn25 = true diff --git a/feature/buildfeatures/feature_dbus_disabled.go b/feature/buildfeatures/feature_dbus_disabled.go index e6ab896773fd1..c09fa7eeb7a34 100644 --- a/feature/buildfeatures/feature_dbus_disabled.go +++ b/feature/buildfeatures/feature_dbus_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_dbus_enabled.go b/feature/buildfeatures/feature_dbus_enabled.go index 374331cdabe0c..f3cc9f003f0ab 100644 --- a/feature/buildfeatures/feature_dbus_enabled.go +++ b/feature/buildfeatures/feature_dbus_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debug_disabled.go b/feature/buildfeatures/feature_debug_disabled.go index eb048c0826eb9..4faafbb756559 100644 --- a/feature/buildfeatures/feature_debug_disabled.go +++ b/feature/buildfeatures/feature_debug_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debug_enabled.go b/feature/buildfeatures/feature_debug_enabled.go index 12a2700a45761..a99dc81044cea 100644 --- a/feature/buildfeatures/feature_debug_enabled.go +++ b/feature/buildfeatures/feature_debug_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debugeventbus_disabled.go b/feature/buildfeatures/feature_debugeventbus_disabled.go index 2eb59993444af..a7cf3dd72e8c6 100644 --- a/feature/buildfeatures/feature_debugeventbus_disabled.go +++ b/feature/buildfeatures/feature_debugeventbus_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debugeventbus_enabled.go b/feature/buildfeatures/feature_debugeventbus_enabled.go index df13b6fa23167..caa4ca30a5039 100644 --- a/feature/buildfeatures/feature_debugeventbus_enabled.go +++ b/feature/buildfeatures/feature_debugeventbus_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debugportmapper_disabled.go b/feature/buildfeatures/feature_debugportmapper_disabled.go index eff85b8baaf50..4b3b03be824f9 100644 --- a/feature/buildfeatures/feature_debugportmapper_disabled.go +++ b/feature/buildfeatures/feature_debugportmapper_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debugportmapper_enabled.go b/feature/buildfeatures/feature_debugportmapper_enabled.go index 491aa5ed84af1..89250083161e6 100644 --- a/feature/buildfeatures/feature_debugportmapper_enabled.go +++ b/feature/buildfeatures/feature_debugportmapper_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_desktop_sessions_disabled.go b/feature/buildfeatures/feature_desktop_sessions_disabled.go index 1536c886fec25..0df68e9d00704 100644 --- a/feature/buildfeatures/feature_desktop_sessions_disabled.go +++ b/feature/buildfeatures/feature_desktop_sessions_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_desktop_sessions_enabled.go b/feature/buildfeatures/feature_desktop_sessions_enabled.go index 84658de952c86..4f03b9da894fa 100644 --- a/feature/buildfeatures/feature_desktop_sessions_enabled.go +++ b/feature/buildfeatures/feature_desktop_sessions_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_dns_disabled.go b/feature/buildfeatures/feature_dns_disabled.go index 30d7379cb9092..e59e4faef0a9a 100644 --- a/feature/buildfeatures/feature_dns_disabled.go +++ b/feature/buildfeatures/feature_dns_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_dns_enabled.go b/feature/buildfeatures/feature_dns_enabled.go index 962f2596bf5c9..f7c7097143aa9 100644 --- a/feature/buildfeatures/feature_dns_enabled.go +++ b/feature/buildfeatures/feature_dns_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_doctor_disabled.go b/feature/buildfeatures/feature_doctor_disabled.go index 8c15e951e311f..a08af9c837771 100644 --- a/feature/buildfeatures/feature_doctor_disabled.go +++ b/feature/buildfeatures/feature_doctor_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_doctor_enabled.go b/feature/buildfeatures/feature_doctor_enabled.go index a8a0bb7d2056b..502950855dd69 100644 --- a/feature/buildfeatures/feature_doctor_enabled.go +++ b/feature/buildfeatures/feature_doctor_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_drive_disabled.go b/feature/buildfeatures/feature_drive_disabled.go index 07202638952e8..90d2eac1b0d15 100644 --- a/feature/buildfeatures/feature_drive_disabled.go +++ b/feature/buildfeatures/feature_drive_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_drive_enabled.go b/feature/buildfeatures/feature_drive_enabled.go index 9f58836a43fc7..8117585c5ef79 100644 --- a/feature/buildfeatures/feature_drive_enabled.go +++ b/feature/buildfeatures/feature_drive_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_gro_disabled.go b/feature/buildfeatures/feature_gro_disabled.go index ffbd0da2e3e4f..9da12c587851f 100644 --- a/feature/buildfeatures/feature_gro_disabled.go +++ b/feature/buildfeatures/feature_gro_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_gro_enabled.go b/feature/buildfeatures/feature_gro_enabled.go index e2c8024e07815..5ca7aeef52b1c 100644 --- a/feature/buildfeatures/feature_gro_enabled.go +++ b/feature/buildfeatures/feature_gro_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_health_disabled.go b/feature/buildfeatures/feature_health_disabled.go index 2f2bcf240a455..59cb53d8c44ba 100644 --- a/feature/buildfeatures/feature_health_disabled.go +++ b/feature/buildfeatures/feature_health_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_health_enabled.go b/feature/buildfeatures/feature_health_enabled.go index 00ce3684eb6db..56b9a97f0b226 100644 --- a/feature/buildfeatures/feature_health_enabled.go +++ b/feature/buildfeatures/feature_health_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_hujsonconf_disabled.go b/feature/buildfeatures/feature_hujsonconf_disabled.go index cee076bc24527..01c82724abc90 100644 --- a/feature/buildfeatures/feature_hujsonconf_disabled.go +++ b/feature/buildfeatures/feature_hujsonconf_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_hujsonconf_enabled.go b/feature/buildfeatures/feature_hujsonconf_enabled.go index aefeeace5f0b9..d321f78ae9d09 100644 --- a/feature/buildfeatures/feature_hujsonconf_enabled.go +++ b/feature/buildfeatures/feature_hujsonconf_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_identityfederation_disabled.go b/feature/buildfeatures/feature_identityfederation_disabled.go index 94488adc8637c..535a478e078f2 100644 --- a/feature/buildfeatures/feature_identityfederation_disabled.go +++ b/feature/buildfeatures/feature_identityfederation_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_identityfederation_enabled.go b/feature/buildfeatures/feature_identityfederation_enabled.go index 892d62d66c37c..85708da513542 100644 --- a/feature/buildfeatures/feature_identityfederation_enabled.go +++ b/feature/buildfeatures/feature_identityfederation_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_iptables_disabled.go b/feature/buildfeatures/feature_iptables_disabled.go index 8cda5be5d6ae6..d444aedbe5aaf 100644 --- a/feature/buildfeatures/feature_iptables_disabled.go +++ b/feature/buildfeatures/feature_iptables_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_iptables_enabled.go b/feature/buildfeatures/feature_iptables_enabled.go index 44d98473f05f2..edc8f110decd0 100644 --- a/feature/buildfeatures/feature_iptables_enabled.go +++ b/feature/buildfeatures/feature_iptables_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_kube_disabled.go b/feature/buildfeatures/feature_kube_disabled.go index 2b76c57e78b94..c16768dabe17d 100644 --- a/feature/buildfeatures/feature_kube_disabled.go +++ b/feature/buildfeatures/feature_kube_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_kube_enabled.go b/feature/buildfeatures/feature_kube_enabled.go index 7abca1759fc49..97fa18e2eed91 100644 --- a/feature/buildfeatures/feature_kube_enabled.go +++ b/feature/buildfeatures/feature_kube_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_lazywg_disabled.go b/feature/buildfeatures/feature_lazywg_disabled.go index ce81d80bab6a1..af1ad388c03a7 100644 --- a/feature/buildfeatures/feature_lazywg_disabled.go +++ b/feature/buildfeatures/feature_lazywg_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_lazywg_enabled.go b/feature/buildfeatures/feature_lazywg_enabled.go index 259357f7f86ef..f2d6a10f81580 100644 --- a/feature/buildfeatures/feature_lazywg_enabled.go +++ b/feature/buildfeatures/feature_lazywg_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_linkspeed_disabled.go b/feature/buildfeatures/feature_linkspeed_disabled.go index 19e254a740ff7..c579fdbdcea06 100644 --- a/feature/buildfeatures/feature_linkspeed_disabled.go +++ b/feature/buildfeatures/feature_linkspeed_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_linkspeed_enabled.go b/feature/buildfeatures/feature_linkspeed_enabled.go index 939858a162910..a63aabc2a3247 100644 --- a/feature/buildfeatures/feature_linkspeed_enabled.go +++ b/feature/buildfeatures/feature_linkspeed_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_linuxdnsfight_disabled.go b/feature/buildfeatures/feature_linuxdnsfight_disabled.go index 2e5b50ea06af0..801696c5f3bc9 100644 --- a/feature/buildfeatures/feature_linuxdnsfight_disabled.go +++ b/feature/buildfeatures/feature_linuxdnsfight_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_linuxdnsfight_enabled.go b/feature/buildfeatures/feature_linuxdnsfight_enabled.go index b9419fccbfc09..9637bdeebcd29 100644 --- a/feature/buildfeatures/feature_linuxdnsfight_enabled.go +++ b/feature/buildfeatures/feature_linuxdnsfight_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_listenrawdisco_disabled.go b/feature/buildfeatures/feature_listenrawdisco_disabled.go index 2911780636cb7..4bad9d002b7ad 100644 --- a/feature/buildfeatures/feature_listenrawdisco_disabled.go +++ b/feature/buildfeatures/feature_listenrawdisco_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_listenrawdisco_enabled.go b/feature/buildfeatures/feature_listenrawdisco_enabled.go index 4a4f85ae37319..e5cfe687f5716 100644 --- a/feature/buildfeatures/feature_listenrawdisco_enabled.go +++ b/feature/buildfeatures/feature_listenrawdisco_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_logtail_disabled.go b/feature/buildfeatures/feature_logtail_disabled.go index 140092a2eba5b..983055d4742a9 100644 --- a/feature/buildfeatures/feature_logtail_disabled.go +++ b/feature/buildfeatures/feature_logtail_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_logtail_enabled.go b/feature/buildfeatures/feature_logtail_enabled.go index 6e777216bf3cb..f9ce154028832 100644 --- a/feature/buildfeatures/feature_logtail_enabled.go +++ b/feature/buildfeatures/feature_logtail_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_netlog_disabled.go b/feature/buildfeatures/feature_netlog_disabled.go index 60367a12600f3..a274f6aca61b6 100644 --- a/feature/buildfeatures/feature_netlog_disabled.go +++ b/feature/buildfeatures/feature_netlog_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_netlog_enabled.go b/feature/buildfeatures/feature_netlog_enabled.go index f9d2abad30553..1206e7e92b062 100644 --- a/feature/buildfeatures/feature_netlog_enabled.go +++ b/feature/buildfeatures/feature_netlog_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_netstack_disabled.go b/feature/buildfeatures/feature_netstack_disabled.go index acb6e8e76396e..45c86c0e362b6 100644 --- a/feature/buildfeatures/feature_netstack_disabled.go +++ b/feature/buildfeatures/feature_netstack_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_netstack_enabled.go b/feature/buildfeatures/feature_netstack_enabled.go index 04f67118523a0..2fc67164ec0b0 100644 --- a/feature/buildfeatures/feature_netstack_enabled.go +++ b/feature/buildfeatures/feature_netstack_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_networkmanager_disabled.go b/feature/buildfeatures/feature_networkmanager_disabled.go index d0ec6f01796ab..9ac3a928b62e0 100644 --- a/feature/buildfeatures/feature_networkmanager_disabled.go +++ b/feature/buildfeatures/feature_networkmanager_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_networkmanager_enabled.go b/feature/buildfeatures/feature_networkmanager_enabled.go index ec284c3109f75..5dd0431e3a892 100644 --- a/feature/buildfeatures/feature_networkmanager_enabled.go +++ b/feature/buildfeatures/feature_networkmanager_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_oauthkey_disabled.go b/feature/buildfeatures/feature_oauthkey_disabled.go index 72ad1723b1d14..8801a90d6db72 100644 --- a/feature/buildfeatures/feature_oauthkey_disabled.go +++ b/feature/buildfeatures/feature_oauthkey_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_oauthkey_enabled.go b/feature/buildfeatures/feature_oauthkey_enabled.go index 39c52a2b0b46d..e03e437957a22 100644 --- a/feature/buildfeatures/feature_oauthkey_enabled.go +++ b/feature/buildfeatures/feature_oauthkey_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_osrouter_disabled.go b/feature/buildfeatures/feature_osrouter_disabled.go index ccd7192bb8899..8004589b8a466 100644 --- a/feature/buildfeatures/feature_osrouter_disabled.go +++ b/feature/buildfeatures/feature_osrouter_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_osrouter_enabled.go b/feature/buildfeatures/feature_osrouter_enabled.go index a5dacc596bfbc..78ed0ca9d96df 100644 --- a/feature/buildfeatures/feature_osrouter_enabled.go +++ b/feature/buildfeatures/feature_osrouter_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_outboundproxy_disabled.go b/feature/buildfeatures/feature_outboundproxy_disabled.go index bf74db0600927..35de2fdbda3b8 100644 --- a/feature/buildfeatures/feature_outboundproxy_disabled.go +++ b/feature/buildfeatures/feature_outboundproxy_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_outboundproxy_enabled.go b/feature/buildfeatures/feature_outboundproxy_enabled.go index 53bb99d5c6a79..5c20b9458375a 100644 --- a/feature/buildfeatures/feature_outboundproxy_enabled.go +++ b/feature/buildfeatures/feature_outboundproxy_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_peerapiclient_disabled.go b/feature/buildfeatures/feature_peerapiclient_disabled.go index 83cc2bdfeef5c..bcb45e5fe3278 100644 --- a/feature/buildfeatures/feature_peerapiclient_disabled.go +++ b/feature/buildfeatures/feature_peerapiclient_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_peerapiclient_enabled.go b/feature/buildfeatures/feature_peerapiclient_enabled.go index 0bd3f50a869ca..214af9312b68e 100644 --- a/feature/buildfeatures/feature_peerapiclient_enabled.go +++ b/feature/buildfeatures/feature_peerapiclient_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_peerapiserver_disabled.go b/feature/buildfeatures/feature_peerapiserver_disabled.go index 4a4f32b8a4065..60b2df96544bb 100644 --- a/feature/buildfeatures/feature_peerapiserver_disabled.go +++ b/feature/buildfeatures/feature_peerapiserver_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_peerapiserver_enabled.go b/feature/buildfeatures/feature_peerapiserver_enabled.go index 17d0547b80946..9c56c5309d483 100644 --- a/feature/buildfeatures/feature_peerapiserver_enabled.go +++ b/feature/buildfeatures/feature_peerapiserver_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_portlist_disabled.go b/feature/buildfeatures/feature_portlist_disabled.go index 934061fd8328f..9269a7b5e4ba6 100644 --- a/feature/buildfeatures/feature_portlist_disabled.go +++ b/feature/buildfeatures/feature_portlist_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_portlist_enabled.go b/feature/buildfeatures/feature_portlist_enabled.go index c1dc1c163b80e..31a2875363517 100644 --- a/feature/buildfeatures/feature_portlist_enabled.go +++ b/feature/buildfeatures/feature_portlist_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_portmapper_disabled.go b/feature/buildfeatures/feature_portmapper_disabled.go index 212b22d40abfb..dea23f2bd6c3b 100644 --- a/feature/buildfeatures/feature_portmapper_disabled.go +++ b/feature/buildfeatures/feature_portmapper_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_portmapper_enabled.go b/feature/buildfeatures/feature_portmapper_enabled.go index 2f915d277a313..495a5bf207d40 100644 --- a/feature/buildfeatures/feature_portmapper_enabled.go +++ b/feature/buildfeatures/feature_portmapper_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_posture_disabled.go b/feature/buildfeatures/feature_posture_disabled.go index a78b1a95720cf..9987819a84980 100644 --- a/feature/buildfeatures/feature_posture_disabled.go +++ b/feature/buildfeatures/feature_posture_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_posture_enabled.go b/feature/buildfeatures/feature_posture_enabled.go index dcd9595f9ca96..4e601d33b578f 100644 --- a/feature/buildfeatures/feature_posture_enabled.go +++ b/feature/buildfeatures/feature_posture_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_qrcodes_disabled.go b/feature/buildfeatures/feature_qrcodes_disabled.go index 4b992501c969e..64d33cfcc731a 100644 --- a/feature/buildfeatures/feature_qrcodes_disabled.go +++ b/feature/buildfeatures/feature_qrcodes_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_qrcodes_enabled.go b/feature/buildfeatures/feature_qrcodes_enabled.go index 5b74e2b3e5cbe..35fe9741b0a59 100644 --- a/feature/buildfeatures/feature_qrcodes_enabled.go +++ b/feature/buildfeatures/feature_qrcodes_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_relayserver_disabled.go b/feature/buildfeatures/feature_relayserver_disabled.go index 08ced83101f96..cee2d93fea7e4 100644 --- a/feature/buildfeatures/feature_relayserver_disabled.go +++ b/feature/buildfeatures/feature_relayserver_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_relayserver_enabled.go b/feature/buildfeatures/feature_relayserver_enabled.go index 6a35f8305d68f..3886c853d6e6c 100644 --- a/feature/buildfeatures/feature_relayserver_enabled.go +++ b/feature/buildfeatures/feature_relayserver_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_resolved_disabled.go b/feature/buildfeatures/feature_resolved_disabled.go index 283dd20c76aaa..e19576e2a9131 100644 --- a/feature/buildfeatures/feature_resolved_disabled.go +++ b/feature/buildfeatures/feature_resolved_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_resolved_enabled.go b/feature/buildfeatures/feature_resolved_enabled.go index af1b3b41e9358..46e59411784fb 100644 --- a/feature/buildfeatures/feature_resolved_enabled.go +++ b/feature/buildfeatures/feature_resolved_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_sdnotify_disabled.go b/feature/buildfeatures/feature_sdnotify_disabled.go index 7efa2d22ff587..4ae1cd8b021b0 100644 --- a/feature/buildfeatures/feature_sdnotify_disabled.go +++ b/feature/buildfeatures/feature_sdnotify_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_sdnotify_enabled.go b/feature/buildfeatures/feature_sdnotify_enabled.go index 40fec9755dd16..0f6adcaaea901 100644 --- a/feature/buildfeatures/feature_sdnotify_enabled.go +++ b/feature/buildfeatures/feature_sdnotify_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_serve_disabled.go b/feature/buildfeatures/feature_serve_disabled.go index 6d79713500e29..51aa5e4cd7291 100644 --- a/feature/buildfeatures/feature_serve_disabled.go +++ b/feature/buildfeatures/feature_serve_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_serve_enabled.go b/feature/buildfeatures/feature_serve_enabled.go index 57bf2c6b0fc2b..10638f5b47a8a 100644 --- a/feature/buildfeatures/feature_serve_enabled.go +++ b/feature/buildfeatures/feature_serve_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_ssh_disabled.go b/feature/buildfeatures/feature_ssh_disabled.go index 754f50eb6a816..c51d5425d4e3a 100644 --- a/feature/buildfeatures/feature_ssh_disabled.go +++ b/feature/buildfeatures/feature_ssh_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_ssh_enabled.go b/feature/buildfeatures/feature_ssh_enabled.go index dbdc3a89fa027..539173db4366d 100644 --- a/feature/buildfeatures/feature_ssh_enabled.go +++ b/feature/buildfeatures/feature_ssh_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_synology_disabled.go b/feature/buildfeatures/feature_synology_disabled.go index 0cdf084c32d8e..98613f16c13c9 100644 --- a/feature/buildfeatures/feature_synology_disabled.go +++ b/feature/buildfeatures/feature_synology_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_synology_enabled.go b/feature/buildfeatures/feature_synology_enabled.go index dde4123b61eb0..2090dafb51f43 100644 --- a/feature/buildfeatures/feature_synology_enabled.go +++ b/feature/buildfeatures/feature_synology_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_syspolicy_disabled.go b/feature/buildfeatures/feature_syspolicy_disabled.go index 54d32e32e71d8..e7b2b3dad1889 100644 --- a/feature/buildfeatures/feature_syspolicy_disabled.go +++ b/feature/buildfeatures/feature_syspolicy_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_syspolicy_enabled.go b/feature/buildfeatures/feature_syspolicy_enabled.go index f7c403ae9d68b..5c3b2794adc1a 100644 --- a/feature/buildfeatures/feature_syspolicy_enabled.go +++ b/feature/buildfeatures/feature_syspolicy_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_systray_disabled.go b/feature/buildfeatures/feature_systray_disabled.go index 4ae1edb0ab83f..bfd16f7a4acb0 100644 --- a/feature/buildfeatures/feature_systray_disabled.go +++ b/feature/buildfeatures/feature_systray_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_systray_enabled.go b/feature/buildfeatures/feature_systray_enabled.go index 5fd7fd220325a..602e1223d7c81 100644 --- a/feature/buildfeatures/feature_systray_enabled.go +++ b/feature/buildfeatures/feature_systray_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_taildrop_disabled.go b/feature/buildfeatures/feature_taildrop_disabled.go index 8ffe90617839f..8165a68a848f0 100644 --- a/feature/buildfeatures/feature_taildrop_disabled.go +++ b/feature/buildfeatures/feature_taildrop_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_taildrop_enabled.go b/feature/buildfeatures/feature_taildrop_enabled.go index 4f55d2801c516..c07a2a037ffff 100644 --- a/feature/buildfeatures/feature_taildrop_enabled.go +++ b/feature/buildfeatures/feature_taildrop_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tailnetlock_disabled.go b/feature/buildfeatures/feature_tailnetlock_disabled.go index 6b5a57f24ba4f..5a208babb7797 100644 --- a/feature/buildfeatures/feature_tailnetlock_disabled.go +++ b/feature/buildfeatures/feature_tailnetlock_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tailnetlock_enabled.go b/feature/buildfeatures/feature_tailnetlock_enabled.go index afedb7faad312..c65151152ea83 100644 --- a/feature/buildfeatures/feature_tailnetlock_enabled.go +++ b/feature/buildfeatures/feature_tailnetlock_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tap_disabled.go b/feature/buildfeatures/feature_tap_disabled.go index f0b3eec8d7e6f..07605ff3792dd 100644 --- a/feature/buildfeatures/feature_tap_disabled.go +++ b/feature/buildfeatures/feature_tap_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tap_enabled.go b/feature/buildfeatures/feature_tap_enabled.go index 1363c4b44afb2..0b88a42b6604b 100644 --- a/feature/buildfeatures/feature_tap_enabled.go +++ b/feature/buildfeatures/feature_tap_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tpm_disabled.go b/feature/buildfeatures/feature_tpm_disabled.go index b9d55815ef5df..b0351c80dfd96 100644 --- a/feature/buildfeatures/feature_tpm_disabled.go +++ b/feature/buildfeatures/feature_tpm_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tpm_enabled.go b/feature/buildfeatures/feature_tpm_enabled.go index dcfc8a30442ad..0af8a10e9b883 100644 --- a/feature/buildfeatures/feature_tpm_enabled.go +++ b/feature/buildfeatures/feature_tpm_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_unixsocketidentity_disabled.go b/feature/buildfeatures/feature_unixsocketidentity_disabled.go index d64e48b825eac..25320544b2282 100644 --- a/feature/buildfeatures/feature_unixsocketidentity_disabled.go +++ b/feature/buildfeatures/feature_unixsocketidentity_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_unixsocketidentity_enabled.go b/feature/buildfeatures/feature_unixsocketidentity_enabled.go index 463ac2ced3636..9511ba00f4094 100644 --- a/feature/buildfeatures/feature_unixsocketidentity_enabled.go +++ b/feature/buildfeatures/feature_unixsocketidentity_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useexitnode_disabled.go b/feature/buildfeatures/feature_useexitnode_disabled.go index 51bec8046cb35..51e95fca084bd 100644 --- a/feature/buildfeatures/feature_useexitnode_disabled.go +++ b/feature/buildfeatures/feature_useexitnode_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useexitnode_enabled.go b/feature/buildfeatures/feature_useexitnode_enabled.go index f7ab414de9477..e6df5c85fd3f4 100644 --- a/feature/buildfeatures/feature_useexitnode_enabled.go +++ b/feature/buildfeatures/feature_useexitnode_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useproxy_disabled.go b/feature/buildfeatures/feature_useproxy_disabled.go index 9f29a9820eb99..604825ba991ca 100644 --- a/feature/buildfeatures/feature_useproxy_disabled.go +++ b/feature/buildfeatures/feature_useproxy_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useproxy_enabled.go b/feature/buildfeatures/feature_useproxy_enabled.go index 9195f2fdce784..fe2ecc9ea538a 100644 --- a/feature/buildfeatures/feature_useproxy_enabled.go +++ b/feature/buildfeatures/feature_useproxy_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_usermetrics_disabled.go b/feature/buildfeatures/feature_usermetrics_disabled.go index 092c89c3b543f..96441b5138497 100644 --- a/feature/buildfeatures/feature_usermetrics_disabled.go +++ b/feature/buildfeatures/feature_usermetrics_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_usermetrics_enabled.go b/feature/buildfeatures/feature_usermetrics_enabled.go index 813e3c3477b66..427c6fd397657 100644 --- a/feature/buildfeatures/feature_usermetrics_enabled.go +++ b/feature/buildfeatures/feature_usermetrics_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useroutes_disabled.go b/feature/buildfeatures/feature_useroutes_disabled.go index ecf9d022bed74..26e2311c6fa17 100644 --- a/feature/buildfeatures/feature_useroutes_disabled.go +++ b/feature/buildfeatures/feature_useroutes_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useroutes_enabled.go b/feature/buildfeatures/feature_useroutes_enabled.go index c0a59322ecdc1..0dc7089d99165 100644 --- a/feature/buildfeatures/feature_useroutes_enabled.go +++ b/feature/buildfeatures/feature_useroutes_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_wakeonlan_disabled.go b/feature/buildfeatures/feature_wakeonlan_disabled.go index 816ac661f78ce..ca76d0b7f00df 100644 --- a/feature/buildfeatures/feature_wakeonlan_disabled.go +++ b/feature/buildfeatures/feature_wakeonlan_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_wakeonlan_enabled.go b/feature/buildfeatures/feature_wakeonlan_enabled.go index 34b3348a10fef..07bb16fba96fc 100644 --- a/feature/buildfeatures/feature_wakeonlan_enabled.go +++ b/feature/buildfeatures/feature_wakeonlan_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_webbrowser_disabled.go b/feature/buildfeatures/feature_webbrowser_disabled.go new file mode 100644 index 0000000000000..e6484479c979b --- /dev/null +++ b/feature/buildfeatures/feature_webbrowser_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_webbrowser + +package buildfeatures + +// HasWebBrowser is whether the binary was built with support for modular feature "Open URLs in the user's web browser". +// Specifically, it's whether the binary was NOT built with the "ts_omit_webbrowser" build tag. +// It's a const so it can be used for dead code elimination. +const HasWebBrowser = false diff --git a/feature/buildfeatures/feature_webbrowser_enabled.go b/feature/buildfeatures/feature_webbrowser_enabled.go new file mode 100644 index 0000000000000..68d80b49f5444 --- /dev/null +++ b/feature/buildfeatures/feature_webbrowser_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_webbrowser + +package buildfeatures + +// HasWebBrowser is whether the binary was built with support for modular feature "Open URLs in the user's web browser". +// Specifically, it's whether the binary was NOT built with the "ts_omit_webbrowser" build tag. +// It's a const so it can be used for dead code elimination. +const HasWebBrowser = true diff --git a/feature/buildfeatures/feature_webclient_disabled.go b/feature/buildfeatures/feature_webclient_disabled.go index a7b24f4ac2dda..9792265c6d559 100644 --- a/feature/buildfeatures/feature_webclient_disabled.go +++ b/feature/buildfeatures/feature_webclient_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_webclient_enabled.go b/feature/buildfeatures/feature_webclient_enabled.go index e40dad33c6ebb..cb558a5fe7831 100644 --- a/feature/buildfeatures/feature_webclient_enabled.go +++ b/feature/buildfeatures/feature_webclient_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/gen.go b/feature/buildfeatures/gen.go index e967cb8ff1906..cf8e9d49f1f47 100644 --- a/feature/buildfeatures/gen.go +++ b/feature/buildfeatures/gen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore @@ -17,7 +17,7 @@ import ( "tailscale.com/util/must" ) -const header = `// Copyright (c) Tailscale Inc & AUTHORS +const header = `// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code g|e|n|e|r|a|t|e|d by gen.go; D|O N|OT E|D|I|T. diff --git a/feature/c2n/c2n.go b/feature/c2n/c2n.go index ae942e31d0d95..331a9af955a07 100644 --- a/feature/c2n/c2n.go +++ b/feature/c2n/c2n.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package c2n registers support for C2N (Control-to-Node) communications. diff --git a/feature/capture/capture.go b/feature/capture/capture.go index e5e150de8e761..d7145e7c1ebd7 100644 --- a/feature/capture/capture.go +++ b/feature/capture/capture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package capture formats packet logging into a debug pcap stream. diff --git a/feature/capture/dissector/dissector.go b/feature/capture/dissector/dissector.go index ab2f6c2ec1607..dec90e28b11b1 100644 --- a/feature/capture/dissector/dissector.go +++ b/feature/capture/dissector/dissector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dissector contains the Lua dissector for Tailscale packets. diff --git a/feature/clientupdate/clientupdate.go b/feature/clientupdate/clientupdate.go index 45fd21129b4e7..d47d048156046 100644 --- a/feature/clientupdate/clientupdate.go +++ b/feature/clientupdate/clientupdate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package clientupdate enables the client update feature. diff --git a/feature/condlite/expvar/expvar.go b/feature/condlite/expvar/expvar.go index edc16ac771b13..68aaf6e2cddf9 100644 --- a/feature/condlite/expvar/expvar.go +++ b/feature/condlite/expvar/expvar.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(ts_omit_debug && ts_omit_clientmetrics && ts_omit_usermetrics) diff --git a/feature/condlite/expvar/omit.go b/feature/condlite/expvar/omit.go index a21d94deb48eb..b5481695c9947 100644 --- a/feature/condlite/expvar/omit.go +++ b/feature/condlite/expvar/omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_debug && ts_omit_clientmetrics && ts_omit_usermetrics diff --git a/feature/condregister/awsparamstore/doc.go b/feature/condregister/awsparamstore/doc.go new file mode 100644 index 0000000000000..93a26e3c22fae --- /dev/null +++ b/feature/condregister/awsparamstore/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package awsparamstore conditionally registers the awsparamstore feature for +// resolving secrets from AWS Parameter Store. +package awsparamstore diff --git a/feature/condregister/awsparamstore/maybe_awsparamstore.go b/feature/condregister/awsparamstore/maybe_awsparamstore.go new file mode 100644 index 0000000000000..78c3f31006765 --- /dev/null +++ b/feature/condregister/awsparamstore/maybe_awsparamstore.go @@ -0,0 +1,8 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build (ts_aws || (linux && (arm64 || amd64) && !android)) && !ts_omit_aws + +package awsparamstore + +import _ "tailscale.com/feature/awsparamstore" diff --git a/feature/condregister/condregister.go b/feature/condregister/condregister.go index 654483d1d7745..e0d72b7ac293c 100644 --- a/feature/condregister/condregister.go +++ b/feature/condregister/condregister.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The condregister package registers all conditional features guarded diff --git a/feature/condregister/identityfederation/doc.go b/feature/condregister/identityfederation/doc.go index 503b2c8f127d5..ee811bdec8064 100644 --- a/feature/condregister/identityfederation/doc.go +++ b/feature/condregister/identityfederation/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package identityfederation registers support for authkey resolution diff --git a/feature/condregister/identityfederation/maybe_identityfederation.go b/feature/condregister/identityfederation/maybe_identityfederation.go index b1db42fc3c77a..04c37e36faa47 100644 --- a/feature/condregister/identityfederation/maybe_identityfederation.go +++ b/feature/condregister/identityfederation/maybe_identityfederation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_identityfederation diff --git a/feature/condregister/maybe_ace.go b/feature/condregister/maybe_ace.go index 07023171144a5..a926f5b0d8810 100644 --- a/feature/condregister/maybe_ace.go +++ b/feature/condregister/maybe_ace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_ace diff --git a/feature/condregister/maybe_appconnectors.go b/feature/condregister/maybe_appconnectors.go index 70112d7810b10..3b872bc1eb90d 100644 --- a/feature/condregister/maybe_appconnectors.go +++ b/feature/condregister/maybe_appconnectors.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_appconnectors diff --git a/feature/condregister/maybe_c2n.go b/feature/condregister/maybe_c2n.go index c222af533a37d..99258956ad8b2 100644 --- a/feature/condregister/maybe_c2n.go +++ b/feature/condregister/maybe_c2n.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_c2n diff --git a/feature/condregister/maybe_capture.go b/feature/condregister/maybe_capture.go index 0c68331f101cd..991843cb58194 100644 --- a/feature/condregister/maybe_capture.go +++ b/feature/condregister/maybe_capture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_capture diff --git a/feature/condregister/maybe_clientupdate.go b/feature/condregister/maybe_clientupdate.go index bc694f970c543..df36d8e67b92e 100644 --- a/feature/condregister/maybe_clientupdate.go +++ b/feature/condregister/maybe_clientupdate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_clientupdate diff --git a/feature/condregister/maybe_conn25.go b/feature/condregister/maybe_conn25.go index fb885bfe32fc1..6ce14b2b3f1ce 100644 --- a/feature/condregister/maybe_conn25.go +++ b/feature/condregister/maybe_conn25.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_conn25 diff --git a/feature/condregister/maybe_debugportmapper.go b/feature/condregister/maybe_debugportmapper.go index 4990d09ea5833..443b21e02d331 100644 --- a/feature/condregister/maybe_debugportmapper.go +++ b/feature/condregister/maybe_debugportmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debugportmapper diff --git a/feature/condregister/maybe_doctor.go b/feature/condregister/maybe_doctor.go index 3dc9ffa539312..41d504c5394df 100644 --- a/feature/condregister/maybe_doctor.go +++ b/feature/condregister/maybe_doctor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_doctor diff --git a/feature/condregister/maybe_drive.go b/feature/condregister/maybe_drive.go index cb447ff289a29..4d979e821852b 100644 --- a/feature/condregister/maybe_drive.go +++ b/feature/condregister/maybe_drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/feature/condregister/maybe_linkspeed.go b/feature/condregister/maybe_linkspeed.go index 46064b39a5935..5e9e9e4004b19 100644 --- a/feature/condregister/maybe_linkspeed.go +++ b/feature/condregister/maybe_linkspeed.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_linkspeed diff --git a/feature/condregister/maybe_linuxdnsfight.go b/feature/condregister/maybe_linuxdnsfight.go index 0dae62b00ab8a..2866fd0d7a891 100644 --- a/feature/condregister/maybe_linuxdnsfight.go +++ b/feature/condregister/maybe_linuxdnsfight.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_linuxdnsfight diff --git a/feature/condregister/maybe_osrouter.go b/feature/condregister/maybe_osrouter.go index 7ab85add22021..771a86e48bfbf 100644 --- a/feature/condregister/maybe_osrouter.go +++ b/feature/condregister/maybe_osrouter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_osrouter diff --git a/feature/condregister/maybe_portlist.go b/feature/condregister/maybe_portlist.go index 1be56f177daf8..8de98d528ae48 100644 --- a/feature/condregister/maybe_portlist.go +++ b/feature/condregister/maybe_portlist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_portlist diff --git a/feature/condregister/maybe_posture.go b/feature/condregister/maybe_posture.go index 6f14c27137127..ca056eb3612b8 100644 --- a/feature/condregister/maybe_posture.go +++ b/feature/condregister/maybe_posture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_posture diff --git a/feature/condregister/maybe_relayserver.go b/feature/condregister/maybe_relayserver.go index 3360dd0627cc1..49404bef8a08b 100644 --- a/feature/condregister/maybe_relayserver.go +++ b/feature/condregister/maybe_relayserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_relayserver diff --git a/feature/condregister/maybe_sdnotify.go b/feature/condregister/maybe_sdnotify.go index 647996f881d8f..ac8e180cd791f 100644 --- a/feature/condregister/maybe_sdnotify.go +++ b/feature/condregister/maybe_sdnotify.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_sdnotify diff --git a/feature/condregister/maybe_store_aws.go b/feature/condregister/maybe_store_aws.go index 8358b49f05843..96de819d1ee39 100644 --- a/feature/condregister/maybe_store_aws.go +++ b/feature/condregister/maybe_store_aws.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (ts_aws || (linux && (arm64 || amd64) && !android)) && !ts_omit_aws diff --git a/feature/condregister/maybe_store_kube.go b/feature/condregister/maybe_store_kube.go index bb795b05e2450..a71ed00e2e248 100644 --- a/feature/condregister/maybe_store_kube.go +++ b/feature/condregister/maybe_store_kube.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (ts_kube || (linux && (arm64 || amd64) && !android)) && !ts_omit_kube diff --git a/feature/condregister/maybe_syspolicy.go b/feature/condregister/maybe_syspolicy.go index 49ec5c02c63e1..66d44ea3804e4 100644 --- a/feature/condregister/maybe_syspolicy.go +++ b/feature/condregister/maybe_syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/feature/condregister/maybe_taildrop.go b/feature/condregister/maybe_taildrop.go index 5fd7b5f8c9a00..264ccff02006f 100644 --- a/feature/condregister/maybe_taildrop.go +++ b/feature/condregister/maybe_taildrop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_taildrop diff --git a/feature/condregister/maybe_tap.go b/feature/condregister/maybe_tap.go index eca4fc3ac84af..fc3997b17dca4 100644 --- a/feature/condregister/maybe_tap.go +++ b/feature/condregister/maybe_tap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_tap diff --git a/feature/condregister/maybe_tpm.go b/feature/condregister/maybe_tpm.go index caa57fef11d73..f46a0996f4feb 100644 --- a/feature/condregister/maybe_tpm.go +++ b/feature/condregister/maybe_tpm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_tpm diff --git a/feature/condregister/maybe_wakeonlan.go b/feature/condregister/maybe_wakeonlan.go index 14cae605d1468..6fc32bb22fe26 100644 --- a/feature/condregister/maybe_wakeonlan.go +++ b/feature/condregister/maybe_wakeonlan.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_wakeonlan diff --git a/feature/condregister/oauthkey/doc.go b/feature/condregister/oauthkey/doc.go index 4c4ea5e4e3078..af1c931480672 100644 --- a/feature/condregister/oauthkey/doc.go +++ b/feature/condregister/oauthkey/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package oauthkey registers support for OAuth key resolution diff --git a/feature/condregister/oauthkey/maybe_oauthkey.go b/feature/condregister/oauthkey/maybe_oauthkey.go index be8d04b8ec035..9e912f149a3db 100644 --- a/feature/condregister/oauthkey/maybe_oauthkey.go +++ b/feature/condregister/oauthkey/maybe_oauthkey.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_oauthkey diff --git a/feature/condregister/portmapper/doc.go b/feature/condregister/portmapper/doc.go index 5c30538c43a11..21e45c4a676be 100644 --- a/feature/condregister/portmapper/doc.go +++ b/feature/condregister/portmapper/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portmapper registers support for portmapper diff --git a/feature/condregister/portmapper/maybe_portmapper.go b/feature/condregister/portmapper/maybe_portmapper.go index c306fd3d5a1f0..e1be2b3ced942 100644 --- a/feature/condregister/portmapper/maybe_portmapper.go +++ b/feature/condregister/portmapper/maybe_portmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_portmapper diff --git a/feature/condregister/useproxy/doc.go b/feature/condregister/useproxy/doc.go index 1e8abb358fa83..d5fde367082e2 100644 --- a/feature/condregister/useproxy/doc.go +++ b/feature/condregister/useproxy/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package useproxy registers support for using proxies diff --git a/feature/condregister/useproxy/useproxy.go b/feature/condregister/useproxy/useproxy.go index bda6e49c0bb95..bca17de88f62e 100644 --- a/feature/condregister/useproxy/useproxy.go +++ b/feature/condregister/useproxy/useproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_useproxy diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index e7baca4bd10b7..05f087e21df46 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -1,18 +1,31 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package conn25 registers the conn25 feature and implements its associated ipnext.Extension. +// conn25 will be an app connector like feature that routes traffic for configured domains via +// connector devices and avoids the "too many routes" pitfall of app connector. It is currently +// (2026-02-04) some peer API routes for clients to tell connectors about their desired routing. package conn25 import ( "encoding/json" + "errors" "net/http" + "net/netip" + "sync" - "tailscale.com/appc" + "go4.org/netipx" + "golang.org/x/net/dns/dnsmessage" "tailscale.com/feature" "tailscale.com/ipn/ipnext" "tailscale.com/ipn/ipnlocal" + "tailscale.com/net/dns" + "tailscale.com/tailcfg" + "tailscale.com/types/appctype" "tailscale.com/types/logger" + "tailscale.com/util/dnsname" + "tailscale.com/util/mak" + "tailscale.com/util/set" ) // featureName is the name of the feature implemented by this package. @@ -23,7 +36,8 @@ func init() { feature.Register(featureName) newExtension := func(logf logger.Logf, sb ipnext.SafeBackend) (ipnext.Extension, error) { e := &extension{ - conn: &appc.Conn25{}, + conn25: newConn25(logger.WithPrefix(logf, "conn25: ")), + backend: sb, } return e, nil } @@ -43,7 +57,11 @@ func handleConnectorTransitIP(h ipnlocal.PeerAPIHandler, w http.ResponseWriter, // extension is an [ipnext.Extension] managing the connector on platforms // that import this package. type extension struct { - conn *appc.Conn25 + conn25 *Conn25 // safe for concurrent access and only set at creation + backend ipnext.SafeBackend // safe for concurrent access and only set at creation + + mu sync.Mutex // protects the fields below + isDNSHookRegistered bool } // Name implements [ipnext.Extension]. @@ -53,6 +71,7 @@ func (e *extension) Name() string { // Init implements [ipnext.Extension]. func (e *extension) Init(host ipnext.Host) error { + host.Hooks().OnSelfChange.Add(e.onSelfChange) return nil } @@ -68,13 +87,13 @@ func (e *extension) handleConnectorTransitIP(h ipnlocal.PeerAPIHandler, w http.R http.Error(w, "Method should be POST", http.StatusMethodNotAllowed) return } - var req appc.ConnectorTransitIPRequest + var req ConnectorTransitIPRequest err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxBodyBytes+1)).Decode(&req) if err != nil { http.Error(w, "Error decoding JSON", http.StatusBadRequest) return } - resp := e.conn.HandleConnectorTransitIPRequest(h.Peer().ID(), req) + resp := e.conn25.handleConnectorTransitIPRequest(h.Peer().ID(), req) bs, err := json.Marshal(resp) if err != nil { http.Error(w, "Error encoding JSON", http.StatusInternalServerError) @@ -82,3 +101,484 @@ func (e *extension) handleConnectorTransitIP(h ipnlocal.PeerAPIHandler, w http.R } w.Write(bs) } + +func (e *extension) onSelfChange(selfNode tailcfg.NodeView) { + err := e.conn25.reconfig(selfNode) + if err != nil { + e.conn25.client.logf("error during Reconfig onSelfChange: %v", err) + return + } + + if e.conn25.isConfigured() { + err = e.registerDNSHook() + } else { + err = e.unregisterDNSHook() + } + if err != nil { + e.conn25.client.logf("error managing DNS hook onSelfChange: %v", err) + } +} + +func (e *extension) registerDNSHook() error { + e.mu.Lock() + defer e.mu.Unlock() + if e.isDNSHookRegistered { + return nil + } + err := e.setDNSHookLocked(e.conn25.mapDNSResponse) + if err == nil { + e.isDNSHookRegistered = true + } + return err +} + +func (e *extension) unregisterDNSHook() error { + e.mu.Lock() + defer e.mu.Unlock() + if !e.isDNSHookRegistered { + return nil + } + err := e.setDNSHookLocked(nil) + if err == nil { + e.isDNSHookRegistered = false + } + return err +} + +func (e *extension) setDNSHookLocked(fx dns.ResponseMapper) error { + dnsManager, ok := e.backend.Sys().DNSManager.GetOK() + if !ok || dnsManager == nil { + return errors.New("couldn't get DNSManager from sys") + } + dnsManager.SetQueryResponseMapper(fx) + return nil +} + +type appAddr struct { + app string + addr netip.Addr +} + +// Conn25 holds state for routing traffic for a domain via a connector. +type Conn25 struct { + client *client + connector *connector +} + +func (c *Conn25) isConfigured() bool { + return c.client.isConfigured() +} + +func newConn25(logf logger.Logf) *Conn25 { + c := &Conn25{ + client: &client{logf: logf}, + connector: &connector{logf: logf}, + } + return c +} + +func ipSetFromIPRanges(rs []netipx.IPRange) (*netipx.IPSet, error) { + b := &netipx.IPSetBuilder{} + for _, r := range rs { + b.AddRange(r) + } + return b.IPSet() +} + +func (c *Conn25) reconfig(selfNode tailcfg.NodeView) error { + cfg, err := configFromNodeView(selfNode) + if err != nil { + return err + } + if err := c.client.reconfig(cfg); err != nil { + return err + } + if err := c.connector.reconfig(cfg); err != nil { + return err + } + return nil +} + +// mapDNSResponse parses and inspects the DNS response, and uses the +// contents to assign addresses for connecting. It does not yet modify +// the response. +func (c *Conn25) mapDNSResponse(buf []byte) []byte { + return c.client.mapDNSResponse(buf) +} + +const dupeTransitIPMessage = "Duplicate transit address in ConnectorTransitIPRequest" + +// handleConnectorTransitIPRequest creates a ConnectorTransitIPResponse in response to a ConnectorTransitIPRequest. +// It updates the connectors mapping of TransitIP->DestinationIP per peer (tailcfg.NodeID). +// If a peer has stored this mapping in the connector Conn25 will route traffic to TransitIPs to DestinationIPs for that peer. +func (c *Conn25) handleConnectorTransitIPRequest(nid tailcfg.NodeID, ctipr ConnectorTransitIPRequest) ConnectorTransitIPResponse { + resp := ConnectorTransitIPResponse{} + seen := map[netip.Addr]bool{} + for _, each := range ctipr.TransitIPs { + if seen[each.TransitIP] { + resp.TransitIPs = append(resp.TransitIPs, TransitIPResponse{ + Code: OtherFailure, + Message: dupeTransitIPMessage, + }) + continue + } + tipresp := c.connector.handleTransitIPRequest(nid, each) + seen[each.TransitIP] = true + resp.TransitIPs = append(resp.TransitIPs, tipresp) + } + return resp +} + +func (s *connector) handleTransitIPRequest(nid tailcfg.NodeID, tipr TransitIPRequest) TransitIPResponse { + s.mu.Lock() + defer s.mu.Unlock() + if s.transitIPs == nil { + s.transitIPs = make(map[tailcfg.NodeID]map[netip.Addr]appAddr) + } + peerMap, ok := s.transitIPs[nid] + if !ok { + peerMap = make(map[netip.Addr]appAddr) + s.transitIPs[nid] = peerMap + } + peerMap[tipr.TransitIP] = appAddr{addr: tipr.DestinationIP, app: tipr.App} + return TransitIPResponse{} +} + +func (s *connector) transitIPTarget(nid tailcfg.NodeID, tip netip.Addr) netip.Addr { + s.mu.Lock() + defer s.mu.Unlock() + return s.transitIPs[nid][tip].addr +} + +// TransitIPRequest details a single TransitIP allocation request from a client to a +// connector. +type TransitIPRequest struct { + // TransitIP is the intermediate destination IP that will be received at this + // connector and will be replaced by DestinationIP when performing DNAT. + TransitIP netip.Addr `json:"transitIP,omitzero"` + + // DestinationIP is the final destination IP that connections to the TransitIP + // should be mapped to when performing DNAT. + DestinationIP netip.Addr `json:"destinationIP,omitzero"` + + // App is the name of the connector application from the tailnet + // configuration. + App string `json:"app,omitzero"` +} + +// ConnectorTransitIPRequest is the request body for a PeerAPI request to +// /connector/transit-ip and can include zero or more TransitIP allocation requests. +type ConnectorTransitIPRequest struct { + // TransitIPs is the list of requested mappings. + TransitIPs []TransitIPRequest `json:"transitIPs,omitempty"` +} + +// TransitIPResponseCode appears in TransitIPResponse and signifies success or failure status. +type TransitIPResponseCode int + +const ( + // OK indicates that the mapping was created as requested. + OK TransitIPResponseCode = 0 + + // OtherFailure indicates that the mapping failed for a reason that does not have + // another relevant [TransitIPResponsecode]. + OtherFailure TransitIPResponseCode = 1 +) + +// TransitIPResponse is the response to a TransitIPRequest +type TransitIPResponse struct { + // Code is an error code indicating success or failure of the [TransitIPRequest]. + Code TransitIPResponseCode `json:"code,omitzero"` + // Message is an error message explaining what happened, suitable for logging but + // not necessarily suitable for displaying in a UI to non-technical users. It + // should be empty when [Code] is [OK]. + Message string `json:"message,omitzero"` +} + +// ConnectorTransitIPResponse is the response to a ConnectorTransitIPRequest +type ConnectorTransitIPResponse struct { + // TransitIPs is the list of outcomes for each requested mapping. Elements + // correspond to the order of [ConnectorTransitIPRequest.TransitIPs]. + TransitIPs []TransitIPResponse `json:"transitIPs,omitempty"` +} + +const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experimental" + +// config holds the config from the policy and lookups derived from that. +// config is not safe for concurrent use. +type config struct { + isConfigured bool + apps []appctype.Conn25Attr + appsByDomain map[dnsname.FQDN][]string + selfRoutedDomains set.Set[dnsname.FQDN] +} + +func configFromNodeView(n tailcfg.NodeView) (config, error) { + apps, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.Conn25Attr](n.CapMap(), AppConnectorsExperimentalAttrName) + if err != nil { + return config{}, err + } + if len(apps) == 0 { + return config{}, nil + } + selfTags := set.SetOf(n.Tags().AsSlice()) + cfg := config{ + isConfigured: true, + apps: apps, + appsByDomain: map[dnsname.FQDN][]string{}, + selfRoutedDomains: set.Set[dnsname.FQDN]{}, + } + for _, app := range apps { + selfMatchesTags := false + for _, tag := range app.Connectors { + if selfTags.Contains(tag) { + selfMatchesTags = true + break + } + } + for _, d := range app.Domains { + fqdn, err := dnsname.ToFQDN(d) + if err != nil { + return config{}, err + } + mak.Set(&cfg.appsByDomain, fqdn, append(cfg.appsByDomain[fqdn], app.Name)) + if selfMatchesTags { + cfg.selfRoutedDomains.Add(fqdn) + } + } + } + return cfg, nil +} + +// client performs the conn25 functionality for clients of connectors +// It allocates magic and transit IP addresses and communicates them with +// connectors. +// It's safe for concurrent use. +type client struct { + logf logger.Logf + + mu sync.Mutex // protects the fields below + magicIPPool *ippool + transitIPPool *ippool + assignments addrAssignments + config config +} + +func (c *client) isConfigured() bool { + c.mu.Lock() + defer c.mu.Unlock() + return c.config.isConfigured +} + +func (c *client) reconfig(newCfg config) error { + c.mu.Lock() + defer c.mu.Unlock() + + c.config = newCfg + + // TODO(fran) this is not the correct way to manage the pools and changes to the pools. + // We probably want to: + // * check the pools haven't changed + // * reset the whole connector if the pools change? or just if they've changed to exclude + // addresses we have in use? + // * have config separate from the apps for this (rather than multiple potentially conflicting places) + // but this works while we are just getting started here. + for _, app := range c.config.apps { + if c.magicIPPool != nil { // just take the first config and never reconfig + break + } + if app.MagicIPPool == nil { + continue + } + mipp, err := ipSetFromIPRanges(app.MagicIPPool) + if err != nil { + return err + } + tipp, err := ipSetFromIPRanges(app.TransitIPPool) + if err != nil { + return err + } + c.magicIPPool = newIPPool(mipp) + c.transitIPPool = newIPPool(tipp) + } + return nil +} + +func (c *client) isConnectorDomain(domain dnsname.FQDN) bool { + c.mu.Lock() + defer c.mu.Unlock() + appNames, ok := c.config.appsByDomain[domain] + return ok && len(appNames) > 0 +} + +// reserveAddresses tries to make an assignment of addrs from the address pools +// for this domain+dst address, so that this client can use conn25 connectors. +// It checks that this domain should be routed and that this client is not itself a connector for the domain +// and generally if it is valid to make the assignment. +func (c *client) reserveAddresses(domain dnsname.FQDN, dst netip.Addr) (addrs, error) { + c.mu.Lock() + defer c.mu.Unlock() + if existing, ok := c.assignments.lookupByDomainDst(domain, dst); ok { + return existing, nil + } + appNames, _ := c.config.appsByDomain[domain] + // only reserve for first app + app := appNames[0] + mip, err := c.magicIPPool.next() + if err != nil { + return addrs{}, err + } + tip, err := c.transitIPPool.next() + if err != nil { + return addrs{}, err + } + as := addrs{ + dst: dst, + magic: mip, + transit: tip, + app: app, + domain: domain, + } + if err := c.assignments.insert(as); err != nil { + return addrs{}, err + } + return as, nil +} + +func (c *client) enqueueAddressAssignment(addrs addrs) { + // TODO(fran) 2026-02-03 asynchronously send peerapi req to connector to + // allocate these addresses for us. +} + +func (c *client) mapDNSResponse(buf []byte) []byte { + var p dnsmessage.Parser + if _, err := p.Start(buf); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + if err := p.SkipAllQuestions(); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + break + } + if err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + + if h.Class != dnsmessage.ClassINET { + if err := p.SkipAnswer(); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + continue + } + + switch h.Type { + case dnsmessage.TypeA: + domain, err := dnsname.ToFQDN(h.Name.String()) + if err != nil { + c.logf("bad dnsname: %v", err) + return buf + } + if !c.isConnectorDomain(domain) { + if err := p.SkipAnswer(); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + continue + } + r, err := p.AResource() + if err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + addrs, err := c.reserveAddresses(domain, netip.AddrFrom4(r.A)) + if err != nil { + c.logf("error assigning connector addresses: %v", err) + return buf + } + if !addrs.isValid() { + c.logf("assigned connector addresses unexpectedly empty: %v", err) + return buf + } + c.enqueueAddressAssignment(addrs) + default: + if err := p.SkipAnswer(); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + continue + } + } + + // TODO(fran) 2026-01-21 return a dns response with addresses + // swapped out for the magic IPs to make conn25 work. + return buf +} + +type connector struct { + logf logger.Logf + + mu sync.Mutex // protects the fields below + // transitIPs is a map of connector client peer NodeID -> client transitIPs that we update as connector client peers instruct us to, and then use to route traffic to its destination on behalf of connector clients. + transitIPs map[tailcfg.NodeID]map[netip.Addr]appAddr + config config +} + +func (s *connector) reconfig(newCfg config) error { + s.mu.Lock() + defer s.mu.Unlock() + s.config = newCfg + return nil +} + +type addrs struct { + dst netip.Addr + magic netip.Addr + transit netip.Addr + domain dnsname.FQDN + app string +} + +func (c addrs) isValid() bool { + return c.dst.IsValid() +} + +// domainDst is a key for looking up an existing address assignment by the +// DNS response domain and destination IP pair. +type domainDst struct { + domain dnsname.FQDN + dst netip.Addr +} + +// addrAssignments is the collection of addrs assigned by this client +// supporting lookup by magicip or domain+dst +type addrAssignments struct { + byMagicIP map[netip.Addr]addrs + byDomainDst map[domainDst]addrs +} + +func (a *addrAssignments) insert(as addrs) error { + // we likely will want to allow overwriting in the future when we + // have address expiry, but for now this should not happen + if _, ok := a.byMagicIP[as.magic]; ok { + return errors.New("byMagicIP key exists") + } + ddst := domainDst{domain: as.domain, dst: as.dst} + if _, ok := a.byDomainDst[ddst]; ok { + return errors.New("byDomainDst key exists") + } + mak.Set(&a.byMagicIP, as.magic, as) + mak.Set(&a.byDomainDst, ddst, as) + return nil +} + +func (a *addrAssignments) lookupByDomainDst(domain dnsname.FQDN, dst netip.Addr) (addrs, bool) { + v, ok := a.byDomainDst[domainDst{domain: domain, dst: dst}] + return v, ok +} diff --git a/feature/conn25/conn25_test.go b/feature/conn25/conn25_test.go new file mode 100644 index 0000000000000..d63e84e024738 --- /dev/null +++ b/feature/conn25/conn25_test.go @@ -0,0 +1,524 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package conn25 + +import ( + "encoding/json" + "net/netip" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "go4.org/netipx" + "golang.org/x/net/dns/dnsmessage" + "tailscale.com/tailcfg" + "tailscale.com/types/appctype" + "tailscale.com/types/logger" + "tailscale.com/util/dnsname" + "tailscale.com/util/must" + "tailscale.com/util/set" +) + +func mustIPSetFromPrefix(s string) *netipx.IPSet { + b := &netipx.IPSetBuilder{} + b.AddPrefix(netip.MustParsePrefix(s)) + set, err := b.IPSet() + if err != nil { + panic(err) + } + return set +} + +// TestHandleConnectorTransitIPRequestZeroLength tests that if sent a +// ConnectorTransitIPRequest with 0 TransitIPRequests, we respond with a +// ConnectorTransitIPResponse with 0 TransitIPResponses. +func TestHandleConnectorTransitIPRequestZeroLength(t *testing.T) { + c := newConn25(logger.Discard) + req := ConnectorTransitIPRequest{} + nid := tailcfg.NodeID(1) + + resp := c.handleConnectorTransitIPRequest(nid, req) + if len(resp.TransitIPs) != 0 { + t.Fatalf("n TransitIPs in response: %d, want 0", len(resp.TransitIPs)) + } +} + +// TestHandleConnectorTransitIPRequestStoresAddr tests that if sent a +// request with a transit addr and a destination addr we store that mapping +// and can retrieve it. If sent another req with a different dst for that transit addr +// we store that instead. +func TestHandleConnectorTransitIPRequestStoresAddr(t *testing.T) { + c := newConn25(logger.Discard) + nid := tailcfg.NodeID(1) + tip := netip.MustParseAddr("0.0.0.1") + dip := netip.MustParseAddr("1.2.3.4") + dip2 := netip.MustParseAddr("1.2.3.5") + mr := func(t, d netip.Addr) ConnectorTransitIPRequest { + return ConnectorTransitIPRequest{ + TransitIPs: []TransitIPRequest{ + {TransitIP: t, DestinationIP: d}, + }, + } + } + + resp := c.handleConnectorTransitIPRequest(nid, mr(tip, dip)) + if len(resp.TransitIPs) != 1 { + t.Fatalf("n TransitIPs in response: %d, want 1", len(resp.TransitIPs)) + } + got := resp.TransitIPs[0].Code + if got != TransitIPResponseCode(0) { + t.Fatalf("TransitIP Code: %d, want 0", got) + } + gotAddr := c.connector.transitIPTarget(nid, tip) + if gotAddr != dip { + t.Fatalf("Connector stored destination for tip: %v, want %v", gotAddr, dip) + } + + // mapping can be overwritten + resp2 := c.handleConnectorTransitIPRequest(nid, mr(tip, dip2)) + if len(resp2.TransitIPs) != 1 { + t.Fatalf("n TransitIPs in response: %d, want 1", len(resp2.TransitIPs)) + } + got2 := resp.TransitIPs[0].Code + if got2 != TransitIPResponseCode(0) { + t.Fatalf("TransitIP Code: %d, want 0", got2) + } + gotAddr2 := c.connector.transitIPTarget(nid, tip) + if gotAddr2 != dip2 { + t.Fatalf("Connector stored destination for tip: %v, want %v", gotAddr, dip2) + } +} + +// TestHandleConnectorTransitIPRequestMultipleTIP tests that we can +// get a req with multiple mappings and we store them all. Including +// multiple transit addrs for the same destination. +func TestHandleConnectorTransitIPRequestMultipleTIP(t *testing.T) { + c := newConn25(logger.Discard) + nid := tailcfg.NodeID(1) + tip := netip.MustParseAddr("0.0.0.1") + tip2 := netip.MustParseAddr("0.0.0.2") + tip3 := netip.MustParseAddr("0.0.0.3") + dip := netip.MustParseAddr("1.2.3.4") + dip2 := netip.MustParseAddr("1.2.3.5") + req := ConnectorTransitIPRequest{ + TransitIPs: []TransitIPRequest{ + {TransitIP: tip, DestinationIP: dip}, + {TransitIP: tip2, DestinationIP: dip2}, + // can store same dst addr for multiple transit addrs + {TransitIP: tip3, DestinationIP: dip}, + }, + } + resp := c.handleConnectorTransitIPRequest(nid, req) + if len(resp.TransitIPs) != 3 { + t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) + } + + for i := 0; i < 3; i++ { + got := resp.TransitIPs[i].Code + if got != TransitIPResponseCode(0) { + t.Fatalf("i=%d TransitIP Code: %d, want 0", i, got) + } + } + gotAddr1 := c.connector.transitIPTarget(nid, tip) + if gotAddr1 != dip { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip, gotAddr1, dip) + } + gotAddr2 := c.connector.transitIPTarget(nid, tip2) + if gotAddr2 != dip2 { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip2, gotAddr2, dip2) + } + gotAddr3 := c.connector.transitIPTarget(nid, tip3) + if gotAddr3 != dip { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip3, gotAddr3, dip) + } +} + +// TestHandleConnectorTransitIPRequestSameTIP tests that if we get +// a req that has more than one TransitIPRequest for the same transit addr +// only the first is stored, and the subsequent ones get an error code and +// message in the response. +func TestHandleConnectorTransitIPRequestSameTIP(t *testing.T) { + c := newConn25(logger.Discard) + nid := tailcfg.NodeID(1) + tip := netip.MustParseAddr("0.0.0.1") + tip2 := netip.MustParseAddr("0.0.0.2") + dip := netip.MustParseAddr("1.2.3.4") + dip2 := netip.MustParseAddr("1.2.3.5") + dip3 := netip.MustParseAddr("1.2.3.6") + req := ConnectorTransitIPRequest{ + TransitIPs: []TransitIPRequest{ + {TransitIP: tip, DestinationIP: dip}, + // cannot have dupe TransitIPs in one ConnectorTransitIPRequest + {TransitIP: tip, DestinationIP: dip2}, + {TransitIP: tip2, DestinationIP: dip3}, + }, + } + + resp := c.handleConnectorTransitIPRequest(nid, req) + if len(resp.TransitIPs) != 3 { + t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) + } + + got := resp.TransitIPs[0].Code + if got != TransitIPResponseCode(0) { + t.Fatalf("i=0 TransitIP Code: %d, want 0", got) + } + msg := resp.TransitIPs[0].Message + if msg != "" { + t.Fatalf("i=0 TransitIP Message: \"%s\", want \"%s\"", msg, "") + } + got1 := resp.TransitIPs[1].Code + if got1 != TransitIPResponseCode(1) { + t.Fatalf("i=1 TransitIP Code: %d, want 1", got1) + } + msg1 := resp.TransitIPs[1].Message + if msg1 != dupeTransitIPMessage { + t.Fatalf("i=1 TransitIP Message: \"%s\", want \"%s\"", msg1, dupeTransitIPMessage) + } + got2 := resp.TransitIPs[2].Code + if got2 != TransitIPResponseCode(0) { + t.Fatalf("i=2 TransitIP Code: %d, want 0", got2) + } + msg2 := resp.TransitIPs[2].Message + if msg2 != "" { + t.Fatalf("i=2 TransitIP Message: \"%s\", want \"%s\"", msg, "") + } + + gotAddr1 := c.connector.transitIPTarget(nid, tip) + if gotAddr1 != dip { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip, gotAddr1, dip) + } + gotAddr2 := c.connector.transitIPTarget(nid, tip2) + if gotAddr2 != dip3 { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip2, gotAddr2, dip3) + } +} + +// TestGetDstIPUnknownTIP tests that unknown transit addresses can be looked up without problem. +func TestTransitIPTargetUnknownTIP(t *testing.T) { + c := newConn25(logger.Discard) + nid := tailcfg.NodeID(1) + tip := netip.MustParseAddr("0.0.0.1") + got := c.connector.transitIPTarget(nid, tip) + want := netip.Addr{} + if got != want { + t.Fatalf("Unknown transit addr, want: %v, got %v", want, got) + } +} + +func TestReserveIPs(t *testing.T) { + c := newConn25(logger.Discard) + c.client.magicIPPool = newIPPool(mustIPSetFromPrefix("100.64.0.0/24")) + c.client.transitIPPool = newIPPool(mustIPSetFromPrefix("169.254.0.0/24")) + mbd := map[dnsname.FQDN][]string{} + mbd["example.com."] = []string{"a"} + c.client.config.appsByDomain = mbd + + dst := netip.MustParseAddr("0.0.0.1") + addrs, err := c.client.reserveAddresses("example.com.", dst) + if err != nil { + t.Fatal(err) + } + + wantDst := netip.MustParseAddr("0.0.0.1") // same as dst we pass in + wantMagic := netip.MustParseAddr("100.64.0.0") // first from magic pool + wantTransit := netip.MustParseAddr("169.254.0.0") // first from transit pool + wantApp := "a" // the app name related to example.com. + wantDomain := must.Get(dnsname.ToFQDN("example.com.")) + + if wantDst != addrs.dst { + t.Errorf("want %v, got %v", wantDst, addrs.dst) + } + if wantMagic != addrs.magic { + t.Errorf("want %v, got %v", wantMagic, addrs.magic) + } + if wantTransit != addrs.transit { + t.Errorf("want %v, got %v", wantTransit, addrs.transit) + } + if wantApp != addrs.app { + t.Errorf("want %s, got %s", wantApp, addrs.app) + } + if wantDomain != addrs.domain { + t.Errorf("want %s, got %s", wantDomain, addrs.domain) + } +} + +func TestReconfig(t *testing.T) { + rawCfg := `{"name":"app1","connectors":["tag:woo"],"domains":["example.com"]}` + capMap := tailcfg.NodeCapMap{ + tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{ + tailcfg.RawMessage(rawCfg), + }, + } + + c := newConn25(logger.Discard) + sn := (&tailcfg.Node{ + CapMap: capMap, + }).View() + + err := c.reconfig(sn) + if err != nil { + t.Fatal(err) + } + + if len(c.client.config.apps) != 1 || c.client.config.apps[0].Name != "app1" { + t.Fatalf("want apps to have one entry 'app1', got %v", c.client.config.apps) + } +} + +func TestConfigReconfig(t *testing.T) { + for _, tt := range []struct { + name string + rawCfg string + cfg []appctype.Conn25Attr + tags []string + wantErr bool + wantAppsByDomain map[dnsname.FQDN][]string + wantSelfRoutedDomains set.Set[dnsname.FQDN] + }{ + { + name: "bad-config", + rawCfg: `bad`, + wantErr: true, + }, + { + name: "simple", + cfg: []appctype.Conn25Attr{ + {Name: "one", Domains: []string{"a.example.com"}, Connectors: []string{"tag:one"}}, + {Name: "two", Domains: []string{"b.example.com"}, Connectors: []string{"tag:two"}}, + }, + tags: []string{"tag:one"}, + wantAppsByDomain: map[dnsname.FQDN][]string{ + "a.example.com.": {"one"}, + "b.example.com.": {"two"}, + }, + wantSelfRoutedDomains: set.SetOf([]dnsname.FQDN{"a.example.com."}), + }, + { + name: "more-complex", + cfg: []appctype.Conn25Attr{ + {Name: "one", Domains: []string{"1.a.example.com", "1.b.example.com"}, Connectors: []string{"tag:one", "tag:onea"}}, + {Name: "two", Domains: []string{"2.b.example.com", "2.c.example.com"}, Connectors: []string{"tag:two", "tag:twoa"}}, + {Name: "three", Domains: []string{"1.b.example.com", "1.c.example.com"}, Connectors: []string{}}, + {Name: "four", Domains: []string{"4.b.example.com", "4.d.example.com"}, Connectors: []string{"tag:four"}}, + }, + tags: []string{"tag:onea", "tag:four", "tag:unrelated"}, + wantAppsByDomain: map[dnsname.FQDN][]string{ + "1.a.example.com.": {"one"}, + "1.b.example.com.": {"one", "three"}, + "1.c.example.com.": {"three"}, + "2.b.example.com.": {"two"}, + "2.c.example.com.": {"two"}, + "4.b.example.com.": {"four"}, + "4.d.example.com.": {"four"}, + }, + wantSelfRoutedDomains: set.SetOf([]dnsname.FQDN{"1.a.example.com.", "1.b.example.com.", "4.b.example.com.", "4.d.example.com."}), + }, + } { + t.Run(tt.name, func(t *testing.T) { + cfg := []tailcfg.RawMessage{tailcfg.RawMessage(tt.rawCfg)} + if tt.cfg != nil { + cfg = []tailcfg.RawMessage{} + for _, attr := range tt.cfg { + bs, err := json.Marshal(attr) + if err != nil { + t.Fatalf("unexpected error in test setup: %v", err) + } + cfg = append(cfg, tailcfg.RawMessage(bs)) + } + } + capMap := tailcfg.NodeCapMap{ + tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): cfg, + } + sn := (&tailcfg.Node{ + CapMap: capMap, + Tags: tt.tags, + }).View() + c, err := configFromNodeView(sn) + if (err != nil) != tt.wantErr { + t.Fatalf("wantErr: %t, err: %v", tt.wantErr, err) + } + if diff := cmp.Diff(tt.wantAppsByDomain, c.appsByDomain); diff != "" { + t.Errorf("appsByDomain diff (-want, +got):\n%s", diff) + } + if diff := cmp.Diff(tt.wantSelfRoutedDomains, c.selfRoutedDomains); diff != "" { + t.Errorf("selfRoutedDomains diff (-want, +got):\n%s", diff) + } + }) + } +} + +func makeSelfNode(t *testing.T, attr appctype.Conn25Attr, tags []string) tailcfg.NodeView { + t.Helper() + bs, err := json.Marshal(attr) + if err != nil { + t.Fatalf("unexpected error in test setup: %v", err) + } + cfg := []tailcfg.RawMessage{tailcfg.RawMessage(bs)} + capMap := tailcfg.NodeCapMap{ + tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): cfg, + } + return (&tailcfg.Node{ + CapMap: capMap, + Tags: tags, + }).View() +} + +func rangeFrom(from, to string) netipx.IPRange { + return netipx.IPRangeFrom( + netip.MustParseAddr("100.64.0."+from), + netip.MustParseAddr("100.64.0."+to), + ) +} + +func TestMapDNSResponse(t *testing.T) { + makeDNSResponse := func(domain string, addrs []dnsmessage.AResource) []byte { + b := dnsmessage.NewBuilder(nil, + dnsmessage.Header{ + ID: 1, + Response: true, + Authoritative: true, + RCode: dnsmessage.RCodeSuccess, + }) + b.EnableCompression() + + if err := b.StartQuestions(); err != nil { + t.Fatal(err) + } + + if err := b.Question(dnsmessage.Question{ + Name: dnsmessage.MustNewName(domain), + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + }); err != nil { + t.Fatal(err) + } + + if err := b.StartAnswers(); err != nil { + t.Fatal(err) + } + + for _, addr := range addrs { + b.AResource( + dnsmessage.ResourceHeader{ + Name: dnsmessage.MustNewName(domain), + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + }, + addr, + ) + } + + outbs, err := b.Finish() + if err != nil { + t.Fatal(err) + } + return outbs + } + + for _, tt := range []struct { + name string + domain string + addrs []dnsmessage.AResource + wantByMagicIP map[netip.Addr]addrs + }{ + { + name: "one-ip-matches", + domain: "example.com.", + addrs: []dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}}, + // these are 'expected' because they are the beginning of the provided pools + wantByMagicIP: map[netip.Addr]addrs{ + netip.MustParseAddr("100.64.0.0"): { + domain: "example.com.", + dst: netip.MustParseAddr("1.0.0.0"), + magic: netip.MustParseAddr("100.64.0.0"), + transit: netip.MustParseAddr("100.64.0.40"), + app: "app1", + }, + }, + }, + { + name: "multiple-ip-matches", + domain: "example.com.", + addrs: []dnsmessage.AResource{ + {A: [4]byte{1, 0, 0, 0}}, + {A: [4]byte{2, 0, 0, 0}}, + }, + wantByMagicIP: map[netip.Addr]addrs{ + netip.MustParseAddr("100.64.0.0"): { + domain: "example.com.", + dst: netip.MustParseAddr("1.0.0.0"), + magic: netip.MustParseAddr("100.64.0.0"), + transit: netip.MustParseAddr("100.64.0.40"), + app: "app1", + }, + netip.MustParseAddr("100.64.0.1"): { + domain: "example.com.", + dst: netip.MustParseAddr("2.0.0.0"), + magic: netip.MustParseAddr("100.64.0.1"), + transit: netip.MustParseAddr("100.64.0.41"), + app: "app1", + }, + }, + }, + { + name: "no-domain-match", + domain: "x.example.com.", + addrs: []dnsmessage.AResource{ + {A: [4]byte{1, 0, 0, 0}}, + {A: [4]byte{2, 0, 0, 0}}, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + dnsResp := makeDNSResponse(tt.domain, tt.addrs) + sn := makeSelfNode(t, appctype.Conn25Attr{ + Name: "app1", + Connectors: []string{"tag:woo"}, + Domains: []string{"example.com"}, + MagicIPPool: []netipx.IPRange{rangeFrom("0", "10"), rangeFrom("20", "30")}, + TransitIPPool: []netipx.IPRange{rangeFrom("40", "50")}, + }, []string{}) + c := newConn25(logger.Discard) + c.reconfig(sn) + + bs := c.mapDNSResponse(dnsResp) + if !reflect.DeepEqual(dnsResp, bs) { + t.Fatal("shouldn't be changing the bytes (yet)") + } + if diff := cmp.Diff(tt.wantByMagicIP, c.client.assignments.byMagicIP, cmpopts.EquateComparable(addrs{}, netip.Addr{})); diff != "" { + t.Errorf("byMagicIP diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestReserveAddressesDeduplicated(t *testing.T) { + c := newConn25(logger.Discard) + c.client.magicIPPool = newIPPool(mustIPSetFromPrefix("100.64.0.0/24")) + c.client.transitIPPool = newIPPool(mustIPSetFromPrefix("169.254.0.0/24")) + c.client.config.appsByDomain = map[dnsname.FQDN][]string{"example.com.": {"a"}} + + dst := netip.MustParseAddr("0.0.0.1") + first, err := c.client.reserveAddresses("example.com.", dst) + if err != nil { + t.Fatal(err) + } + + second, err := c.client.reserveAddresses("example.com.", dst) + if err != nil { + t.Fatal(err) + } + + if first != second { + t.Errorf("expected same addrs on repeated call, got first=%v second=%v", first, second) + } + if got := len(c.client.assignments.byMagicIP); got != 1 { + t.Errorf("want 1 entry in byMagicIP, got %d", got) + } + if got := len(c.client.assignments.byDomainDst); got != 1 { + t.Errorf("want 1 entry in byDomainDst, got %d", got) + } +} diff --git a/appc/ippool.go b/feature/conn25/ippool.go similarity index 96% rename from appc/ippool.go rename to feature/conn25/ippool.go index a2e86a7c296a8..e50186d880914 100644 --- a/appc/ippool.go +++ b/feature/conn25/ippool.go @@ -1,7 +1,7 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -package appc +package conn25 import ( "errors" diff --git a/appc/ippool_test.go b/feature/conn25/ippool_test.go similarity index 95% rename from appc/ippool_test.go rename to feature/conn25/ippool_test.go index 64b76738f661e..ccfaad3eb71e1 100644 --- a/appc/ippool_test.go +++ b/feature/conn25/ippool_test.go @@ -1,7 +1,7 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -package appc +package conn25 import ( "errors" diff --git a/feature/debugportmapper/debugportmapper.go b/feature/debugportmapper/debugportmapper.go index 2625086c64dcf..45f3c22084fba 100644 --- a/feature/debugportmapper/debugportmapper.go +++ b/feature/debugportmapper/debugportmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package debugportmapper registers support for debugging Tailscale's diff --git a/feature/doctor/doctor.go b/feature/doctor/doctor.go index 875b57d14c4f0..db061311b2e1f 100644 --- a/feature/doctor/doctor.go +++ b/feature/doctor/doctor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The doctor package registers the "doctor" problem diagnosis support into the diff --git a/feature/drive/drive.go b/feature/drive/drive.go index 3660a2b959643..1cf616a143d6e 100644 --- a/feature/drive/drive.go +++ b/feature/drive/drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package drive registers the Taildrive (file server) feature. diff --git a/feature/feature.go b/feature/feature.go index 48a4aff43b84d..5bd79db45c4ed 100644 --- a/feature/feature.go +++ b/feature/feature.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package feature tracks which features are linked into the binary. diff --git a/feature/featuretags/featuretags.go b/feature/featuretags/featuretags.go index 99df18b5a3c3b..4220c02b75fa2 100644 --- a/feature/featuretags/featuretags.go +++ b/feature/featuretags/featuretags.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The featuretags package is a registry of all the ts_omit-able build tags. @@ -84,7 +84,7 @@ type FeatureMeta struct { Deps []FeatureTag // other features this feature requires // ImplementationDetail is whether the feature is an internal implementation - // detail. That is, it's not something a user wuold care about having or not + // detail. That is, it's not something a user would care about having or not // having, but we'd like to able to omit from builds if no other // user-visible features depend on it. ImplementationDetail bool @@ -130,6 +130,7 @@ var Features = map[FeatureTag]FeatureMeta{ "captiveportal": {Sym: "CaptivePortal", Desc: "Captive portal detection"}, "capture": {Sym: "Capture", Desc: "Packet capture"}, "cli": {Sym: "CLI", Desc: "embed the CLI into the tailscaled binary"}, + "colorable": {Sym: "Colorable", Desc: "Colorized terminal output"}, "cliconndiag": {Sym: "CLIConnDiag", Desc: "CLI connection error diagnostics"}, "clientmetrics": {Sym: "ClientMetrics", Desc: "Client metrics support"}, "clientupdate": { @@ -138,7 +139,12 @@ var Features = map[FeatureTag]FeatureMeta{ Deps: []FeatureTag{"c2n"}, }, "completion": {Sym: "Completion", Desc: "CLI shell completion"}, - "cloud": {Sym: "Cloud", Desc: "detect cloud environment to learn instances IPs and DNS servers"}, + "conn25": {Sym: "Conn25", Desc: "Route traffic for configured domains through connector devices"}, + "completion_scripts": { + Sym: "CompletionScripts", Desc: "embed CLI shell completion scripts", + Deps: []FeatureTag{"completion"}, + }, + "cloud": {Sym: "Cloud", Desc: "detect cloud environment to learn instances IPs and DNS servers"}, "dbus": { Sym: "DBus", Desc: "Linux DBus support", @@ -251,7 +257,7 @@ var Features = map[FeatureTag]FeatureMeta{ "systray": { Sym: "SysTray", Desc: "Linux system tray", - Deps: []FeatureTag{"dbus"}, + Deps: []FeatureTag{"dbus", "webbrowser"}, }, "taildrop": { Sym: "Taildrop", @@ -285,6 +291,10 @@ var Features = map[FeatureTag]FeatureMeta{ Desc: "Usermetrics (documented, stable) metrics support", }, "wakeonlan": {Sym: "WakeOnLAN", Desc: "Wake-on-LAN support"}, + "webbrowser": { + Sym: "WebBrowser", + Desc: "Open URLs in the user's web browser", + }, "webclient": { Sym: "WebClient", Desc: "Web client support", Deps: []FeatureTag{"serve"}, diff --git a/feature/featuretags/featuretags_test.go b/feature/featuretags/featuretags_test.go index 893ab0e6a1c71..c8a9f77ae5e10 100644 --- a/feature/featuretags/featuretags_test.go +++ b/feature/featuretags/featuretags_test.go @@ -1,11 +1,14 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package featuretags import ( "maps" + "os/exec" + "regexp" "slices" + "strings" "testing" "tailscale.com/util/set" @@ -83,3 +86,37 @@ func TestRequiredBy(t *testing.T) { } } } + +// Verify that all "ts_omit_foo" build tags are declared in featuretags.go +func TestAllOmitBuildTagsDeclared(t *testing.T) { + if _, err := exec.LookPath("git"); err != nil { + t.Skipf("git not found in PATH; skipping test") + } + root, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() + if err != nil { + t.Skipf("not in a git repository; skipping test") + } + + cmd := exec.Command("git", "grep", "ts_omit_") + cmd.Dir = strings.TrimSpace(string(root)) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("git grep failed: %v\nOutput:\n%s", err, out) + } + rx := regexp.MustCompile(`\bts_omit_[\w_]+\b`) + found := set.Set[string]{} + rx.ReplaceAllFunc(out, func(tag []byte) []byte { + tagStr := string(tag) + found.Add(tagStr) + return tag + }) + for tag := range found { + if strings.EqualFold(tag, "ts_omit_foo") { + continue + } + ft := FeatureTag(strings.TrimPrefix(tag, "ts_omit_")) + if _, ok := Features[ft]; !ok { + t.Errorf("found undeclared ts_omit_* build tags: %v", tag) + } + } +} diff --git a/feature/hooks.go b/feature/hooks.go index 7e31061a7eaac..5cd3c0d818ca6 100644 --- a/feature/hooks.go +++ b/feature/hooks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package feature diff --git a/feature/identityfederation/identityfederation.go b/feature/identityfederation/identityfederation.go index f75b096a603a2..4b96fd6a2020c 100644 --- a/feature/identityfederation/identityfederation.go +++ b/feature/identityfederation/identityfederation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package identityfederation registers support for using ID tokens to diff --git a/feature/identityfederation/identityfederation_test.go b/feature/identityfederation/identityfederation_test.go index b050f1a019e38..5e3660dc58725 100644 --- a/feature/identityfederation/identityfederation_test.go +++ b/feature/identityfederation/identityfederation_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package identityfederation diff --git a/feature/linkspeed/doc.go b/feature/linkspeed/doc.go index 2d2fcf0929808..b3adf88ef76e8 100644 --- a/feature/linkspeed/doc.go +++ b/feature/linkspeed/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package linkspeed registers support for setting the TUN link speed on Linux, diff --git a/feature/linkspeed/linkspeed_linux.go b/feature/linkspeed/linkspeed_linux.go index 90e33d4c9fea4..4e36e281ca80c 100644 --- a/feature/linkspeed/linkspeed_linux.go +++ b/feature/linkspeed/linkspeed_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/feature/linuxdnsfight/linuxdnsfight.go b/feature/linuxdnsfight/linuxdnsfight.go index 02d99a3144246..ea37ed7a5b9ef 100644 --- a/feature/linuxdnsfight/linuxdnsfight.go +++ b/feature/linuxdnsfight/linuxdnsfight.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/feature/linuxdnsfight/linuxdnsfight_test.go b/feature/linuxdnsfight/linuxdnsfight_test.go index bd3463666d46b..661ba7f6f3a00 100644 --- a/feature/linuxdnsfight/linuxdnsfight_test.go +++ b/feature/linuxdnsfight/linuxdnsfight_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/feature/oauthkey/oauthkey.go b/feature/oauthkey/oauthkey.go index 336340c85109b..532f6ec73bab9 100644 --- a/feature/oauthkey/oauthkey.go +++ b/feature/oauthkey/oauthkey.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package oauthkey registers support for using OAuth client secrets to diff --git a/feature/oauthkey/oauthkey_test.go b/feature/oauthkey/oauthkey_test.go index b550d8c2ce77a..f8027e45a922e 100644 --- a/feature/oauthkey/oauthkey_test.go +++ b/feature/oauthkey/oauthkey_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package oauthkey diff --git a/feature/portlist/portlist.go b/feature/portlist/portlist.go index 7d69796ffd5d2..4d2908962bca8 100644 --- a/feature/portlist/portlist.go +++ b/feature/portlist/portlist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portlist contains code to poll the local system for open ports @@ -122,6 +122,19 @@ func (e *Extension) runPollLoop() { return } + // Before we do potentially expensive work below (polling what might be + // a ton of ports), double check that we actually need to do it. + // TODO(bradfitz): the onSelfChange, and onChangeProfile hooks above are + // not enough, because CollectServices is a NetMap-level thing and not a + // change to the local self node. We should add an eventbus topic for + // when CollectServices changes probably, or move the CollectServices + // thing into a local node self cap (at least logically, if not on the + // wire) so then the onSelfChange hook would cover it. In the meantime, + // we'll just end up doing some extra checks every PollInterval, which + // is not the end of the world, and fixes the problem of picking up + // changes to CollectServices that come down in the netmap. + e.updateShouldUploadServices() + if !e.shouldUploadServicesAtomic.Load() { continue } diff --git a/feature/portmapper/portmapper.go b/feature/portmapper/portmapper.go index d1b903cb69c20..3d2004993a510 100644 --- a/feature/portmapper/portmapper.go +++ b/feature/portmapper/portmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portmapper registers support for NAT-PMP, PCP, and UPnP port diff --git a/feature/posture/posture.go b/feature/posture/posture.go index 977e7429571a8..d8db1ac1933fb 100644 --- a/feature/posture/posture.go +++ b/feature/posture/posture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package posture registers support for device posture checking, diff --git a/feature/relayserver/relayserver.go b/feature/relayserver/relayserver.go index b29a6abed5336..45d6abcc1d3d6 100644 --- a/feature/relayserver/relayserver.go +++ b/feature/relayserver/relayserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package relayserver registers the relay server feature and implements its diff --git a/feature/relayserver/relayserver_test.go b/feature/relayserver/relayserver_test.go index 807306c707bc1..730e25a00d0d3 100644 --- a/feature/relayserver/relayserver_test.go +++ b/feature/relayserver/relayserver_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package relayserver diff --git a/feature/sdnotify.go b/feature/sdnotify.go index 7a786dfabd519..45f280c8116f2 100644 --- a/feature/sdnotify.go +++ b/feature/sdnotify.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package feature diff --git a/feature/sdnotify/sdnotify.go b/feature/sdnotify/sdnotify.go index d13aa63f23c15..d74eafd52d1d7 100644 --- a/feature/sdnotify/sdnotify.go +++ b/feature/sdnotify/sdnotify.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /* diff --git a/feature/sdnotify/sdnotify_linux.go b/feature/sdnotify/sdnotify_linux.go index 2b13e24bbe509..6a3df879638c9 100644 --- a/feature/sdnotify/sdnotify_linux.go +++ b/feature/sdnotify/sdnotify_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/feature/syspolicy/syspolicy.go b/feature/syspolicy/syspolicy.go index 08c3cf3736b29..dce2eb0b18bbb 100644 --- a/feature/syspolicy/syspolicy.go +++ b/feature/syspolicy/syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package syspolicy provides an interface for system-wide policy management. diff --git a/feature/taildrop/delete.go b/feature/taildrop/delete.go index 8b03a125f445e..dc5036006a2f6 100644 --- a/feature/taildrop/delete.go +++ b/feature/taildrop/delete.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/delete_test.go b/feature/taildrop/delete_test.go index 36950f58288cb..2b740a3fb2d6c 100644 --- a/feature/taildrop/delete_test.go +++ b/feature/taildrop/delete_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/doc.go b/feature/taildrop/doc.go index 8980a217096c0..c394ebe82e18a 100644 --- a/feature/taildrop/doc.go +++ b/feature/taildrop/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package taildrop registers the taildrop (file sending) feature. diff --git a/feature/taildrop/ext.go b/feature/taildrop/ext.go index 6bdb375ccfe63..3a4ed456d2269 100644 --- a/feature/taildrop/ext.go +++ b/feature/taildrop/ext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/fileops.go b/feature/taildrop/fileops.go index 14f76067a8094..beac0c375c574 100644 --- a/feature/taildrop/fileops.go +++ b/feature/taildrop/fileops.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/fileops_fs.go b/feature/taildrop/fileops_fs.go index 4fecbe4af6bbb..4a5b3e71a0f55 100644 --- a/feature/taildrop/fileops_fs.go +++ b/feature/taildrop/fileops_fs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android diff --git a/feature/taildrop/integration_test.go b/feature/taildrop/integration_test.go index 75896a95b2b54..ad66aa827faa0 100644 --- a/feature/taildrop/integration_test.go +++ b/feature/taildrop/integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop_test diff --git a/feature/taildrop/localapi.go b/feature/taildrop/localapi.go index 8a3904f9f0198..2af057ae8d88c 100644 --- a/feature/taildrop/localapi.go +++ b/feature/taildrop/localapi.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/paths.go b/feature/taildrop/paths.go index 79dc37d8f0699..76054ef4d58b3 100644 --- a/feature/taildrop/paths.go +++ b/feature/taildrop/paths.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/peerapi.go b/feature/taildrop/peerapi.go index b75ce33b864b4..8b92c8c85b0cd 100644 --- a/feature/taildrop/peerapi.go +++ b/feature/taildrop/peerapi.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/peerapi_test.go b/feature/taildrop/peerapi_test.go index 254d8794e8273..65f881be906b7 100644 --- a/feature/taildrop/peerapi_test.go +++ b/feature/taildrop/peerapi_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/resume.go b/feature/taildrop/resume.go index 20ef527a6da55..208d61de3767a 100644 --- a/feature/taildrop/resume.go +++ b/feature/taildrop/resume.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/resume_test.go b/feature/taildrop/resume_test.go index 4e59d401dcc53..69dd547a123e0 100644 --- a/feature/taildrop/resume_test.go +++ b/feature/taildrop/resume_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/retrieve.go b/feature/taildrop/retrieve.go index e767bac324684..dd1b75b174db7 100644 --- a/feature/taildrop/retrieve.go +++ b/feature/taildrop/retrieve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/send.go b/feature/taildrop/send.go index 32ba5f6f0d644..668166d4409cf 100644 --- a/feature/taildrop/send.go +++ b/feature/taildrop/send.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/send_test.go b/feature/taildrop/send_test.go index 9ffa5fccc0a36..c1def2afdd106 100644 --- a/feature/taildrop/send_test.go +++ b/feature/taildrop/send_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/taildrop.go b/feature/taildrop/taildrop.go index 6c3deaed1b538..7042ca97aa7ef 100644 --- a/feature/taildrop/taildrop.go +++ b/feature/taildrop/taildrop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package taildrop contains the implementation of the Taildrop diff --git a/feature/taildrop/taildrop_test.go b/feature/taildrop/taildrop_test.go index 0d77273f0aab0..5c48412e044ba 100644 --- a/feature/taildrop/taildrop_test.go +++ b/feature/taildrop/taildrop_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/target_test.go b/feature/taildrop/target_test.go index 57c96a77a4802..7948c488293bf 100644 --- a/feature/taildrop/target_test.go +++ b/feature/taildrop/target_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/tap/tap_linux.go b/feature/tap/tap_linux.go index 53dcabc364d6b..e66f74ba4d1e4 100644 --- a/feature/tap/tap_linux.go +++ b/feature/tap/tap_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tap registers Tailscale's experimental (demo) Linux TAP (Layer 2) support. diff --git a/feature/tpm/attestation.go b/feature/tpm/attestation.go index 197a8d6b8798a..8955d5b98f605 100644 --- a/feature/tpm/attestation.go +++ b/feature/tpm/attestation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/tpm/attestation_test.go b/feature/tpm/attestation_test.go index e7ff729871230..06518b63a9059 100644 --- a/feature/tpm/attestation_test.go +++ b/feature/tpm/attestation_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/tpm/tpm.go b/feature/tpm/tpm.go index 8df269b95bc2e..e257aa7bc2174 100644 --- a/feature/tpm/tpm.go +++ b/feature/tpm/tpm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tpm implements support for TPM 2.0 devices. diff --git a/feature/tpm/tpm_linux.go b/feature/tpm/tpm_linux.go index 3f05c9a8c38ad..e7c214c0be6a5 100644 --- a/feature/tpm/tpm_linux.go +++ b/feature/tpm/tpm_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/tpm/tpm_other.go b/feature/tpm/tpm_other.go index 108b2c057e4bd..c34d8edb24617 100644 --- a/feature/tpm/tpm_other.go +++ b/feature/tpm/tpm_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !windows diff --git a/feature/tpm/tpm_test.go b/feature/tpm/tpm_test.go index afce570fc250d..02fb13f58c908 100644 --- a/feature/tpm/tpm_test.go +++ b/feature/tpm/tpm_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/tpm/tpm_windows.go b/feature/tpm/tpm_windows.go index 429d20cb879f7..168c7dca135a4 100644 --- a/feature/tpm/tpm_windows.go +++ b/feature/tpm/tpm_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/useproxy/useproxy.go b/feature/useproxy/useproxy.go index a18e60577af85..96be23710caa1 100644 --- a/feature/useproxy/useproxy.go +++ b/feature/useproxy/useproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package useproxy registers support for using system proxies. diff --git a/feature/wakeonlan/wakeonlan.go b/feature/wakeonlan/wakeonlan.go index 96c424084dcc6..5a567ad44237d 100644 --- a/feature/wakeonlan/wakeonlan.go +++ b/feature/wakeonlan/wakeonlan.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wakeonlan registers the Wake-on-LAN feature. diff --git a/flake.nix b/flake.nix index 149223d0aac60..c9e3b50a1ad73 100644 --- a/flake.nix +++ b/flake.nix @@ -4,7 +4,7 @@ # environment for working on tailscale, for use with "nix develop". # # For more information about this and why this file is useful, see: -# https://nixos.wiki/wiki/Flakes +# https://wiki.nixos.org/wiki/Flakes # # Also look into direnv: https://direnv.net/, this can make it so that you can # automatically get your environment set up when you change folders into the @@ -55,7 +55,7 @@ system = system; overlays = [ (final: prev: { - go_1_25 = prev.go_1_25.overrideAttrs { + go_1_26 = prev.go_1_26.overrideAttrs { version = goVersion; src = prev.fetchFromGitHub { owner = "tailscale"; @@ -132,7 +132,7 @@ }); devShells = eachSystem (pkgs: { - devShell = pkgs.mkShell { + default = pkgs.mkShell { packages = with pkgs; [ curl git @@ -140,7 +140,7 @@ gotools graphviz perl - go_1_25 + go_1_26 yarn # qemu and e2fsprogs are needed for natlab @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-WeMTOkERj4hvdg4yPaZ1gRgKnhRIBXX55kUVbX/k/xM= +# nix-direnv cache busting line: sha256-rhuWEEN+CtumVxOw6Dy/IRxWIrZ2x6RJb6ULYwXCQc4= diff --git a/go.mod b/go.mod index a8ec79e6e014f..352023fefe006 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module tailscale.com -go 1.25.5 +go 1.26.1 require ( filippo.io/mkcert v1.4.4 @@ -17,15 +17,17 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3 github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 - github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd + github.com/bradfitz/go-tool-cache v0.0.0-20260216153636-9e5201344fe5 + github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032 github.com/bramvdbogaerde/go-scp v1.4.0 github.com/cilium/ebpf v0.16.0 github.com/coder/websocket v1.8.12 github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf + github.com/creachadair/mds v0.25.9 github.com/creachadair/msync v0.7.1 github.com/creachadair/taskgroup v0.13.2 - github.com/creack/pty v1.1.23 + github.com/creack/pty v1.1.24 github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e github.com/distribution/reference v0.6.0 @@ -36,17 +38,20 @@ require ( github.com/fogleman/gg v1.3.0 github.com/frankban/quicktest v1.14.6 github.com/fxamacker/cbor/v2 v2.9.0 - github.com/gaissmai/bart v0.18.0 + github.com/gaissmai/bart v0.26.1 github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced github.com/go-logr/zapr v1.3.0 github.com/go-ole/go-ole v1.3.0 github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 + github.com/gokrazy/breakglass v0.0.0-20251229072214-9dbc0478d486 + github.com/gokrazy/gokrazy v0.0.0-20260123094004-294c93fa173c + github.com/gokrazy/serial-busybox v0.0.0-20250119153030-ac58ba7574e7 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 github.com/golang/snappy v0.0.4 github.com/golangci/golangci-lint v1.57.1 github.com/google/go-cmp v0.7.0 - github.com/google/go-containerregistry v0.20.3 + github.com/google/go-containerregistry v0.20.7 github.com/google/go-tpm v0.9.4 github.com/google/gopacket v1.1.19 github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 @@ -86,6 +91,7 @@ require ( github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e github.com/tailscale/depaware v0.0.0-20251001183927-9c2ad255ef3f github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 + github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a github.com/tailscale/mkctr v0.0.0-20260107121656-ea857e3e500b @@ -108,7 +114,7 @@ require ( golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/mod v0.30.0 golang.org/x/net v0.48.0 - golang.org/x/oauth2 v0.32.0 + golang.org/x/oauth2 v0.33.0 golang.org/x/sync v0.19.0 golang.org/x/sys v0.40.0 golang.org/x/term v0.38.0 @@ -117,7 +123,7 @@ require ( golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wireguard/windows v0.5.3 gopkg.in/square/go-jose.v2 v2.6.0 - gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 + gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 helm.sh/helm/v3 v3.19.0 honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 k8s.io/api v0.34.0 @@ -147,6 +153,7 @@ require ( github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/beevik/ntp v0.3.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/boltdb/bolt v1.3.1 // indirect github.com/bombsimon/wsl/v4 v4.2.1 // indirect @@ -157,6 +164,7 @@ require ( github.com/ckaznocha/intrange v0.1.0 // indirect github.com/containerd/containerd v1.7.29 // indirect github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v1.0.0-rc.2 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect @@ -173,14 +181,18 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect + github.com/gokrazy/gokapi v0.0.0-20250222071133-506fdb322775 // indirect + github.com/gokrazy/internal v0.0.0-20251208203110-3c1aa9087c82 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-github/v66 v66.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/renameio/v2 v2.0.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect @@ -193,13 +205,18 @@ require ( github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/jjti/go-spancheck v0.5.3 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/josharian/native v1.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.0.8 // indirect + github.com/kenshaw/evdev v0.1.0 // indirect + github.com/kr/pty v1.1.8 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/macabu/inamedparam v0.1.3 // indirect + github.com/mdlayher/packet v1.1.2 // indirect + github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moby/buildkit v0.20.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -211,11 +228,13 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/puzpuzpuz/xsync v1.5.2 // indirect + github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 // indirect github.com/rubenv/sql-migrate v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/stacklok/frizbee v0.1.7 // indirect + github.com/vishvananda/netlink v1.3.1-0.20240922070040-084abd93d350 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect @@ -230,6 +249,7 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 // indirect golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect @@ -251,7 +271,7 @@ require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect dario.cat/mergo v1.0.1 // indirect - filippo.io/edwards25519 v1.1.0 // indirect + filippo.io/edwards25519 v1.2.0 // indirect github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/AlekSi/pointer v1.2.0 github.com/Antonboom/errname v0.1.12 // indirect @@ -262,7 +282,7 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect - github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect @@ -293,16 +313,16 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.12.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/docker/cli v27.5.1+incompatible // indirect + github.com/docker/cli v29.0.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v27.5.1+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect + github.com/docker/docker v28.5.2+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/ettle/strcase v0.2.0 // indirect @@ -315,7 +335,7 @@ require ( github.com/go-critic/go-critic v0.11.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-git/go-git/v5 v5.13.1 // indirect + github.com/go-git/go-git/v5 v5.16.5 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect @@ -400,8 +420,8 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pierrec/lz4/v4 v4.1.25 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.4.8 // indirect @@ -425,12 +445,12 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.7.1 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/spf13/viper v1.16.0 // indirect @@ -452,7 +472,7 @@ require ( github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.0 // indirect github.com/uudashr/gocognit v1.1.2 // indirect - github.com/vbatts/tar-split v0.11.6 // indirect + github.com/vbatts/tar-split v0.12.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/yagipy/maintidx v1.0.0 // indirect diff --git a/go.mod.sri b/go.mod.sri index b533a75654aa6..a307075942f64 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-WeMTOkERj4hvdg4yPaZ1gRgKnhRIBXX55kUVbX/k/xM= +sha256-rhuWEEN+CtumVxOw6Dy/IRxWIrZ2x6RJb6ULYwXCQc4= diff --git a/go.sum b/go.sum index 541cef6058655..b61f1d24a1db1 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,9 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc= filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= fyne.io/systray v1.11.1-0.20250812065214-4856ac3adc3c h1:km4PIleGtbbF1oxmFQuO93CyNCldwuRTPB8WlzNWNZs= @@ -97,8 +98,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= @@ -183,6 +184,9 @@ github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ= github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= +github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= +github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw= +github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -199,8 +203,10 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= -github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd h1:1Df3FBmfyUCIQ4eKzAPXIWTfewY89L0fWPWO56zWCyI= -github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd/go.mod h1:2+xptBAd0m2kZ1wLO4AYZhldLEFPy+KeGwmnlXLvy+w= +github.com/bradfitz/go-tool-cache v0.0.0-20260216153636-9e5201344fe5 h1:0sG3c7afYdBNlc3QyhckvZ4bV9iqlfqCQM1i+mWm0eE= +github.com/bradfitz/go-tool-cache v0.0.0-20260216153636-9e5201344fe5/go.mod h1:78ZLITnBUCDJeU01+wYYJKaPYYgsDzJPRfxeI8qFh5g= +github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032 h1:xDomVqO85ss/98Ky5zxM/g86bXDNBLebM2I9G/fu6uA= +github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032/go.mod h1:TG1HbU9fRVDnNgXncVkKz9GdvjIvqquXjH6QZSEVmY4= github.com/bramvdbogaerde/go-scp v1.4.0 h1:jKMwpwCbcX1KyvDbm/PDJuXcMuNVlLGi0Q0reuzjyKY= github.com/bramvdbogaerde/go-scp v1.4.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ= github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= @@ -247,8 +253,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/ckaznocha/intrange v0.1.0 h1:ZiGBhvrdsKpoEfzh9CjBfDSZof6QB0ORY5tXasUtiew= github.com/ckaznocha/intrange v0.1.0/go.mod h1:Vwa9Ekex2BrEQMg6zlrWwbs/FtYw7eS5838Q7UjK7TQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= @@ -256,20 +262,22 @@ github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9 github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= -github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= -github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= +github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= +github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creachadair/mds v0.25.9 h1:080Hr8laN2h+l3NeVCGMBpXtIPnl9mz8e4HLraGPqtA= github.com/creachadair/mds v0.25.9/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs= @@ -277,9 +285,10 @@ github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7u github.com/creachadair/msync v0.7.1/go.mod h1:8CcFlLsSujfHE5wWm19uUBLHIPDAUr6LXDwneVMO008= github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc= github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= -github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= @@ -310,14 +319,14 @@ github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/cli v27.5.1+incompatible h1:JB9cieUT9YNiMITtIsguaN55PLOHhBSz3LKVc6cqWaY= -github.com/docker/cli v27.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.0.3+incompatible h1:8J+PZIcF2xLd6h5sHPsp5pvvJA+Sr2wGQxHkRl53a1E= +github.com/docker/cli v29.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= -github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4= @@ -330,8 +339,8 @@ github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI= github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40= github.com/elastic/crd-ref-docs v0.0.12 h1:F3seyncbzUz3rT3d+caeYWhumb5ojYQ6Bl0Z+zOp16M= github.com/elastic/crd-ref-docs v0.0.12/go.mod h1:X83mMBdJt05heJUYiS3T0yJ/JkCuliuhSUNav5Gjo/U= -github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= -github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -373,8 +382,8 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= -github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= +github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo= +github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= @@ -391,8 +400,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= -github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= +github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= +github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -454,8 +463,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo= @@ -473,6 +482,18 @@ github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeH github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gokrazy/breakglass v0.0.0-20251229072214-9dbc0478d486 h1:QBELQyXGy+eCEcWtvSfslJk3y7nUPZldOwBqIz1tkXc= +github.com/gokrazy/breakglass v0.0.0-20251229072214-9dbc0478d486/go.mod h1:PFPkRFcazBmCZKo+sBaGjsWouTtfDvg13nCDm0tFOCA= +github.com/gokrazy/gokapi v0.0.0-20250222071133-506fdb322775 h1:f5+2UMRRbr3+e/gdWCBNn48chS/KMMljfbmlSSHfRBA= +github.com/gokrazy/gokapi v0.0.0-20250222071133-506fdb322775/go.mod h1:q9mIV8al0wqmqFXJhKiO3SOHkL9/7Q4kIMynqUQWhgU= +github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904/go.mod h1:pq6rGHqxMRPSaTXaCMzIZy0wLDusAJyoVNyNo05RLs0= +github.com/gokrazy/gokrazy v0.0.0-20260123094004-294c93fa173c h1:grjqEMf6dPJzZxf+gdo8rjx6bcyseO5p9hierlVkhXQ= +github.com/gokrazy/gokrazy v0.0.0-20260123094004-294c93fa173c/go.mod h1:NtMkrFeDGnwldKLi0dLdd2ipNwoVa7TI4HTxsy7lFRg= +github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA= +github.com/gokrazy/internal v0.0.0-20251208203110-3c1aa9087c82 h1:4ghNfD9NaZLpFrqQiBF6mPVFeMYXJSky38ubVA4ic2E= +github.com/gokrazy/internal v0.0.0-20251208203110-3c1aa9087c82/go.mod h1:dQY4EMkD4L5ZjYJ0SPtpgYbV7MIUMCxNIXiOfnZ6jP4= +github.com/gokrazy/serial-busybox v0.0.0-20250119153030-ac58ba7574e7 h1:gurTGc4sL7Ik+IKZ29rhGgHNZQTXPtEXLw+aM9E+/HE= +github.com/gokrazy/serial-busybox v0.0.0-20250119153030-ac58ba7574e7/go.mod h1:OYcG5tSb+QrelmUOO4EZVUFcIHyyZb0QDbEbZFUp1TA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -543,8 +564,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= -github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= +github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= +github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M= github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -556,6 +577,7 @@ github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:E github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4= @@ -574,6 +596,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= +github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A= github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -704,6 +728,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= @@ -727,6 +753,8 @@ github.com/karamaru-alpha/copyloopvar v1.0.8 h1:gieLARwuByhEMxRwM3GRS/juJqFbLraf github.com/karamaru-alpha/copyloopvar v1.0.8/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= +github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -750,6 +778,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -812,10 +842,15 @@ github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy5 github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= +github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= +github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= +github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= +github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5 h1:80FAK3TW5lVymfHu3kvB1QvTZvy9Kmx1lx6sT5Ep16s= +github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5/go.mod h1:z0QjVpjpK4jksEkffQwS3+abQ3XFTm1bnimyDzWyUk0= github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= @@ -838,6 +873,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -903,12 +942,12 @@ github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkM github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= +github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -986,6 +1025,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0= +github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 h1:3psQveH4RUiv5yc3p7kRySilf1nSXLQhAvJFwg4fgnE= +github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46/go.mod h1:Ng1F/s+z0zCMsbEFEneh+30LJa9DrTfmA+REbEqcTPk= github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -1027,8 +1069,8 @@ github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+W github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU= @@ -1043,8 +1085,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -1092,6 +1134,8 @@ github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8 github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns= github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ= +github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e h1:tyUUgeRPGHjCZWycRnhdx8Lx9DRkjl3WsVUxYMrVBOw= +github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM= github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= @@ -1151,9 +1195,12 @@ github.com/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZ github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= -github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= -github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= +github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= +github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vishvananda/netlink v1.3.1-0.20240922070040-084abd93d350 h1:w5OI+kArIBVksl8UGn6ARQshtPCQvDsbuA9NQie3GIg= +github.com/vishvananda/netlink v1.3.1-0.20240922070040-084abd93d350/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -1263,12 +1310,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 h1:EBHQuS9qI8xJ96+YRgVV2ahFLUYbWpt1rf3wPfXN2wQ= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1365,8 +1415,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1409,6 +1459,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1445,6 +1496,7 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= @@ -1674,8 +1726,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= +gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA= +gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q= helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.toolchain.branch b/go.toolchain.branch index a2bebbeb7858e..6022b95593bbe 100644 --- a/go.toolchain.branch +++ b/go.toolchain.branch @@ -1 +1 @@ -tailscale.go1.25 +tailscale.go1.26 diff --git a/go.toolchain.next.branch b/go.toolchain.next.branch new file mode 100644 index 0000000000000..6022b95593bbe --- /dev/null +++ b/go.toolchain.next.branch @@ -0,0 +1 @@ +tailscale.go1.26 diff --git a/go.toolchain.next.rev b/go.toolchain.next.rev new file mode 100644 index 0000000000000..205355c4745f6 --- /dev/null +++ b/go.toolchain.next.rev @@ -0,0 +1 @@ +f4de14a515221e27c0d79446b423849a6546e3a6 diff --git a/go.toolchain.rev b/go.toolchain.rev index 16058a407c704..205355c4745f6 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -0bab982699fa5903259ba9b4cba3e5fd6cb3baf2 +f4de14a515221e27c0d79446b423849a6546e3a6 diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index 310dcf87fcf1c..86b2083ff8624 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-fBezkBGRHCnfJiOUmMMqBCPCqjlGC4F6KEt5h1JhsCg= +sha256-qmX68/Ml/jvf+sD9qykdx9QhSbkYaF8xJMFtd3iLHI8= diff --git a/go.toolchain.version b/go.toolchain.version index b45fe310644f7..dd43a143f0217 100644 --- a/go.toolchain.version +++ b/go.toolchain.version @@ -1 +1 @@ -1.25.5 +1.26.1 diff --git a/gokrazy/build.go b/gokrazy/build.go index c1ee1cbeb1974..f92edb1a34abb 100644 --- a/gokrazy/build.go +++ b/gokrazy/build.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This program builds the Tailscale Appliance Gokrazy image. @@ -137,25 +137,24 @@ func buildImage() error { // Build the tsapp.img var buf bytes.Buffer cmd := exec.Command("go", "run", - "github.com/gokrazy/tools/cmd/gok", - "--parent_dir="+dir, - "--instance="+*app, + "github.com/bradfitz/monogok/cmd/monogok", "overwrite", - "--full", *app+".img", + "--full", filepath.Join(dir, *app+".img"), "--target_storage_bytes=1258299392") + cmd.Dir = filepath.Join(dir, *app) cmd.Stdout = io.MultiWriter(os.Stdout, &buf) cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return err } - // gok overwrite emits a line of text saying how to run mkfs.ext4 + // monogok overwrite emits a line of text saying how to run mkfs.ext4 // to create the ext4 /perm filesystem. Parse that and run it. // The regexp is tight to avoid matching if the command changes, // to force us to check it's still correct/safe. But it shouldn't - // change on its own because we pin the gok version in our go.mod. + // change on its own because we pin the monogok version in our go.mod. // - // TODO(bradfitz): emit this in a machine-readable way from gok. + // TODO(bradfitz): emit this in a machine-readable way from monogok. rx := regexp.MustCompile(`(?m)/mkfs.ext4 (-F) (-E) (offset=\d+) (\S+) (\d+)\s*?$`) m := rx.FindStringSubmatch(buf.String()) if m == nil { diff --git a/gokrazy/go.mod b/gokrazy/go.mod deleted file mode 100644 index f7483f41d5d46..0000000000000 --- a/gokrazy/go.mod +++ /dev/null @@ -1,19 +0,0 @@ -module tailscale.com/gokrazy - -go 1.23 - -require github.com/gokrazy/tools v0.0.0-20250128200151-63160424957c - -require ( - github.com/breml/rootcerts v0.2.10 // indirect - github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 // indirect - github.com/gokrazy/internal v0.0.0-20250126213949-423a5b587b57 // indirect - github.com/gokrazy/updater v0.0.0-20230215172637-813ccc7f21e2 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/cobra v1.6.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.28.0 // indirect -) diff --git a/gokrazy/go.sum b/gokrazy/go.sum deleted file mode 100644 index 170d15b3db19c..0000000000000 --- a/gokrazy/go.sum +++ /dev/null @@ -1,33 +0,0 @@ -github.com/breml/rootcerts v0.2.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8= -github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao= -github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= -github.com/gokrazy/internal v0.0.0-20250126213949-423a5b587b57 h1:f5bEvO4we3fbfiBkECrrUgWQ8OH6J3SdB2Dwxid/Yx4= -github.com/gokrazy/internal v0.0.0-20250126213949-423a5b587b57/go.mod h1:SJG1KwuJQXFEoBgryaNCkMbdISyovDgZd0xmXJRZmiw= -github.com/gokrazy/tools v0.0.0-20250128200151-63160424957c h1:iEbS8GrNOn671ze8J/AfrYFEVzf8qMx8aR5K0VxPK2w= -github.com/gokrazy/tools v0.0.0-20250128200151-63160424957c/go.mod h1:f2vZhnaPzy92+Bjpx1iuZHK7VuaJx6SNCWQWmu23HZA= -github.com/gokrazy/updater v0.0.0-20230215172637-813ccc7f21e2 h1:kBY5R1tSf+EYZ+QaSrofLaVJtBqYsVNVBWkdMq3Smcg= -github.com/gokrazy/updater v0.0.0-20230215172637-813ccc7f21e2/go.mod h1:PYOvzGOL4nlBmuxu7IyKQTFLaxr61+WPRNRzVtuYOHw= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gokrazy/gok b/gokrazy/gok deleted file mode 100755 index 13111dab28f6a..0000000000000 --- a/gokrazy/gok +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -# This is a wrapper around gok that sets --parent_dir. - -dir=$(dirname "${BASH_SOURCE[0]}") - -cd $dir -$dir/../tool/go run github.com/gokrazy/tools/cmd/gok --parent_dir="$dir" "$@" diff --git a/gokrazy/monogok b/gokrazy/monogok new file mode 100755 index 0000000000000..2e09a6918fb5a --- /dev/null +++ b/gokrazy/monogok @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# This is a wrapper around monogok that sets the working directory. + +dir=$(dirname "${BASH_SOURCE[0]}") + +cd $dir +$dir/../tool/go run github.com/bradfitz/monogok/cmd/monogok "$@" diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod deleted file mode 100644 index c56dede46ed65..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/josharian/native v1.0.0 // indirect - github.com/mdlayher/packet v1.0.0 // indirect - github.com/mdlayher/socket v0.2.3 // indirect - github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 // indirect - github.com/vishvananda/netlink v1.1.0 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.20.0 // indirect -) diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum deleted file mode 100644 index 3cd002ae782b1..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum +++ /dev/null @@ -1,39 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= -github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= -github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= -github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 h1:3psQveH4RUiv5yc3p7kRySilf1nSXLQhAvJFwg4fgnE= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46/go.mod h1:Ng1F/s+z0zCMsbEFEneh+30LJa9DrTfmA+REbEqcTPk= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.mod deleted file mode 100644 index 33656efeea7d7..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240802144848-676865a4e84f // indirect - github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/kenshaw/evdev v0.1.0 // indirect - github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.20.0 // indirect -) - -replace github.com/gokrazy/gokrazy => github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.sum deleted file mode 100644 index 479eb1cef1ca7..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.sum +++ /dev/null @@ -1,23 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a h1:FKeN678rNpKTpWRdFbAhYL9mWzPu57R5XPXCR3WmXdI= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 h1:XDklMxV0pE5jWiNaoo5TzvWfqdoiRRScmr4ZtDzE4Uw= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= -github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b h1:7tUBfsEEBWfFeHOB7CUfoOamak+Gx/BlirfXyPk1WjI= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b/go.mod h1:bmoJUS6qOA3uKFvF3KVuhf7mU1KQirzQMeHXtPyKEqg= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a h1:7dnA8x14JihQmKbPr++Y5CCN/XSyDmOB6cXUxcIj6VQ= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f h1:ZSAGWpgs+6dK2oIz5OR+HUul3oJbnhFn8YNgcZ3d9SQ= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 h1:2B8/FbIRqmVgRUulQ4iu1EojniufComYe5Yj4BtIn1c= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -golang.org/x/sys v0.0.0-20201005065044-765f4ea38db3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.mod deleted file mode 100644 index d4708bf4628ff..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/natlabapp.arm64 - -go 1.23.1 - -require github.com/gokrazy/kernel.arm64 v0.0.0-20240830035047-cdba87a9eb0e // indirect diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.sum deleted file mode 100644 index 5084da5c5990c..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/gokrazy/kernel.arm64 v0.0.0-20240830035047-cdba87a9eb0e h1:D9QYleJ7CI4p7gpgUT1mPgAlWMi5au6yOiE8/qC5PhE= -github.com/gokrazy/kernel.arm64 v0.0.0-20240830035047-cdba87a9eb0e/go.mod h1:WWx72LXHEesuJxbopusRfSoKJQ6ffdwkT0DZditdrLo= diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.mod deleted file mode 100644 index de52e181b9c3c..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca // indirect diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.sum deleted file mode 100644 index 8135f60c3e791..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904 h1:eqfH4A/LLgxv5RvqEXwVoFvfmpRa8+TokRjB5g6xBkk= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904/go.mod h1:pq6rGHqxMRPSaTXaCMzIZy0wLDusAJyoVNyNo05RLs0= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9 h1:x5jR/nNo4/kMSoNo/nwa2xbL7PN1an8S3oIn4OZJdec= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca h1:x0eSjuFy8qsRctVHeWm3EC474q3xm4h3OOOrYpcqyyA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca/go.mod h1:OYcG5tSb+QrelmUOO4EZVUFcIHyyZb0QDbEbZFUp1TA= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= -github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4 h1:c1Sgqkh8v6ZxafNGG64r8C8UisIW2TKMJN8P86tKjr0= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.mod deleted file mode 100644 index ec4d9c64fc93e..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e // indirect diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.sum deleted file mode 100644 index d32d5460bf29c..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2 h1:xzf+cMvBJBcA/Av7OTWBa0Tjrbfcy00TeatJeJt6zrY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e h1:tyUUgeRPGHjCZWycRnhdx8Lx9DRkjl3WsVUxYMrVBOw= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= diff --git a/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.mod b/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.mod deleted file mode 100644 index da21a143975e9..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gokrazy/build/tsapp - -go 1.23.1 - -replace tailscale.com => ../../../.. - -require tailscale.com v0.0.0-00010101000000-000000000000 // indirect diff --git a/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.sum b/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.sum deleted file mode 100644 index ae814f31698f4..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.sum +++ /dev/null @@ -1,266 +0,0 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= -github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= -github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= -github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= -github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= -github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= -github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= -github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= -github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= -github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= -github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= -github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= -github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= -github.com/illarion/gonotify/v2 v2.0.2 h1:oDH5yvxq9oiQGWUeut42uShcWzOy/hsT9E7pvO95+kQ= -github.com/illarion/gonotify/v2 v2.0.2/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= -github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= -github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= -github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= -github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= -github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= -github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= -github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= -github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1 h1:ycpNCSYwzZ7x4G4ioPNtKQmIY0G/3o4pVf8wCZq6blY= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= -github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= -go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07 h1:Z+Zg+aXJYq6f4TK2E4H+vZkQ4dJAWnInXDR6hM9znxo= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab h1:BMkEEWYOjkvOX7+YKOGbp6jCyQ5pR2j0Ah47p1Vdsx4= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= -k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= -nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= -software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod b/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod deleted file mode 100644 index c56dede46ed65..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/josharian/native v1.0.0 // indirect - github.com/mdlayher/packet v1.0.0 // indirect - github.com/mdlayher/socket v0.2.3 // indirect - github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 // indirect - github.com/vishvananda/netlink v1.1.0 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.20.0 // indirect -) diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum b/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum deleted file mode 100644 index 3cd002ae782b1..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum +++ /dev/null @@ -1,39 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= -github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= -github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= -github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 h1:3psQveH4RUiv5yc3p7kRySilf1nSXLQhAvJFwg4fgnE= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46/go.mod h1:Ng1F/s+z0zCMsbEFEneh+30LJa9DrTfmA+REbEqcTPk= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.mod b/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.mod deleted file mode 100644 index 33656efeea7d7..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240802144848-676865a4e84f // indirect - github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/kenshaw/evdev v0.1.0 // indirect - github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.20.0 // indirect -) - -replace github.com/gokrazy/gokrazy => github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.sum b/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.sum deleted file mode 100644 index 479eb1cef1ca7..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.sum +++ /dev/null @@ -1,23 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a h1:FKeN678rNpKTpWRdFbAhYL9mWzPu57R5XPXCR3WmXdI= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 h1:XDklMxV0pE5jWiNaoo5TzvWfqdoiRRScmr4ZtDzE4Uw= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= -github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b h1:7tUBfsEEBWfFeHOB7CUfoOamak+Gx/BlirfXyPk1WjI= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b/go.mod h1:bmoJUS6qOA3uKFvF3KVuhf7mU1KQirzQMeHXtPyKEqg= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a h1:7dnA8x14JihQmKbPr++Y5CCN/XSyDmOB6cXUxcIj6VQ= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f h1:ZSAGWpgs+6dK2oIz5OR+HUul3oJbnhFn8YNgcZ3d9SQ= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 h1:2B8/FbIRqmVgRUulQ4iu1EojniufComYe5Yj4BtIn1c= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -golang.org/x/sys v0.0.0-20201005065044-765f4ea38db3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.mod b/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.mod deleted file mode 100644 index de52e181b9c3c..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca // indirect diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.sum b/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.sum deleted file mode 100644 index 8135f60c3e791..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904 h1:eqfH4A/LLgxv5RvqEXwVoFvfmpRa8+TokRjB5g6xBkk= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904/go.mod h1:pq6rGHqxMRPSaTXaCMzIZy0wLDusAJyoVNyNo05RLs0= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9 h1:x5jR/nNo4/kMSoNo/nwa2xbL7PN1an8S3oIn4OZJdec= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca h1:x0eSjuFy8qsRctVHeWm3EC474q3xm4h3OOOrYpcqyyA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca/go.mod h1:OYcG5tSb+QrelmUOO4EZVUFcIHyyZb0QDbEbZFUp1TA= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= -github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4 h1:c1Sgqkh8v6ZxafNGG64r8C8UisIW2TKMJN8P86tKjr0= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod b/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod deleted file mode 100644 index ec4d9c64fc93e..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e // indirect diff --git a/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum b/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum deleted file mode 100644 index d32d5460bf29c..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2 h1:xzf+cMvBJBcA/Av7OTWBa0Tjrbfcy00TeatJeJt6zrY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e h1:tyUUgeRPGHjCZWycRnhdx8Lx9DRkjl3WsVUxYMrVBOw= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= diff --git a/gokrazy/natlabapp/builddir/tailscale.com/go.mod b/gokrazy/natlabapp/builddir/tailscale.com/go.mod deleted file mode 100644 index 53bc11f9bd3f8..0000000000000 --- a/gokrazy/natlabapp/builddir/tailscale.com/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gokrazy/build/tsapp - -go 1.25.5 - -replace tailscale.com => ../../../.. - -require tailscale.com v0.0.0-00010101000000-000000000000 // indirect diff --git a/gokrazy/natlabapp/builddir/tailscale.com/go.sum b/gokrazy/natlabapp/builddir/tailscale.com/go.sum deleted file mode 100644 index 25f15059d3af6..0000000000000 --- a/gokrazy/natlabapp/builddir/tailscale.com/go.sum +++ /dev/null @@ -1,268 +0,0 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= -github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= -github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= -github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= -github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= -github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= -github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= -github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= -github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= -github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= -github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= -github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= -github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= -github.com/illarion/gonotify/v2 v2.0.2 h1:oDH5yvxq9oiQGWUeut42uShcWzOy/hsT9E7pvO95+kQ= -github.com/illarion/gonotify/v2 v2.0.2/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= -github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= -github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= -github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= -github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= -github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= -github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= -github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= -github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1 h1:ycpNCSYwzZ7x4G4ioPNtKQmIY0G/3o4pVf8wCZq6blY= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc h1:cezaQN9pvKVaw56Ma5qr/G646uKIYP0yQf+OyWN/okc= -github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= -github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= -go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07 h1:Z+Zg+aXJYq6f4TK2E4H+vZkQ4dJAWnInXDR6hM9znxo= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab h1:BMkEEWYOjkvOX7+YKOGbp6jCyQ5pR2j0Ah47p1Vdsx4= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= -k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= -nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= -software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/gokrazy/natlabapp/gokrazydeps.go b/gokrazy/natlabapp/gokrazydeps.go new file mode 100644 index 0000000000000..c5d2b32a3d543 --- /dev/null +++ b/gokrazy/natlabapp/gokrazydeps.go @@ -0,0 +1,16 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build for_go_mod_tidy_only + +package gokrazydeps + +import ( + _ "github.com/gokrazy/gokrazy" + _ "github.com/gokrazy/gokrazy/cmd/dhcp" + _ "github.com/gokrazy/serial-busybox" + _ "github.com/tailscale/gokrazy-kernel" + _ "tailscale.com/cmd/tailscale" + _ "tailscale.com/cmd/tailscaled" + _ "tailscale.com/cmd/tta" +) diff --git a/gokrazy/tidy-deps.go b/gokrazy/tidy-deps.go index 104156e473642..3b9dbea7c73af 100644 --- a/gokrazy/tidy-deps.go +++ b/gokrazy/tidy-deps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build for_go_mod_tidy @@ -6,5 +6,5 @@ package gokrazy import ( - _ "github.com/gokrazy/tools/cmd/gok" + _ "github.com/bradfitz/monogok/cmd/monogok" ) diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.mod deleted file mode 100644 index fc809b8f7c130..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.mod +++ /dev/null @@ -1,19 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/creack/pty v1.1.18 // indirect - github.com/gokrazy/breakglass v0.0.0-20240604170121-09eeab3321d6 // indirect - github.com/gokrazy/gokrazy v0.0.0-20230812092215-346db1998f83 // indirect - github.com/gokrazy/internal v0.0.0-20230211171410-9608422911d0 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/kenshaw/evdev v0.1.0 // indirect - github.com/kr/fs v0.1.0 // indirect - github.com/kr/pty v1.1.8 // indirect - github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5 // indirect - github.com/pkg/sftp v1.13.5 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect -) diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.sum deleted file mode 100644 index 99e0622742caf..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.sum +++ /dev/null @@ -1,46 +0,0 @@ -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gokrazy/breakglass v0.0.0-20240529175905-44b3fe64f19c h1:cWzgXJIluB6jAQ0HcnvA1yExLawmtDSssk9H4fLv3yM= -github.com/gokrazy/breakglass v0.0.0-20240529175905-44b3fe64f19c/go.mod h1:4Yffo2Z5w3q2eDvo3HDR8eDnmkDpMAkX0Tn7b/9upgs= -github.com/gokrazy/breakglass v0.0.0-20240604170121-09eeab3321d6 h1:38JB1lVPx+ihCzlWZdbH1LoNmu0KR+jRSmNFR7aMVTg= -github.com/gokrazy/breakglass v0.0.0-20240604170121-09eeab3321d6/go.mod h1:4Yffo2Z5w3q2eDvo3HDR8eDnmkDpMAkX0Tn7b/9upgs= -github.com/gokrazy/gokrazy v0.0.0-20230812092215-346db1998f83 h1:Y4sADvUYd/c0eqnqebipHHl0GMpAxOQeTzPnwI4ievM= -github.com/gokrazy/gokrazy v0.0.0-20230812092215-346db1998f83/go.mod h1:9q5Tg+q+YvRjC3VG0gfMFut46dhbhtAnvUEp4lPjc6c= -github.com/gokrazy/internal v0.0.0-20230211171410-9608422911d0 h1:QTi0skQ/OM7he/5jEWA9k/DYgdwGAhw3hrUoiPGGZHM= -github.com/gokrazy/internal v0.0.0-20230211171410-9608422911d0/go.mod h1:ddHcxXZ/VVQOSAWcRBbkYY58+QOw4L145ye6phyDmRA= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= -github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5 h1:80FAK3TW5lVymfHu3kvB1QvTZvy9Kmx1lx6sT5Ep16s= -github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5/go.mod h1:z0QjVpjpK4jksEkffQwS3+abQ3XFTm1bnimyDzWyUk0= -github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go= -github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tailscale/breakglass v0.0.0-20240529174846-0d8ebfc2c652 h1:36TB+ZuYaA8OTdMoPnygC9CJuQmTWxMEmn+a+9XTOgk= -github.com/tailscale/breakglass v0.0.0-20240529174846-0d8ebfc2c652/go.mod h1:4Yffo2Z5w3q2eDvo3HDR8eDnmkDpMAkX0Tn7b/9upgs= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod deleted file mode 100644 index c56dede46ed65..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/josharian/native v1.0.0 // indirect - github.com/mdlayher/packet v1.0.0 // indirect - github.com/mdlayher/socket v0.2.3 // indirect - github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 // indirect - github.com/vishvananda/netlink v1.1.0 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.20.0 // indirect -) diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum deleted file mode 100644 index 3cd002ae782b1..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum +++ /dev/null @@ -1,39 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= -github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= -github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= -github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 h1:3psQveH4RUiv5yc3p7kRySilf1nSXLQhAvJFwg4fgnE= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46/go.mod h1:Ng1F/s+z0zCMsbEFEneh+30LJa9DrTfmA+REbEqcTPk= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.mod deleted file mode 100644 index d851081bbc660..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 // indirect diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.sum deleted file mode 100644 index d3dc288edf218..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw= -github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.mod deleted file mode 100644 index 33656efeea7d7..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240802144848-676865a4e84f // indirect - github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/kenshaw/evdev v0.1.0 // indirect - github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.20.0 // indirect -) - -replace github.com/gokrazy/gokrazy => github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.sum deleted file mode 100644 index 479eb1cef1ca7..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.sum +++ /dev/null @@ -1,23 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a h1:FKeN678rNpKTpWRdFbAhYL9mWzPu57R5XPXCR3WmXdI= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 h1:XDklMxV0pE5jWiNaoo5TzvWfqdoiRRScmr4ZtDzE4Uw= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= -github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b h1:7tUBfsEEBWfFeHOB7CUfoOamak+Gx/BlirfXyPk1WjI= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b/go.mod h1:bmoJUS6qOA3uKFvF3KVuhf7mU1KQirzQMeHXtPyKEqg= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a h1:7dnA8x14JihQmKbPr++Y5CCN/XSyDmOB6cXUxcIj6VQ= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f h1:ZSAGWpgs+6dK2oIz5OR+HUul3oJbnhFn8YNgcZ3d9SQ= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 h1:2B8/FbIRqmVgRUulQ4iu1EojniufComYe5Yj4BtIn1c= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -golang.org/x/sys v0.0.0-20201005065044-765f4ea38db3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.mod deleted file mode 100644 index 613104a7f6469..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/rpi-eeprom v0.0.0-20240518032756-37da22ee9608 // indirect diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.sum deleted file mode 100644 index b037c105633fb..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.sum +++ /dev/null @@ -1,3 +0,0 @@ -github.com/gokrazy/rpi-eeprom v0.0.0-20240518032756-37da22ee9608 h1:8uderKR+8eXR0nRcyBugql1YPoJQjpjoltHqX9yl2DI= -github.com/gokrazy/rpi-eeprom v0.0.0-20240518032756-37da22ee9608/go.mod h1:vabxV1M+i6S3rGuWoFieHxCJW3jlob3rqe0KV82j+0o= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.mod deleted file mode 100644 index de52e181b9c3c..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca // indirect diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.sum deleted file mode 100644 index 8135f60c3e791..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904 h1:eqfH4A/LLgxv5RvqEXwVoFvfmpRa8+TokRjB5g6xBkk= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904/go.mod h1:pq6rGHqxMRPSaTXaCMzIZy0wLDusAJyoVNyNo05RLs0= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9 h1:x5jR/nNo4/kMSoNo/nwa2xbL7PN1an8S3oIn4OZJdec= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca h1:x0eSjuFy8qsRctVHeWm3EC474q3xm4h3OOOrYpcqyyA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca/go.mod h1:OYcG5tSb+QrelmUOO4EZVUFcIHyyZb0QDbEbZFUp1TA= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= -github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4 h1:c1Sgqkh8v6ZxafNGG64r8C8UisIW2TKMJN8P86tKjr0= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod b/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod deleted file mode 100644 index ec4d9c64fc93e..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e // indirect diff --git a/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum b/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum deleted file mode 100644 index d32d5460bf29c..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2 h1:xzf+cMvBJBcA/Av7OTWBa0Tjrbfcy00TeatJeJt6zrY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e h1:tyUUgeRPGHjCZWycRnhdx8Lx9DRkjl3WsVUxYMrVBOw= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= diff --git a/gokrazy/tsapp/builddir/tailscale.com/go.mod b/gokrazy/tsapp/builddir/tailscale.com/go.mod deleted file mode 100644 index 53bc11f9bd3f8..0000000000000 --- a/gokrazy/tsapp/builddir/tailscale.com/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gokrazy/build/tsapp - -go 1.25.5 - -replace tailscale.com => ../../../.. - -require tailscale.com v0.0.0-00010101000000-000000000000 // indirect diff --git a/gokrazy/tsapp/builddir/tailscale.com/go.sum b/gokrazy/tsapp/builddir/tailscale.com/go.sum deleted file mode 100644 index 2ffef7bf7ba22..0000000000000 --- a/gokrazy/tsapp/builddir/tailscale.com/go.sum +++ /dev/null @@ -1,262 +0,0 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= -github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= -github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= -github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= -github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= -github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= -github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= -github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= -github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= -github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= -github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= -github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= -github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= -github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= -github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= -github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= -github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= -github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= -github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= -github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1 h1:ycpNCSYwzZ7x4G4ioPNtKQmIY0G/3o4pVf8wCZq6blY= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= -github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= -go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07 h1:Z+Zg+aXJYq6f4TK2E4H+vZkQ4dJAWnInXDR6hM9znxo= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab h1:BMkEEWYOjkvOX7+YKOGbp6jCyQ5pR2j0Ah47p1Vdsx4= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= -k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= -nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= -software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/gokrazy/tsapp/gokrazydeps.go b/gokrazy/tsapp/gokrazydeps.go new file mode 100644 index 0000000000000..931080647f8e5 --- /dev/null +++ b/gokrazy/tsapp/gokrazydeps.go @@ -0,0 +1,18 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build for_go_mod_tidy_only + +package gokrazydeps + +import ( + _ "github.com/gokrazy/breakglass" + _ "github.com/gokrazy/gokrazy" + _ "github.com/gokrazy/gokrazy/cmd/dhcp" + _ "github.com/gokrazy/gokrazy/cmd/ntp" + _ "github.com/gokrazy/gokrazy/cmd/randomd" + _ "github.com/gokrazy/serial-busybox" + _ "github.com/tailscale/gokrazy-kernel" + _ "tailscale.com/cmd/tailscale" + _ "tailscale.com/cmd/tailscaled" +) diff --git a/gomod_test.go b/gomod_test.go index f984b5d6f27a5..a9a3c437d9341 100644 --- a/gomod_test.go +++ b/gomod_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscaleroot diff --git a/header.txt b/header.txt index 8111cb74e25c7..5a058566ba4b8 100644 --- a/header.txt +++ b/header.txt @@ -1,2 +1,2 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause \ No newline at end of file diff --git a/health/args.go b/health/args.go index 01a75aa2d79f3..e89f7676f0b64 100644 --- a/health/args.go +++ b/health/args.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package health diff --git a/health/health.go b/health/health.go index f0f6a6ffbb162..0cfe570c4296a 100644 --- a/health/health.go +++ b/health/health.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package health is a registry for other packages to report & check diff --git a/health/health_test.go b/health/health_test.go index af7d06c8fe258..953c4dca26ea3 100644 --- a/health/health_test.go +++ b/health/health_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package health diff --git a/health/healthmsg/healthmsg.go b/health/healthmsg/healthmsg.go index 5ea1c736d8851..c6efb0d574db4 100644 --- a/health/healthmsg/healthmsg.go +++ b/health/healthmsg/healthmsg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package healthmsg contains some constants for health messages. @@ -11,7 +11,5 @@ const ( WarnAcceptRoutesOff = "Some peers are advertising routes but --accept-routes is false" TailscaleSSHOnBut = "Tailscale SSH enabled, but " // + ... something from caller LockedOut = "this node is locked out; it will not have connectivity until it is signed. For more info, see https://tailscale.com/s/locked-out" - WarnExitNodeUsage = "The following issues on your machine will likely make usage of exit nodes impossible" - DisableRPFilter = "Please set rp_filter=2 instead of rp_filter=1; see https://github.com/tailscale/tailscale/issues/3310" InMemoryTailnetLockState = "Tailnet Lock state is only being stored in-memory. Set --statedir to store state on disk, which is more secure. See https://tailscale.com/kb/1226/tailnet-lock#tailnet-lock-state" ) diff --git a/health/state.go b/health/state.go index e6d937b6a8f02..61d36797ceaf0 100644 --- a/health/state.go +++ b/health/state.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package health @@ -11,6 +11,7 @@ import ( "tailscale.com/feature/buildfeatures" "tailscale.com/tailcfg" + "tailscale.com/util/mak" ) // State contains the health status of the backend, and is @@ -128,11 +129,7 @@ func (t *Tracker) CurrentState() *State { t.mu.Lock() defer t.mu.Unlock() - if t.warnableVal == nil || len(t.warnableVal) == 0 { - return &State{} - } - - wm := map[WarnableCode]UnhealthyState{} + var wm map[WarnableCode]UnhealthyState for w, ws := range t.warnableVal { if !w.IsVisible(ws, t.now) { @@ -145,7 +142,7 @@ func (t *Tracker) CurrentState() *State { continue } state := w.unhealthyState(ws) - wm[w.Code] = state.withETag() + mak.Set(&wm, w.Code, state.withETag()) } for id, msg := range t.lastNotifiedControlMessages { @@ -165,7 +162,7 @@ func (t *Tracker) CurrentState() *State { } } - wm[state.WarnableCode] = state.withETag() + mak.Set(&wm, state.WarnableCode, state.withETag()) } return &State{ diff --git a/health/usermetrics.go b/health/usermetrics.go index 110c57b57971c..b1af0c5852010 100644 --- a/health/usermetrics.go +++ b/health/usermetrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_health && !ts_omit_usermetrics diff --git a/health/usermetrics_omit.go b/health/usermetrics_omit.go index 9d5e35b861681..8a37d8ad8a311 100644 --- a/health/usermetrics_omit.go +++ b/health/usermetrics_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_health || ts_omit_usermetrics diff --git a/health/warnings.go b/health/warnings.go index a9c4b34a0f849..fc9099af2ecc7 100644 --- a/health/warnings.go +++ b/health/warnings.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package health diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go index 3e8f2f994791e..f91f52ec0c3d8 100644 --- a/hostinfo/hostinfo.go +++ b/hostinfo/hostinfo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package hostinfo answers questions about the host environment that Tailscale is diff --git a/hostinfo/hostinfo_container_linux_test.go b/hostinfo/hostinfo_container_linux_test.go index 594a5f5120a6a..0c14776e712b7 100644 --- a/hostinfo/hostinfo_container_linux_test.go +++ b/hostinfo/hostinfo_container_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && ts_package_container diff --git a/hostinfo/hostinfo_darwin.go b/hostinfo/hostinfo_darwin.go index 0b1774e7712d7..cd551ca425790 100644 --- a/hostinfo/hostinfo_darwin.go +++ b/hostinfo/hostinfo_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin @@ -8,14 +8,31 @@ package hostinfo import ( "os" "path/filepath" + + "golang.org/x/sys/unix" + "tailscale.com/types/ptr" ) func init() { + osVersion = lazyOSVersion.Get packageType = packageTypeDarwin } +var ( + lazyOSVersion = &lazyAtomicValue[string]{f: ptr.To(osVersionDarwin)} +) + func packageTypeDarwin() string { // Using tailscaled or IPNExtension? exe, _ := os.Executable() return filepath.Base(exe) } + +// Returns the marketing version (e.g., "15.0.1" or "26.0.0") +func osVersionDarwin() string { + version, err := unix.Sysctl("kern.osproductversion") + if err != nil { + return "" + } + return version +} diff --git a/hostinfo/hostinfo_freebsd.go b/hostinfo/hostinfo_freebsd.go index 3661b13229ac5..3a214ed2463cb 100644 --- a/hostinfo/hostinfo_freebsd.go +++ b/hostinfo/hostinfo_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build freebsd diff --git a/hostinfo/hostinfo_linux.go b/hostinfo/hostinfo_linux.go index 66484a3588027..bb9a5c58c1bb0 100644 --- a/hostinfo/hostinfo_linux.go +++ b/hostinfo/hostinfo_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/hostinfo/hostinfo_linux_test.go b/hostinfo/hostinfo_linux_test.go index 0286fadf329ab..ebf285e94baeb 100644 --- a/hostinfo/hostinfo_linux_test.go +++ b/hostinfo/hostinfo_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_package_container diff --git a/hostinfo/hostinfo_plan9.go b/hostinfo/hostinfo_plan9.go index f9aa30e51769f..27a2543b38ff0 100644 --- a/hostinfo/hostinfo_plan9.go +++ b/hostinfo/hostinfo_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package hostinfo diff --git a/hostinfo/hostinfo_test.go b/hostinfo/hostinfo_test.go index 15b6971b6ccd0..6508d5d995e3b 100644 --- a/hostinfo/hostinfo_test.go +++ b/hostinfo/hostinfo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package hostinfo diff --git a/hostinfo/hostinfo_uname.go b/hostinfo/hostinfo_uname.go index 32b733a03bcb3..b358c0e2cb108 100644 --- a/hostinfo/hostinfo_uname.go +++ b/hostinfo/hostinfo_uname.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || freebsd || openbsd || darwin diff --git a/hostinfo/hostinfo_windows.go b/hostinfo/hostinfo_windows.go index f0422f5a001c5..5e0b340919e34 100644 --- a/hostinfo/hostinfo_windows.go +++ b/hostinfo/hostinfo_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package hostinfo diff --git a/hostinfo/packagetype_container.go b/hostinfo/packagetype_container.go index 9bd14493cb34f..8db7df8616e7a 100644 --- a/hostinfo/packagetype_container.go +++ b/hostinfo/packagetype_container.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && ts_package_container diff --git a/internal/client/tailscale/awsparamstore.go b/internal/client/tailscale/awsparamstore.go new file mode 100644 index 0000000000000..bb0a31d45cc8a --- /dev/null +++ b/internal/client/tailscale/awsparamstore.go @@ -0,0 +1,21 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package tailscale + +import ( + "context" + + "tailscale.com/feature" +) + +// ResolvePrefixAWSParameterStore is the string prefix for values that can be +// resolved from AWS Parameter Store. +const ResolvePrefixAWSParameterStore = "arn:aws:ssm:" + +// HookResolveValueFromParameterStore resolves to [awsparamstore.ResolveValue] when +// the corresponding feature tag is enabled in the build process. +// +// It fetches a value from AWS Parameter Store given an ARN. If the provided +// value is not an Parameter Store ARN, it returns the value unchanged. +var HookResolveValueFromParameterStore feature.Hook[func(ctx context.Context, valueOrARN string) (string, error)] diff --git a/internal/client/tailscale/identityfederation.go b/internal/client/tailscale/identityfederation.go index 3bb64b270a017..8c60c1c3c59a5 100644 --- a/internal/client/tailscale/identityfederation.go +++ b/internal/client/tailscale/identityfederation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/internal/client/tailscale/oauthkeys.go b/internal/client/tailscale/oauthkeys.go index 21102ce0b5fc8..43d5b0744fefe 100644 --- a/internal/client/tailscale/oauthkeys.go +++ b/internal/client/tailscale/oauthkeys.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/internal/client/tailscale/tailscale.go b/internal/client/tailscale/tailscale.go index 0e603bf792562..1e6b576fb0cc1 100644 --- a/internal/client/tailscale/tailscale.go +++ b/internal/client/tailscale/tailscale.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tailscale provides a minimal control plane API client for internal diff --git a/internal/client/tailscale/vip_service.go b/internal/client/tailscale/vip_service.go index 48c59ce4569da..5c35a0c299621 100644 --- a/internal/client/tailscale/vip_service.go +++ b/internal/client/tailscale/vip_service.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/internal/tooldeps/tooldeps.go b/internal/tooldeps/tooldeps.go index 22940c54d8c33..4ea1cf5084a55 100644 --- a/internal/tooldeps/tooldeps.go +++ b/internal/tooldeps/tooldeps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build for_go_mod_tidy_only diff --git a/ipn/auditlog/auditlog.go b/ipn/auditlog/auditlog.go index 0460bc4e2c655..cc6b43cbdba08 100644 --- a/ipn/auditlog/auditlog.go +++ b/ipn/auditlog/auditlog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package auditlog provides a mechanism for logging audit events. diff --git a/ipn/auditlog/auditlog_test.go b/ipn/auditlog/auditlog_test.go index 041cab3546bd0..5e499cc674fcb 100644 --- a/ipn/auditlog/auditlog_test.go +++ b/ipn/auditlog/auditlog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package auditlog diff --git a/ipn/auditlog/extension.go b/ipn/auditlog/extension.go index ae2a296b2c420..293d3742c97f9 100644 --- a/ipn/auditlog/extension.go +++ b/ipn/auditlog/extension.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package auditlog diff --git a/ipn/auditlog/store.go b/ipn/auditlog/store.go index 3b58ffa9318a2..07c9717726147 100644 --- a/ipn/auditlog/store.go +++ b/ipn/auditlog/store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package auditlog diff --git a/ipn/backend.go b/ipn/backend.go index b4ba958c5dd1e..3183c8b5e7a4e 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/backend_test.go b/ipn/backend_test.go index d72b966152ca3..bfb6d6bc41540 100644 --- a/ipn/backend_test.go +++ b/ipn/backend_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/conf.go b/ipn/conf.go index 2c9fb2fd15f9e..ef753a0b48544 100644 --- a/ipn/conf.go +++ b/ipn/conf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/conffile/cloudconf.go b/ipn/conffile/cloudconf.go index 4475a2d7b799e..ac9f640c34275 100644 --- a/ipn/conffile/cloudconf.go +++ b/ipn/conffile/cloudconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package conffile diff --git a/ipn/conffile/conffile.go b/ipn/conffile/conffile.go index 3a2aeffb3a0c6..c221a3d8c8d94 100644 --- a/ipn/conffile/conffile.go +++ b/ipn/conffile/conffile.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package conffile contains code to load, manipulate, and access config file diff --git a/ipn/conffile/conffile_hujson.go b/ipn/conffile/conffile_hujson.go index 1e967f1bdcca2..4f4613dafac4f 100644 --- a/ipn/conffile/conffile_hujson.go +++ b/ipn/conffile/conffile_hujson.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !ts_omit_hujsonconf diff --git a/ipn/conffile/serveconf.go b/ipn/conffile/serveconf.go index bb63c1ac5571a..0d336029246f4 100644 --- a/ipn/conffile/serveconf.go +++ b/ipn/conffile/serveconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/ipn/desktop/doc.go b/ipn/desktop/doc.go index 64a332792a5a4..3aa60619d913a 100644 --- a/ipn/desktop/doc.go +++ b/ipn/desktop/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package desktop facilitates interaction with the desktop environment diff --git a/ipn/desktop/extension.go b/ipn/desktop/extension.go index 0277726714512..3e96a9c4c7929 100644 --- a/ipn/desktop/extension.go +++ b/ipn/desktop/extension.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Both the desktop session manager and multi-user support diff --git a/ipn/desktop/mksyscall.go b/ipn/desktop/mksyscall.go index b7af12366b64e..dd4e2acbf554d 100644 --- a/ipn/desktop/mksyscall.go +++ b/ipn/desktop/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package desktop diff --git a/ipn/desktop/session.go b/ipn/desktop/session.go index c95378914321d..b5656d99bd4f2 100644 --- a/ipn/desktop/session.go +++ b/ipn/desktop/session.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package desktop diff --git a/ipn/desktop/sessions.go b/ipn/desktop/sessions.go index 8bf7a75e2dc3a..e7026c1434f44 100644 --- a/ipn/desktop/sessions.go +++ b/ipn/desktop/sessions.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package desktop diff --git a/ipn/desktop/sessions_notwindows.go b/ipn/desktop/sessions_notwindows.go index da3230a456480..396d57eff39b2 100644 --- a/ipn/desktop/sessions_notwindows.go +++ b/ipn/desktop/sessions_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/ipn/desktop/sessions_windows.go b/ipn/desktop/sessions_windows.go index 83b884228c1f8..6128548a51216 100644 --- a/ipn/desktop/sessions_windows.go +++ b/ipn/desktop/sessions_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package desktop diff --git a/ipn/doc.go b/ipn/doc.go index c98c7e8b3599f..b39310cf51b4b 100644 --- a/ipn/doc.go +++ b/ipn/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:generate go run tailscale.com/cmd/viewer -type=LoginProfile,Prefs,ServeConfig,ServiceConfig,TCPPortHandler,HTTPHandler,WebServerConfig diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go index 4bf78b40b022b..94aebefdfd73d 100644 --- a/ipn/ipn_clone.go +++ b/ipn/ipn_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/ipn/ipn_test.go b/ipn/ipn_test.go index cba70bccd658a..2689faa8e37cd 100644 --- a/ipn/ipn_test.go +++ b/ipn/ipn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go index 4157ec76e61a8..90560cec0e195 100644 --- a/ipn/ipn_view.go +++ b/ipn/ipn_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/ipn/ipnauth/access.go b/ipn/ipnauth/access.go index 74c66392221b2..3d320585e9637 100644 --- a/ipn/ipnauth/access.go +++ b/ipn/ipnauth/access.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/actor.go b/ipn/ipnauth/actor.go index 108bdd341ae6a..0fa4735f9fe69 100644 --- a/ipn/ipnauth/actor.go +++ b/ipn/ipnauth/actor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/actor_windows.go b/ipn/ipnauth/actor_windows.go index 90d3bdd362bbf..0345bc5fdb2fe 100644 --- a/ipn/ipnauth/actor_windows.go +++ b/ipn/ipnauth/actor_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/ipnauth.go b/ipn/ipnauth/ipnauth.go index 497f30f8c198e..d48ec1f140c58 100644 --- a/ipn/ipnauth/ipnauth.go +++ b/ipn/ipnauth/ipnauth.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnauth controls access to the LocalAPI. diff --git a/ipn/ipnauth/ipnauth_omit_unixsocketidentity.go b/ipn/ipnauth/ipnauth_omit_unixsocketidentity.go index defe7d89c409b..fce0143e43c38 100644 --- a/ipn/ipnauth/ipnauth_omit_unixsocketidentity.go +++ b/ipn/ipnauth/ipnauth_omit_unixsocketidentity.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && ts_omit_unixsocketidentity diff --git a/ipn/ipnauth/ipnauth_unix_creds.go b/ipn/ipnauth/ipnauth_unix_creds.go index 89a9ceaa99388..d031d3b6e09c2 100644 --- a/ipn/ipnauth/ipnauth_unix_creds.go +++ b/ipn/ipnauth/ipnauth_unix_creds.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !ts_omit_unixsocketidentity diff --git a/ipn/ipnauth/ipnauth_windows.go b/ipn/ipnauth/ipnauth_windows.go index e3ea448a855e5..b4591ea3a03f8 100644 --- a/ipn/ipnauth/ipnauth_windows.go +++ b/ipn/ipnauth/ipnauth_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/policy.go b/ipn/ipnauth/policy.go index eeee324352387..692005e5516d0 100644 --- a/ipn/ipnauth/policy.go +++ b/ipn/ipnauth/policy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/self.go b/ipn/ipnauth/self.go index adee0696458d6..0379aede4076c 100644 --- a/ipn/ipnauth/self.go +++ b/ipn/ipnauth/self.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/test_actor.go b/ipn/ipnauth/test_actor.go index 80c5fcc8a6328..667fb84137ca6 100644 --- a/ipn/ipnauth/test_actor.go +++ b/ipn/ipnauth/test_actor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnext/ipnext.go b/ipn/ipnext/ipnext.go index fc93cc8760a0b..6dea49939af91 100644 --- a/ipn/ipnext/ipnext.go +++ b/ipn/ipnext/ipnext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnext defines types and interfaces used for extending the core LocalBackend @@ -21,6 +21,7 @@ import ( "tailscale.com/tstime" "tailscale.com/types/logger" "tailscale.com/types/mapx" + "tailscale.com/wgengine/filter" ) // Extension augments LocalBackend with additional functionality. @@ -377,6 +378,39 @@ type Hooks struct { // ShouldUploadServices reports whether this node should include services // in Hostinfo from the portlist extension. ShouldUploadServices feature.Hook[func() bool] + + // Filter contains hooks for the packet filter. + // See [filter.Filter] for details on how these hooks are invoked. + Filter FilterHooks +} + +// FilterHooks contains hooks that extensions can use to customize the packet +// filter. Field names match the corresponding fields in filter.Filter. +type FilterHooks struct { + // IngressAllowHooks are hooks that allow extensions to accept inbound + // packets beyond the standard filter rules. Packets that are not dropped + // by the direction-agnostic pre-check, but would be not accepted by the + // main filter rules, including the check for destinations in the node's + // local IP set, will be accepted if they match one of these hooks. + // As of 2026-02-24, the ingress filter does not implement explicit drop + // rules, but if it does, an explicitly dropped packet will be dropped, + // and these hooks will not be evaluated. + // + // Processing of hooks stop after the first one that returns true. + // The returned why string of the first match is used in logging. + // Returning false does not drop the packet. + // See also [filter.Filter.IngressAllowHooks]. + IngressAllowHooks feature.Hooks[filter.PacketMatch] + + // LinkLocalAllowHooks are hooks that provide exceptions to the default + // policy of dropping link-local unicast packets. They run inside the + // direction-agnostic pre-checks for both ingress and egress. + // + // A hook can allow a link-local packet to pass the link-local check, + // but the packet is still subject to all other filter rules, and could be + // dropped elsewhere. Matching link-local packets are not logged. + // See also [filter.Filter.LinkLocalAllowHooks]. + LinkLocalAllowHooks feature.Hooks[filter.PacketMatch] } // NodeBackend is an interface to query the current node and its peers. diff --git a/ipn/ipnlocal/breaktcp_darwin.go b/ipn/ipnlocal/breaktcp_darwin.go index 13566198ce9fc..732c375f7f5af 100644 --- a/ipn/ipnlocal/breaktcp_darwin.go +++ b/ipn/ipnlocal/breaktcp_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/breaktcp_linux.go b/ipn/ipnlocal/breaktcp_linux.go index b82f6521246f0..0ba9ed6d78f19 100644 --- a/ipn/ipnlocal/breaktcp_linux.go +++ b/ipn/ipnlocal/breaktcp_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/bus.go b/ipn/ipnlocal/bus.go index 910e4e774c958..6061f7223988d 100644 --- a/ipn/ipnlocal/bus.go +++ b/ipn/ipnlocal/bus.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/bus_test.go b/ipn/ipnlocal/bus_test.go index 5c75ac54d688d..27ffebcdd570e 100644 --- a/ipn/ipnlocal/bus_test.go +++ b/ipn/ipnlocal/bus_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go index b5e722b97c4a4..ccce2a65d99e6 100644 --- a/ipn/ipnlocal/c2n.go +++ b/ipn/ipnlocal/c2n.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/c2n_pprof.go b/ipn/ipnlocal/c2n_pprof.go index 13237cc4fad2f..783ba16ff93c5 100644 --- a/ipn/ipnlocal/c2n_pprof.go +++ b/ipn/ipnlocal/c2n_pprof.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !wasm && !ts_omit_debug diff --git a/ipn/ipnlocal/c2n_test.go b/ipn/ipnlocal/c2n_test.go index 86cc6a5490865..810d6765b45e2 100644 --- a/ipn/ipnlocal/c2n_test.go +++ b/ipn/ipnlocal/c2n_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/captiveportal.go b/ipn/ipnlocal/captiveportal.go index 14f8b799eb6dd..ae310c7cc785a 100644 --- a/ipn/ipnlocal/captiveportal.go +++ b/ipn/ipnlocal/captiveportal.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_captiveportal diff --git a/ipn/ipnlocal/cert.go b/ipn/ipnlocal/cert.go index b389c93e7e971..efab9db7aad6e 100644 --- a/ipn/ipnlocal/cert.go +++ b/ipn/ipnlocal/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_acme @@ -37,7 +37,6 @@ import ( "tailscale.com/feature/buildfeatures" "tailscale.com/hostinfo" "tailscale.com/ipn" - "tailscale.com/ipn/ipnstate" "tailscale.com/ipn/store" "tailscale.com/ipn/store/mem" "tailscale.com/net/bakedroots" @@ -106,6 +105,13 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK // // If a cert is expired, or expires sooner than minValidity, it will be renewed // synchronously. Otherwise it will be renewed asynchronously. +// +// The domain must be one of: +// +// - An exact CertDomain (e.g., "node.ts.net") +// - A wildcard domain (e.g., "*.node.ts.net") +// +// The wildcard format requires the NodeAttrDNSSubdomainResolve capability. func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string, minValidity time.Duration) (*TLSCertKeyPair, error) { b.mu.Lock() getCertForTest := b.getCertForTest @@ -119,6 +125,11 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string if !validLookingCertDomain(domain) { return nil, errors.New("invalid domain") } + + certDomain, err := b.resolveCertDomain(domain) + if err != nil { + return nil, err + } logf := logger.WithPrefix(b.logf, fmt.Sprintf("cert(%q): ", domain)) now := b.clock.Now() traceACME := func(v any) { @@ -134,13 +145,13 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string return nil, err } - if pair, err := getCertPEMCached(cs, domain, now); err == nil { + if pair, err := getCertPEMCached(cs, certDomain, now); err == nil { if envknob.IsCertShareReadOnlyMode() { return pair, nil } // If we got here, we have a valid unexpired cert. // Check whether we should start an async renewal. - shouldRenew, err := b.shouldStartDomainRenewal(cs, domain, now, pair, minValidity) + shouldRenew, err := b.shouldStartDomainRenewal(cs, certDomain, now, pair, minValidity) if err != nil { logf("error checking for certificate renewal: %v", err) // Renewal check failed, but the current cert is valid and not @@ -154,7 +165,7 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string logf("starting async renewal") // Start renewal in the background, return current valid cert. b.goTracker.Go(func() { - if _, err := getCertPEM(context.Background(), b, cs, logf, traceACME, domain, now, minValidity); err != nil { + if _, err := getCertPEM(context.Background(), b, cs, logf, traceACME, certDomain, now, minValidity); err != nil { logf("async renewal failed: getCertPem: %v", err) } }) @@ -169,7 +180,7 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string return nil, fmt.Errorf("retrieving cached TLS certificate failed and cert store is configured in read-only mode, not attempting to issue a new certificate: %w", err) } - pair, err := getCertPEM(ctx, b, cs, logf, traceACME, domain, now, minValidity) + pair, err := getCertPEM(ctx, b, cs, logf, traceACME, certDomain, now, minValidity) if err != nil { logf("getCertPEM: %v", err) return nil, err @@ -488,8 +499,12 @@ func (kp TLSCertKeyPair) parseCertificate() (*x509.Certificate, error) { return x509.ParseCertificate(block.Bytes) } -func keyFile(dir, domain string) string { return filepath.Join(dir, domain+".key") } -func certFile(dir, domain string) string { return filepath.Join(dir, domain+".crt") } +func keyFile(dir, domain string) string { + return filepath.Join(dir, strings.Replace(domain, "*.", "wildcard_.", 1)+".key") +} +func certFile(dir, domain string) string { + return filepath.Join(dir, strings.Replace(domain, "*.", "wildcard_.", 1)+".crt") +} // getCertPEMCached returns a non-nil keyPair if a cached keypair for domain // exists on disk in dir that is valid at the provided now time. @@ -506,11 +521,14 @@ func getCertPEMCached(cs certStore, domain string, now time.Time) (p *TLSCertKey } // getCertPem checks if a cert needs to be renewed and if so, renews it. +// domain is the resolved cert domain (e.g., "*.node.ts.net" for wildcards). // It can be overridden in tests. var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf logger.Logf, traceACME func(any), domain string, now time.Time, minValidity time.Duration) (*TLSCertKeyPair, error) { acmeMu.Lock() defer acmeMu.Unlock() + baseDomain, isWildcard := strings.CutPrefix(domain, "*.") + // In case this method was triggered multiple times in parallel (when // serving incoming requests), check whether one of the other goroutines // already renewed the cert before us. @@ -561,12 +579,6 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l return nil, fmt.Errorf("unexpected ACME account status %q", a.Status) } - // Before hitting LetsEncrypt, see if this is a domain that Tailscale will do DNS challenges for. - st := b.StatusWithoutPeers() - if err := checkCertDomain(st, domain); err != nil { - return nil, err - } - // If we have a previous cert, include it in the order. Assuming we're // within the ARI renewal window this should exclude us from LE rate // limits. @@ -580,7 +592,18 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l opts = append(opts, acme.WithOrderReplacesCert(prevCrt)) } } - order, err := ac.AuthorizeOrder(ctx, []acme.AuthzID{{Type: "dns", Value: domain}}, opts...) + + // For wildcards, we need to authorize both the wildcard and base domain. + var authzIDs []acme.AuthzID + if isWildcard { + authzIDs = []acme.AuthzID{ + {Type: "dns", Value: domain}, + {Type: "dns", Value: baseDomain}, + } + } else { + authzIDs = []acme.AuthzID{{Type: "dns", Value: domain}} + } + order, err := ac.AuthorizeOrder(ctx, authzIDs, opts...) if err != nil { return nil, err } @@ -598,7 +621,9 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l if err != nil { return nil, err } - key := "_acme-challenge." + domain + // For wildcards, the challenge is on the base domain. + // e.g., "*.node.ts.net" -> "_acme-challenge.node.ts.net" + key := "_acme-challenge." + strings.TrimPrefix(az.Identifier.Value, "*.") // 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 @@ -608,14 +633,14 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l txts, _ := resolver.LookupTXT(lookupCtx, key) lookupCancel() if slices.Contains(txts, rec) { - logf("TXT record already existed") + logf("TXT record already existed for %s", key) } else { - logf("starting SetDNS call...") + logf("starting SetDNS call for %s...", key) err = b.SetDNS(ctx, key, rec) if err != nil { return nil, fmt.Errorf("SetDNS %q => %q: %w", key, rec, err) } - logf("did SetDNS") + logf("did SetDNS for %s", key) } chal, err := ac.Accept(ctx, ch) @@ -680,11 +705,19 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l return &TLSCertKeyPair{CertPEM: certPEM.Bytes(), KeyPEM: privPEM.Bytes()}, nil } -// certRequest generates a CSR for the given common name cn and optional SANs. -func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) { +// certRequest generates a CSR for the given domain and optional SANs. +func certRequest(key crypto.Signer, domain string, ext []pkix.Extension) ([]byte, error) { + dnsNames := []string{domain} + if base, ok := strings.CutPrefix(domain, "*."); ok { + // Wildcard cert must also include the base domain as a SAN. + // This is load-bearing: getCertPEMCached validates certs using + // the storage key (base domain), which only passes x509 verification + // if the base domain is in DNSNames. + dnsNames = append(dnsNames, base) + } req := &x509.CertificateRequest{ - Subject: pkix.Name{CommonName: name}, - DNSNames: []string{name}, + Subject: pkix.Name{CommonName: domain}, + DNSNames: dnsNames, ExtraExtensions: ext, } return x509.CreateCertificateRequest(rand.Reader, req, key) @@ -844,7 +877,7 @@ func isDefaultDirectoryURL(u string) bool { // we might be able to get a cert for. // // It's a light check primarily for double checking before it's used -// as part of a filesystem path. The actual validation happens in checkCertDomain. +// as part of a filesystem path. The actual validation happens in resolveCertDomain. func validLookingCertDomain(name string) bool { if name == "" || strings.Contains(name, "..") || @@ -852,22 +885,56 @@ func validLookingCertDomain(name string) bool { !strings.Contains(name, ".") { return false } + // Only allow * as a wildcard prefix "*.domain.tld" + if rest, ok := strings.CutPrefix(name, "*."); ok { + if strings.Contains(rest, "*") || !strings.Contains(rest, ".") { + return false + } + } else if strings.Contains(name, "*") { + return false + } return true } -func checkCertDomain(st *ipnstate.Status, domain string) error { +// resolveCertDomain validates a domain and returns the cert domain to use. +// +// - "node.ts.net" -> "node.ts.net" (exact CertDomain match) +// - "*.node.ts.net" -> "*.node.ts.net" (explicit wildcard, requires NodeAttrDNSSubdomainResolve) +// +// Subdomain requests like "app.node.ts.net" are rejected; callers should +// request "*.node.ts.net" explicitly for subdomain coverage. +func (b *LocalBackend) resolveCertDomain(domain string) (string, error) { if domain == "" { - return errors.New("missing domain name") + return "", errors.New("missing domain name") } - for _, d := range st.CertDomains { - if d == domain { - return nil + + // Read the netmap once to get both CertDomains and capabilities atomically. + nm := b.NetMap() + if nm == nil { + return "", errors.New("no netmap available") + } + certDomains := nm.DNS.CertDomains + if len(certDomains) == 0 { + return "", errors.New("your Tailscale account does not support getting TLS certs") + } + + // Wildcard request like "*.node.ts.net". + if base, ok := strings.CutPrefix(domain, "*."); ok { + if !nm.AllCaps.Contains(tailcfg.NodeAttrDNSSubdomainResolve) { + return "", fmt.Errorf("wildcard certificates are not enabled for this node") } + if !slices.Contains(certDomains, base) { + return "", fmt.Errorf("invalid domain %q; wildcard certificates are not enabled for this domain", domain) + } + return domain, nil } - if len(st.CertDomains) == 0 { - return errors.New("your Tailscale account does not support getting TLS certs") + + // Exact CertDomain match. + if slices.Contains(certDomains, domain) { + return domain, nil } - return fmt.Errorf("invalid domain %q; must be one of %q", domain, st.CertDomains) + + return "", fmt.Errorf("invalid domain %q; must be one of %q", domain, certDomains) } // handleC2NTLSCertStatus returns info about the last TLS certificate issued for the diff --git a/ipn/ipnlocal/cert_disabled.go b/ipn/ipnlocal/cert_disabled.go index 17d446c11af39..0caab6bc32b27 100644 --- a/ipn/ipnlocal/cert_disabled.go +++ b/ipn/ipnlocal/cert_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js || ts_omit_acme diff --git a/ipn/ipnlocal/cert_test.go b/ipn/ipnlocal/cert_test.go index e2398f670b5ad..cc9146ae1e055 100644 --- a/ipn/ipnlocal/cert_test.go +++ b/ipn/ipnlocal/cert_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !js @@ -17,17 +17,205 @@ import ( "math/big" "os" "path/filepath" + "slices" "testing" "time" "github.com/google/go-cmp/cmp" "tailscale.com/envknob" "tailscale.com/ipn/store/mem" + "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/util/must" + "tailscale.com/util/set" ) +func TestCertRequest(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("GenerateKey: %v", err) + } + + tests := []struct { + domain string + wantSANs []string + }{ + { + domain: "example.com", + wantSANs: []string{"example.com"}, + }, + { + domain: "*.example.com", + wantSANs: []string{"*.example.com", "example.com"}, + }, + { + domain: "*.foo.bar.com", + wantSANs: []string{"*.foo.bar.com", "foo.bar.com"}, + }, + } + + for _, tt := range tests { + t.Run(tt.domain, func(t *testing.T) { + csrDER, err := certRequest(key, tt.domain, nil) + if err != nil { + t.Fatalf("certRequest: %v", err) + } + csr, err := x509.ParseCertificateRequest(csrDER) + if err != nil { + t.Fatalf("ParseCertificateRequest: %v", err) + } + if csr.Subject.CommonName != tt.domain { + t.Errorf("CommonName = %q, want %q", csr.Subject.CommonName, tt.domain) + } + if !slices.Equal(csr.DNSNames, tt.wantSANs) { + t.Errorf("DNSNames = %v, want %v", csr.DNSNames, tt.wantSANs) + } + }) + } +} + +func TestResolveCertDomain(t *testing.T) { + tests := []struct { + name string + domain string + certDomains []string + hasCap bool + skipNetmap bool + want string + wantErr string + }{ + { + name: "exact_match", + domain: "node.ts.net", + certDomains: []string{"node.ts.net"}, + want: "node.ts.net", + }, + { + name: "exact_match_with_cap", + domain: "node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + want: "node.ts.net", + }, + { + name: "wildcard_with_cap", + domain: "*.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + want: "*.node.ts.net", + }, + { + name: "wildcard_without_cap", + domain: "*.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: false, + wantErr: "wildcard certificates are not enabled for this node", + }, + { + name: "subdomain_with_cap_rejected", + domain: "app.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + wantErr: `invalid domain "app.node.ts.net"; must be one of ["node.ts.net"]`, + }, + { + name: "subdomain_without_cap_rejected", + domain: "app.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: false, + wantErr: `invalid domain "app.node.ts.net"; must be one of ["node.ts.net"]`, + }, + { + name: "multi_level_subdomain_rejected", + domain: "a.b.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + wantErr: `invalid domain "a.b.node.ts.net"; must be one of ["node.ts.net"]`, + }, + { + name: "wildcard_no_matching_parent", + domain: "*.unrelated.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + wantErr: `invalid domain "*.unrelated.ts.net"; wildcard certificates are not enabled for this domain`, + }, + { + name: "subdomain_unrelated_rejected", + domain: "app.unrelated.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + wantErr: `invalid domain "app.unrelated.ts.net"; must be one of ["node.ts.net"]`, + }, + { + name: "no_cert_domains", + domain: "node.ts.net", + certDomains: nil, + wantErr: "your Tailscale account does not support getting TLS certs", + }, + { + name: "wildcard_no_cert_domains", + domain: "*.foo.ts.net", + certDomains: nil, + hasCap: true, + wantErr: "your Tailscale account does not support getting TLS certs", + }, + { + name: "empty_domain", + domain: "", + certDomains: []string{"node.ts.net"}, + wantErr: "missing domain name", + }, + { + name: "nil_netmap", + domain: "node.ts.net", + skipNetmap: true, + wantErr: "no netmap available", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTestLocalBackend(t) + + if !tt.skipNetmap { + // Set up netmap with CertDomains and capability + var allCaps set.Set[tailcfg.NodeCapability] + if tt.hasCap { + allCaps = set.Of(tailcfg.NodeAttrDNSSubdomainResolve) + } + b.mu.Lock() + b.currentNode().SetNetMap(&netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{}).View(), + DNS: tailcfg.DNSConfig{ + CertDomains: tt.certDomains, + }, + AllCaps: allCaps, + }) + b.mu.Unlock() + } + + got, err := b.resolveCertDomain(tt.domain) + if tt.wantErr != "" { + if err == nil { + t.Errorf("resolveCertDomain(%q) = %q, want error %q", tt.domain, got, tt.wantErr) + } else if err.Error() != tt.wantErr { + t.Errorf("resolveCertDomain(%q) error = %q, want %q", tt.domain, err.Error(), tt.wantErr) + } + return + } + if err != nil { + t.Errorf("resolveCertDomain(%q) error = %v, want nil", tt.domain, err) + return + } + if got != tt.want { + t.Errorf("resolveCertDomain(%q) = %q, want %q", tt.domain, got, tt.want) + } + }) + } +} + func TestValidLookingCertDomain(t *testing.T) { tests := []struct { in string @@ -40,6 +228,16 @@ func TestValidLookingCertDomain(t *testing.T) { {"", false}, {"foo\\bar.com", false}, {"foo\x00bar.com", false}, + // Wildcard tests + {"*.foo.com", true}, + {"*.foo.bar.com", true}, + {"*foo.com", false}, // must be *. + {"*.com", false}, // must have domain after *. + {"*.", false}, // must have domain after *. + {"*.*.foo.com", false}, // no nested wildcards + {"foo.*.bar.com", false}, // no wildcard mid-string + {"app.foo.com", true}, // regular subdomain + {"*", false}, // bare asterisk } for _, tt := range tests { if got := validLookingCertDomain(tt.in); got != tt.want { @@ -231,12 +429,19 @@ func TestDebugACMEDirectoryURL(t *testing.T) { func TestGetCertPEMWithValidity(t *testing.T) { const testDomain = "example.com" - b := &LocalBackend{ - store: &mem.Store{}, - varRoot: t.TempDir(), - ctx: context.Background(), - logf: t.Logf, - } + b := newTestLocalBackend(t) + b.varRoot = t.TempDir() + + // Set up netmap with CertDomains so resolveCertDomain works + b.mu.Lock() + b.currentNode().SetNetMap(&netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{}).View(), + DNS: tailcfg.DNSConfig{ + CertDomains: []string{testDomain}, + }, + }) + b.mu.Unlock() + certDir, err := b.certDir() if err != nil { t.Fatalf("certDir error: %v", err) diff --git a/ipn/ipnlocal/diskcache.go b/ipn/ipnlocal/diskcache.go new file mode 100644 index 0000000000000..0b1b7b4487bd1 --- /dev/null +++ b/ipn/ipnlocal/diskcache.go @@ -0,0 +1,56 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package ipnlocal + +import ( + "tailscale.com/feature/buildfeatures" + "tailscale.com/ipn/ipnlocal/netmapcache" + "tailscale.com/types/netmap" +) + +// diskCache is the state netmap caching to disk. +type diskCache struct { + // all fields guarded by LocalBackend.mu + + dir string // active profile cache directory + cache *netmapcache.Cache +} + +func (b *LocalBackend) writeNetmapToDiskLocked(nm *netmap.NetworkMap) error { + if !buildfeatures.HasCacheNetMap || nm == nil || nm.Cached { + return nil + } + b.logf("writing netmap to disk cache") + + dir, err := b.profileMkdirAllLocked(b.pm.CurrentProfile().ID(), "netmap-cache") + if err != nil { + return err + } + if c := b.diskCache; c.cache == nil || c.dir != dir { + b.diskCache.cache = netmapcache.NewCache(netmapcache.FileStore(dir)) + b.diskCache.dir = dir + } + return b.diskCache.cache.Store(b.currentNode().Context(), nm) +} + +func (b *LocalBackend) loadDiskCacheLocked() (om *netmap.NetworkMap, ok bool) { + if !buildfeatures.HasCacheNetMap { + return nil, false + } + dir, err := b.profileMkdirAllLocked(b.pm.CurrentProfile().ID(), "netmap-cache") + if err != nil { + b.logf("profile data directory: %v", err) + return nil, false + } + if c := b.diskCache; c.cache == nil || c.dir != dir { + b.diskCache.cache = netmapcache.NewCache(netmapcache.FileStore(dir)) + b.diskCache.dir = dir + } + nm, err := b.diskCache.cache.Load(b.currentNode().Context()) + if err != nil { + b.logf("load netmap from cache: %v", err) + return nil, false + } + return nm, true +} diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index e23d8a057546f..9d30029ff8659 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal @@ -10,14 +10,17 @@ import ( "reflect" "testing" + "tailscale.com/appc" "tailscale.com/ipn" "tailscale.com/net/dns" "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/types/dnstype" "tailscale.com/types/netmap" + "tailscale.com/types/opt" "tailscale.com/util/cloudenv" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) func ipps(ippStrs ...string) (ipps []netip.Prefix) { @@ -103,6 +106,39 @@ func TestDNSConfigForNetmap(t *testing.T) { }, }, }, + { + name: "subdomain_resolve_capability", + nm: &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "myname.net.", + Addresses: ipps("100.101.101.101"), + }).View(), + AllCaps: set.SetOf([]tailcfg.NodeCapability{tailcfg.NodeAttrDNSSubdomainResolve}), + }, + peers: nodeViews([]*tailcfg.Node{ + { + ID: 1, + Name: "peer-with-cap.net.", + Addresses: ipps("100.102.0.1"), + CapMap: tailcfg.NodeCapMap{tailcfg.NodeAttrDNSSubdomainResolve: nil}, + }, + { + ID: 2, + Name: "peer-without-cap.net.", + Addresses: ipps("100.102.0.2"), + }, + }), + prefs: &ipn.Prefs{}, + want: &dns.Config{ + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: map[dnsname.FQDN][]netip.Addr{ + "myname.net.": ips("100.101.101.101"), + "peer-with-cap.net.": ips("100.102.0.1"), + "peer-without-cap.net.": ips("100.102.0.2"), + }, + SubdomainHosts: set.Of[dnsname.FQDN]("myname.net.", "peer-with-cap.net."), + }, + }, { // An ephemeral node with only an IPv6 address // should get IPv6 records for all its peers, @@ -183,7 +219,8 @@ func TestDNSConfigForNetmap(t *testing.T) { CorpDNS: true, }, want: &dns.Config{ - Hosts: map[dnsname.FQDN][]netip.Addr{}, + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, Routes: map[dnsname.FQDN][]*dnstype.Resolver{ "0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.": nil, "100.100.in-addr.arpa.": nil, @@ -283,7 +320,8 @@ func TestDNSConfigForNetmap(t *testing.T) { CorpDNS: true, }, want: &dns.Config{ - Hosts: map[dnsname.FQDN][]netip.Addr{}, + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, DefaultResolvers: []*dnstype.Resolver{ {Addr: "8.8.8.8"}, }, @@ -306,8 +344,9 @@ func TestDNSConfigForNetmap(t *testing.T) { ExitNodeID: "some-id", }, want: &dns.Config{ - Hosts: map[dnsname.FQDN][]netip.Addr{}, - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, DefaultResolvers: []*dnstype.Resolver{ {Addr: "8.8.4.4"}, }, @@ -326,8 +365,9 @@ func TestDNSConfigForNetmap(t *testing.T) { CorpDNS: true, }, want: &dns.Config{ - Hosts: map[dnsname.FQDN][]netip.Addr{}, - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, }, }, { @@ -349,6 +389,96 @@ func TestDNSConfigForNetmap(t *testing.T) { prefs: &ipn.Prefs{}, want: &dns.Config{}, }, + { + name: "conn25-split-dns", + nm: &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "a", + Addresses: ipps("100.101.101.101"), + CapMap: tailcfg.NodeCapMap{ + tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{ + tailcfg.RawMessage(`{"name":"app1","connectors":["tag:woo"],"domains":["example.com"]}`), + }, + }, + }).View(), + AllCaps: set.Of(tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName)), + }, + peers: nodeViews([]*tailcfg.Node{ + { + ID: 1, + Name: "p1", + Addresses: ipps("100.102.0.1"), + Tags: []string{"tag:woo"}, + Hostinfo: (&tailcfg.Hostinfo{ + Services: []tailcfg.Service{ + { + Proto: tailcfg.PeerAPI4, + Port: 1234, + }, + }, + AppConnector: opt.NewBool(true), + }).View(), + }, + }), + prefs: &ipn.Prefs{ + CorpDNS: true, + }, + want: &dns.Config{ + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{ + "a.": ips("100.101.101.101"), + "p1.": ips("100.102.0.1"), + }, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{ + dnsname.FQDN("example.com."): { + {Addr: "http://100.102.0.1:1234/dns-query"}, + }, + }, + }, + }, + { + name: "conn25-split-dns-no-matching-peers", + nm: &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "a", + Addresses: ipps("100.101.101.101"), + CapMap: tailcfg.NodeCapMap{ + tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{ + tailcfg.RawMessage(`{"name":"app1","connectors":["tag:woo"],"domains":["example.com"]}`), + }, + }, + }).View(), + AllCaps: set.Of(tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName)), + }, + peers: nodeViews([]*tailcfg.Node{ + { + ID: 1, + Name: "p1", + Addresses: ipps("100.102.0.1"), + Tags: []string{"tag:nomatch"}, + Hostinfo: (&tailcfg.Hostinfo{ + Services: []tailcfg.Service{ + { + Proto: tailcfg.PeerAPI4, + Port: 1234, + }, + }, + AppConnector: opt.NewBool(true), + }).View(), + }, + }), + prefs: &ipn.Prefs{ + CorpDNS: true, + }, + want: &dns.Config{ + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: map[dnsname.FQDN][]netip.Addr{ + "a.": ips("100.101.101.101"), + "p1.": ips("100.102.0.1"), + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/ipn/ipnlocal/drive.go b/ipn/ipnlocal/drive.go index 456cd45441ba9..485114eae9d27 100644 --- a/ipn/ipnlocal/drive.go +++ b/ipn/ipnlocal/drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/ipn/ipnlocal/drive_test.go b/ipn/ipnlocal/drive_test.go index 323c3821499ed..aca05432b8276 100644 --- a/ipn/ipnlocal/drive_test.go +++ b/ipn/ipnlocal/drive_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/ipn/ipnlocal/drive_tomove.go b/ipn/ipnlocal/drive_tomove.go index 290fe097022fd..ccea48f7a5106 100644 --- a/ipn/ipnlocal/drive_tomove.go +++ b/ipn/ipnlocal/drive_tomove.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This is the Taildrive stuff that should ideally be registered in init only when diff --git a/ipn/ipnlocal/expiry.go b/ipn/ipnlocal/expiry.go index 8ea63d21a4fb0..461dce2fda055 100644 --- a/ipn/ipnlocal/expiry.go +++ b/ipn/ipnlocal/expiry.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/expiry_test.go b/ipn/ipnlocal/expiry_test.go index 2c646ca724efd..1d85d81d1819d 100644 --- a/ipn/ipnlocal/expiry_test.go +++ b/ipn/ipnlocal/expiry_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/extension_host.go b/ipn/ipnlocal/extension_host.go index ca802ab89f747..125a2329447a3 100644 --- a/ipn/ipnlocal/extension_host.go +++ b/ipn/ipnlocal/extension_host.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/extension_host_test.go b/ipn/ipnlocal/extension_host_test.go index f5c081a5bdb3e..3bd302aeab93d 100644 --- a/ipn/ipnlocal/extension_host_test.go +++ b/ipn/ipnlocal/extension_host_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/hwattest.go b/ipn/ipnlocal/hwattest.go index 2c93cad4c97ff..07c09dc7fe043 100644 --- a/ipn/ipnlocal/hwattest.go +++ b/ipn/ipnlocal/hwattest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tpm diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index f7d1b93421d13..d2d52ca422b10 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnlocal is the heart of the Tailscale node agent that controls @@ -6,6 +6,7 @@ package ipnlocal import ( + "bufio" "cmp" "context" "crypto/sha256" @@ -21,6 +22,7 @@ import ( "net/netip" "net/url" "os" + "path/filepath" "reflect" "runtime" "slices" @@ -97,6 +99,7 @@ import ( "tailscale.com/util/syspolicy/ptype" "tailscale.com/util/testenv" "tailscale.com/util/usermetric" + "tailscale.com/util/vizerror" "tailscale.com/version" "tailscale.com/version/distro" "tailscale.com/wgengine" @@ -165,6 +168,10 @@ var ( // errManagedByPolicy indicates the operation is blocked // because the target state is managed by a GP/MDM policy. errManagedByPolicy = errors.New("managed by policy") + + // ErrProfileStorageUnavailable indicates that profile-specific local data + // storage is not available; see [LocalBackend.ProfileMkdirAll]. + ErrProfileStorageUnavailable = errors.New("profile local data storage unavailable") ) // LocalBackend is the glue between the major pieces of the Tailscale @@ -264,6 +271,7 @@ type LocalBackend struct { // of [LocalBackend]'s own state that is not tied to the node context. currentNodeAtomic atomic.Pointer[nodeBackend] + diskCache diskCache conf *conffile.Config // latest parsed config, or nil if not in declarative mode pm *profileManager // mu guards access lastFilterInputs *filterInputs @@ -917,6 +925,22 @@ func (b *LocalBackend) setStateLocked(state ipn.State) { } } +func (b *LocalBackend) IPServiceMappings() netmap.IPServiceMappings { + b.mu.Lock() + defer b.mu.Unlock() + return b.ipVIPServiceMap +} + +func (b *LocalBackend) SetIPServiceMappingsForTest(m netmap.IPServiceMappings) { + b.mu.Lock() + defer b.mu.Unlock() + testenv.AssertInTest() + b.ipVIPServiceMap = m + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateIPServiceMappings(m) + } +} + // setConfigLocked uses the provided config to update the backend's prefs // and other state. func (b *LocalBackend) setConfigLocked(conf *conffile.Config) error { @@ -1007,7 +1031,6 @@ func (b *LocalBackend) linkChange(delta *netmon.ChangeDelta) { // If the local network configuration has changed, our filter may // need updating to tweak default routes. b.updateFilterLocked(prefs) - updateExitNodeUsageWarning(prefs, delta.CurrentState(), b.health) if buildfeatures.HasPeerAPIServer { cn := b.currentNode() @@ -1541,22 +1564,6 @@ func (b *LocalBackend) GetFilterForTest() *filter.Filter { return nb.filterAtomic.Load() } -func (b *LocalBackend) settleEventBus() { - // The move to eventbus made some things racy that - // weren't before so we have to wait for it to all be settled - // before we call certain things. - // See https://github.com/tailscale/tailscale/issues/16369 - // But we can't do this while holding b.mu without deadlocks, - // (https://github.com/tailscale/tailscale/pull/17804#issuecomment-3514426485) so - // now we just do it in lots of places before acquiring b.mu. - // Is this winning?? - if b.sys != nil { - if ms, ok := b.sys.MagicSock.GetOK(); ok { - ms.Synchronize() - } - } -} - // SetControlClientStatus is the callback invoked by the control client whenever it posts a new status. // Among other things, this is where we update the netmap, packet filters, DNS and DERP maps. func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st controlclient.Status) { @@ -1566,7 +1573,13 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control } b.mu.Lock() defer b.mu.Unlock() + b.setControlClientStatusLocked(c, st) +} +// setControlClientStatusLocked is the locked version of SetControlClientStatus. +// +// b.mu must be held. +func (b *LocalBackend) setControlClientStatusLocked(c controlclient.Client, st controlclient.Status) { if b.cc != c { b.logf("Ignoring SetControlClientStatus from old client") return @@ -1577,9 +1590,8 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control return } b.logf("Received error: %v", st.Err) - var uerr controlclient.UserVisibleError - if errors.As(st.Err, &uerr) { - s := uerr.UserVisibleError() + if vizerr, ok := vizerror.As(st.Err); ok { + s := vizerr.Error() b.sendLocked(ipn.Notify{ErrMessage: &s}) } return @@ -2094,16 +2106,16 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo } }() - // Gross. See https://github.com/tailscale/tailscale/issues/16369 - b.settleEventBus() - defer b.settleEventBus() - b.mu.Lock() defer b.mu.Unlock() cn := b.currentNode() cn.UpdateNetmapDelta(muts) + if ms, ok := b.sys.MagicSock.GetOK(); ok { + ms.UpdateNetmapDelta(muts) + } + // If auto exit nodes are enabled and our exit node went offline, // we need to schedule picking a new one. // TODO(nickkhyl): move the auto exit node logic to a feature package. @@ -2408,6 +2420,14 @@ func (b *LocalBackend) initOnce() { b.extHost.Init() } +func (b *LocalBackend) controlDebugFlags() []string { + debugFlags := controlDebugFlags + if b.sys.IsNetstackRouter() { + return append([]string{"netstack"}, debugFlags...) + } + return debugFlags +} + // Start applies the configuration specified in opts, and starts the // state machine. // @@ -2419,7 +2439,6 @@ func (b *LocalBackend) initOnce() { // actually a supported operation (it should be, but it's very unclear // from the following whether or not that is a safe transition). func (b *LocalBackend) Start(opts ipn.Options) error { - defer b.settleEventBus() // with b.mu unlocked b.mu.Lock() defer b.mu.Unlock() return b.startLocked(opts) @@ -2457,7 +2476,9 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { if b.state != ipn.Running && b.conf == nil && opts.AuthKey == "" { sysak, _ := b.polc.GetString(pkey.AuthKey, "") - if sysak != "" { + if sysak != "" && len(b.pm.Profiles()) > 0 && b.state != ipn.NeedsLogin { + logf("not setting opts.AuthKey from syspolicy; login profiles exist, state=%v", b.state) + } else if sysak != "" { logf("setting opts.AuthKey by syspolicy, len=%v", len(sysak)) opts.AuthKey = strings.TrimSpace(sysak) } @@ -2563,14 +2584,18 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { persistv = new(persist.Persist) } - discoPublic := b.MagicConn().DiscoPublicKey() - - isNetstack := b.sys.IsNetstackRouter() - debugFlags := controlDebugFlags - if isNetstack { - debugFlags = append([]string{"netstack"}, debugFlags...) + if envknob.Bool("TS_USE_CACHED_NETMAP") { + if nm, ok := b.loadDiskCacheLocked(); ok { + logf("loaded netmap from disk cache; %d peers", len(nm.Peers)) + b.setControlClientStatusLocked(nil, controlclient.Status{ + NetMap: nm, + LoggedIn: true, // sure + }) + } } + discoPublic := b.MagicConn().DiscoPublicKey() + var ccShutdownCbs []func() ccShutdown := func() { for _, cb := range ccShutdownCbs { @@ -2596,7 +2621,7 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { Hostinfo: b.hostInfoWithServicesLocked(), HTTPTestClient: httpTestClient, DiscoPublicKey: discoPublic, - DebugFlags: debugFlags, + DebugFlags: b.controlDebugFlags(), HealthTracker: b.health, PolicyClient: b.sys.PolicyClientOrDefault(), Pinger: b, @@ -2612,7 +2637,7 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { // Don't warn about broken Linux IP forwarding when // netstack is being used. - SkipIPForwardingCheck: isNetstack, + SkipIPForwardingCheck: b.sys.IsNetstackRouter(), }) if err != nil { return err @@ -2863,7 +2888,11 @@ func (b *LocalBackend) updateFilterLocked(prefs ipn.PrefsView) { b.setFilter(filter.NewShieldsUpFilter(localNets, logNets, oldFilter, b.logf)) } else { b.logf("[v1] netmap packet filter: %v filters", len(packetFilter)) - b.setFilter(filter.New(packetFilter, b.srcIPHasCapForFilter, localNets, logNets, oldFilter, b.logf)) + filt := filter.New(packetFilter, b.srcIPHasCapForFilter, localNets, logNets, oldFilter, b.logf) + + filt.IngressAllowHooks = b.extHost.Hooks().Filter.IngressAllowHooks + filt.LinkLocalAllowHooks = b.extHost.Hooks().Filter.LinkLocalAllowHooks + b.setFilter(filt) } // The filter for a jailed node is the exact same as a ShieldsUp filter. oldJailedFilter := b.e.GetJailedFilter() @@ -2918,6 +2947,9 @@ func packetFilterPermitsUnlockedNodes(peers map[tailcfg.NodeID]tailcfg.NodeView, func (b *LocalBackend) setFilter(f *filter.Filter) { b.currentNode().setFilter(f) b.e.SetFilter(f) + if ms, ok := b.sys.MagicSock.GetOK(); ok { + ms.SetFilter(f) + } } var removeFromDefaultRoute = []netip.Prefix{ @@ -4185,35 +4217,6 @@ func (b *LocalBackend) isDefaultServerLocked() bool { return prefs.ControlURLOrDefault(b.polc) == ipn.DefaultControlURL } -var exitNodeMisconfigurationWarnable = health.Register(&health.Warnable{ - Code: "exit-node-misconfiguration", - Title: "Exit node misconfiguration", - Severity: health.SeverityMedium, - Text: func(args health.Args) string { - return "Exit node misconfiguration: " + args[health.ArgError] - }, -}) - -// updateExitNodeUsageWarning updates a warnable meant to notify users of -// configuration issues that could break exit node usage. -func updateExitNodeUsageWarning(p ipn.PrefsView, state *netmon.State, healthTracker *health.Tracker) { - if !buildfeatures.HasUseExitNode { - return - } - var msg string - if p.ExitNodeIP().IsValid() || p.ExitNodeID() != "" { - warn, _ := netutil.CheckReversePathFiltering(state) - if len(warn) > 0 { - msg = fmt.Sprintf("%s: %v, %s", healthmsg.WarnExitNodeUsage, warn, healthmsg.DisableRPFilter) - } - } - if len(msg) > 0 { - healthTracker.SetUnhealthy(exitNodeMisconfigurationWarnable, health.Args{health.ArgError: msg}) - } else { - healthTracker.SetHealthy(exitNodeMisconfigurationWarnable) - } -} - func (b *LocalBackend) checkExitNodePrefsLocked(p *ipn.Prefs) error { tryingToUseExitNode := p.ExitNodeIP.IsValid() || p.ExitNodeID != "" if !tryingToUseExitNode { @@ -4334,7 +4337,6 @@ func (b *LocalBackend) EditPrefsAs(mp *ipn.MaskedPrefs, actor ipnauth.Actor) (ip if mp.SetsInternal() { return ipn.PrefsView{}, errors.New("can't set Internal fields") } - defer b.settleEventBus() b.mu.Lock() defer b.mu.Unlock() @@ -4502,6 +4504,12 @@ func (b *LocalBackend) onEditPrefsLocked(_ ipnauth.Actor, mp *ipn.MaskedPrefs, o } } + if mp.AdvertiseServicesSet { + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateActiveVIPServices(newPrefs.AdvertiseServices()) + } + } + // This is recorded here in the EditPrefs path, not the setPrefs path on purpose. // recordForEdit records metrics related to edits and changes, not the final state. // If, in the future, we want to record gauge-metrics related to the state of prefs, @@ -5125,7 +5133,7 @@ func (b *LocalBackend) authReconfigLocked() { } oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.NetMon.Get(), b.sys.ControlKnobs(), version.OS()) - rcfg := b.routerConfigLocked(cfg, prefs, oneCGNATRoute) + rcfg := b.routerConfigLocked(cfg, prefs, nm, oneCGNATRoute) err = b.e.Reconfig(cfg, rcfg, dcfg) if err == wgengine.ErrNoChanges { @@ -5233,6 +5241,56 @@ func (b *LocalBackend) TailscaleVarRoot() string { return "" } +// ProfileMkdirAll creates (if necessary) and returns the path of a directory +// specific to the specified login profile, inside Tailscale's writable storage +// area. If subs are provided, they are joined to the base path to form the +// subdirectory path. +// +// It reports [ErrProfileStorageUnavailable] if there's no configured or +// discovered storage location, or if there was an error making the +// subdirectory. +func (b *LocalBackend) ProfileMkdirAll(id ipn.ProfileID, subs ...string) (string, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.profileMkdirAllLocked(id, subs...) +} + +// profileDataPathLocked returns a path of a profile-specific (sub)directory +// inside the writable storage area for the given profile ID. It does not +// create or verify the existence of the path in the filesystem. +// If b.varRoot == "", it returns "". It panics if id is empty. +// +// The caller must hold b.mu. +func (b *LocalBackend) profileDataPathLocked(id ipn.ProfileID, subs ...string) string { + if id == "" { + panic("invalid empty profile ID") + } + vr := b.TailscaleVarRoot() + if vr == "" { + return "" + } + return filepath.Join(append([]string{vr, "profile-data", string(id)}, subs...)...) +} + +// profileMkdirAllLocked implements ProfileMkdirAll. +// The caller must hold b.mu. +func (b *LocalBackend) profileMkdirAllLocked(id ipn.ProfileID, subs ...string) (string, error) { + if id == "" { + return "", errProfileNotFound + } + if vr := b.TailscaleVarRoot(); vr == "" { + return "", ErrProfileStorageUnavailable + } + + // Use the LoginProfile ID rather than the UserProfile ID, as the latter may + // change over time. + dir := b.profileDataPathLocked(id, subs...) + if err := os.MkdirAll(dir, 0700); err != nil { + return "", fmt.Errorf("create profile directory: %w", err) + } + return dir, nil +} + // closePeerAPIListenersLocked closes any existing PeerAPI listeners // and clears out the PeerAPI server state. // @@ -5450,7 +5508,7 @@ func peerRoutes(logf logger.Logf, peers []wgcfg.Peer, cgnatThreshold int, routeA // routerConfig produces a router.Config from a wireguard config and IPN prefs. // // b.mu must be held. -func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView, oneCGNATRoute bool) *router.Config { +func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView, nm *netmap.NetworkMap, oneCGNATRoute bool) *router.Config { singleRouteThreshold := 10_000 if oneCGNATRoute { singleRouteThreshold = 1 @@ -5518,7 +5576,7 @@ func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView b.logf("failed to discover interface ips: %v", err) } switch runtime.GOOS { - case "linux", "windows", "darwin", "ios", "android": + case "linux", "windows", "darwin", "ios", "android", "openbsd": rs.LocalRoutes = internalIPs // unconditionally allow access to guest VM networks if prefs.ExitNodeAllowLANAccess() { rs.LocalRoutes = append(rs.LocalRoutes, externalIPs...) @@ -5535,11 +5593,23 @@ func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView } } + // Get the VIPs for VIP services this node hosts. We will add all locally served VIPs to routes then + // we terminate these connection locally in netstack instead of routing to peer. + vipServiceIPs := nm.GetIPVIPServiceMap() + v4, v6 := false, false + if slices.ContainsFunc(rs.LocalAddrs, tsaddr.PrefixIs4) { rs.Routes = append(rs.Routes, netip.PrefixFrom(tsaddr.TailscaleServiceIP(), 32)) + v4 = true } if slices.ContainsFunc(rs.LocalAddrs, tsaddr.PrefixIs6) { rs.Routes = append(rs.Routes, netip.PrefixFrom(tsaddr.TailscaleServiceIPv6(), 128)) + v6 = true + } + for vip := range vipServiceIPs { + if (vip.Is4() && v4) || (vip.Is6() && v6) { + rs.Routes = append(rs.Routes, netip.PrefixFrom(vip, vip.BitLen())) + } } return rs @@ -5585,6 +5655,10 @@ func (b *LocalBackend) applyPrefsToHostinfoLocked(hi *tailcfg.Hostinfo, prefs ip } hi.SSH_HostKeys = sshHostKeys + if buildfeatures.HasRelayServer { + hi.PeerRelay = prefs.RelayServerPort().Valid() + } + for _, f := range hookMaybeMutateHostinfoLocked { f(b, hi, prefs) } @@ -6172,8 +6246,20 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { var login string if nm != nil { login = cmp.Or(profileFromView(nm.UserProfiles[nm.User()]).LoginName, "") + if envknob.Bool("TS_USE_CACHED_NETMAP") { + if err := b.writeNetmapToDiskLocked(nm); err != nil { + b.logf("write netmap to cache: %v", err) + } + } } b.currentNode().SetNetMap(nm) + if ms, ok := b.sys.MagicSock.GetOK(); ok { + if nm != nil { + ms.SetNetworkMap(nm.SelfNode, nm.Peers) + } else { + ms.SetNetworkMap(tailcfg.NodeView{}, nil) + } + } if login != b.activeLogin { b.logf("active login: %v", login) b.activeLogin = login @@ -6217,7 +6303,15 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs()) if buildfeatures.HasServe { - b.ipVIPServiceMap = nm.GetIPVIPServiceMap() + m := nm.GetIPVIPServiceMap() + b.ipVIPServiceMap = m + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateIPServiceMappings(m) + // In case the prefs reloaded from Profile Manager but didn't change, + // we still need to load the active VIP services into netstack. + ns.UpdateActiveVIPServices(b.pm.CurrentPrefs().AdvertiseServices()) + } + } if !oldSelf.Equal(nm.SelfNodeOrZero()) { @@ -7016,6 +7110,12 @@ func (b *LocalBackend) DeleteProfile(p ipn.ProfileID) error { } return err } + // Make a best-effort to remove the profile-specific data directory, if one exists. + if pd := b.profileDataPathLocked(p); pd != "" { + if err := os.RemoveAll(pd); err != nil { + b.logf("warning: removing profile data for %q: %v", p, err) + } + } if !needToRestart { return nil } @@ -7388,13 +7488,20 @@ func suggestExitNode(report *netcheck.Report, nb *nodeBackend, prevSuggestion ta switch { case nb.SelfHasCap(tailcfg.NodeAttrTrafficSteering): // The traffic-steering feature flag is enabled on this tailnet. - return suggestExitNodeUsingTrafficSteering(nb, allowList) + res, err = suggestExitNodeUsingTrafficSteering(nb, allowList) default: // The control plane will always strip the `traffic-steering` // node attribute if it isn’t enabled for this tailnet, even if // it is set in the policy file: tailscale/corp#34401 - return suggestExitNodeUsingDERP(report, nb, prevSuggestion, selectRegion, selectNode, allowList) + res, err = suggestExitNodeUsingDERP(report, nb, prevSuggestion, selectRegion, selectNode, allowList) + } + if err != nil { + nb.logf("netmap: suggested exit node: %v", err) + } else { + name, _, _ := strings.Cut(res.Name, ".") + nb.logf("netmap: suggested exit node: %s (%s)", name, res.ID) } + return res, err } // suggestExitNodeUsingDERP is the classic algorithm used to suggest exit nodes, @@ -7627,6 +7734,21 @@ func suggestExitNodeUsingTrafficSteering(nb *nodeBackend, allowed set.Set[tailcf pick = nodes[0] } + nb.logf("netmap: traffic steering: exit node scores: %v", logger.ArgWriter(func(bw *bufio.Writer) { + const max = 10 + for i, n := range nodes { + if i == max { + fmt.Fprintf(bw, "... +%d", len(nodes)-max) + return + } + if i > 0 { + bw.WriteString(", ") + } + name, _, _ := strings.Cut(n.Name(), ".") + fmt.Fprintf(bw, "%d:%s", score(n), name) + } + })) + if !pick.Valid() { return apitype.ExitNodeSuggestionResponse{}, nil } diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index bcc5ebaf26dbf..259e4b6b28a83 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal @@ -41,6 +41,7 @@ import ( "tailscale.com/ipn" "tailscale.com/ipn/conffile" "tailscale.com/ipn/ipnauth" + "tailscale.com/ipn/ipnlocal/netmapcache" "tailscale.com/ipn/store/mem" "tailscale.com/net/netcheck" "tailscale.com/net/netmon" @@ -611,6 +612,105 @@ func makeExitNode(id tailcfg.NodeID, opts ...peerOptFunc) tailcfg.NodeView { return makePeer(id, append([]peerOptFunc{withCap(26), withSuggest(), withExitRoutes()}, opts...)...) } +func TestLoadCachedNetMap(t *testing.T) { + t.Setenv("TS_USE_CACHED_NETMAP", "1") + + // Write a small network map into a cache, and verify we can load it. + varRoot := t.TempDir() + cacheDir := filepath.Join(varRoot, "profile-data", "id0", "netmap-cache") + if err := os.MkdirAll(cacheDir, 0700); err != nil { + t.Fatalf("Create cache directory: %v", err) + } + + testMap := &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "example.ts.net", + User: tailcfg.UserID(1), + Addresses: []netip.Prefix{ + netip.MustParsePrefix("100.2.3.4/32"), + }, + }).View(), + UserProfiles: map[tailcfg.UserID]tailcfg.UserProfileView{ + tailcfg.UserID(1): (&tailcfg.UserProfile{ + ID: 1, + LoginName: "amelie@example.com", + DisplayName: "Amelie du Pangoline", + }).View(), + }, + Peers: []tailcfg.NodeView{ + (&tailcfg.Node{ + ID: 601, + StableID: "n601FAKE", + ComputedName: "some-peer", + User: tailcfg.UserID(1), + Key: makeNodeKeyFromID(601), + Addresses: []netip.Prefix{ + netip.MustParsePrefix("100.2.3.5/32"), + }, + }).View(), + (&tailcfg.Node{ + ID: 602, + StableID: "n602FAKE", + ComputedName: "some-tagged-peer", + Tags: []string{"tag:server", "tag:test"}, + User: tailcfg.UserID(1), + Key: makeNodeKeyFromID(602), + Addresses: []netip.Prefix{ + netip.MustParsePrefix("100.2.3.6/32"), + }, + }).View(), + }, + } + dc := netmapcache.NewCache(netmapcache.FileStore(cacheDir)) + if err := dc.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store netmap in cache: %v", err) + } + + // Now make a new backend and hook it up to have access to the cache created + // above, then start it to pull in the cached netmap. + sys := tsd.NewSystem() + e, err := wgengine.NewFakeUserspaceEngine(logger.Discard, + sys.Set, + sys.HealthTracker.Get(), + sys.UserMetricsRegistry(), + sys.Bus.Get(), + ) + if err != nil { + t.Fatalf("Make userspace engine: %v", err) + } + t.Cleanup(e.Close) + sys.Set(e) + sys.Set(new(mem.Store)) + + logf := tstest.WhileTestRunningLogger(t) + clb, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0) + if err != nil { + t.Fatalf("Make local backend: %v", err) + } + t.Cleanup(clb.Shutdown) + clb.SetVarRoot(varRoot) + + pm := must.Get(newProfileManager(new(mem.Store), logf, health.NewTracker(sys.Bus.Get()))) + pm.currentProfile = (&ipn.LoginProfile{ID: "id0"}).View() + clb.pm = pm + + // Start up the node. We can't actually log in, because we have no + // controlplane, but verify that we got a network map. + if err := clb.Start(ipn.Options{}); err != nil { + t.Fatalf("Start local backend: %v", err) + } + + // Check that the network map the backend wound up with is the one we + // stored, modulo uncached fields. + nm := clb.currentNode().NetMap() + if diff := cmp.Diff(nm, testMap, + cmpopts.IgnoreFields(netmap.NetworkMap{}, "Cached", "PacketFilter", "PacketFilterRules"), + cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}), + ); diff != "" { + t.Error(diff) + } +} + func TestConfigureExitNode(t *testing.T) { controlURL := "https://localhost:1/" exitNode1 := makeExitNode(1, withName("node-1"), withDERP(1), withAddresses(netip.MustParsePrefix("100.64.1.1/32"))) @@ -2306,6 +2406,56 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) { } } +func TestProfileMkdirAll(t *testing.T) { + t.Run("NoVarRoot", func(t *testing.T) { + b := newTestBackend(t) + b.SetVarRoot("") + + got, err := b.ProfileMkdirAll(b.CurrentProfile().ID()) + if got != "" || !errors.Is(err, ErrProfileStorageUnavailable) { + t.Errorf(`ProfileMkdirAll: got %q, %v; want "", %v`, got, err, ErrProfileStorageUnavailable) + } + }) + + t.Run("InvalidProfileID", func(t *testing.T) { + b := newTestBackend(t) + got, err := b.ProfileMkdirAll("") + if got != "" || !errors.Is(err, errProfileNotFound) { + t.Errorf("ProfileMkdirAll: got %q, %v; want %q, %v", got, err, "", errProfileNotFound) + } + }) + + t.Run("ProfileRoot", func(t *testing.T) { + b := newTestBackend(t) + want := filepath.Join(b.TailscaleVarRoot(), "profile-data", "id0") + + got, err := b.ProfileMkdirAll(b.CurrentProfile().ID()) + if err != nil || got != want { + t.Errorf("ProfileMkdirAll: got %q, %v, want %q, nil", got, err, want) + } + if fi, err := os.Stat(got); err != nil { + t.Errorf("Check directory: %v", err) + } else if !fi.IsDir() { + t.Errorf("Path %q is not a directory", got) + } + }) + + t.Run("ProfileSubdir", func(t *testing.T) { + b := newTestBackend(t) + want := filepath.Join(b.TailscaleVarRoot(), "profile-data", "id0", "a", "b") + + got, err := b.ProfileMkdirAll(b.CurrentProfile().ID(), "a", "b") + if err != nil || got != want { + t.Errorf("ProfileMkdirAll: got %q, %v, want %q, nil", got, err, want) + } + if fi, err := os.Stat(got); err != nil { + t.Errorf("Check directory: %v", err) + } else if !fi.IsDir() { + t.Errorf("Path %q is not a directory", got) + } + }) +} + func TestOfferingAppConnector(t *testing.T) { for _, shouldStore := range []bool{false, true} { b := newTestBackend(t) @@ -7380,8 +7530,31 @@ func TestRouteAllDisabled(t *testing.T) { cfg := &wgcfg.Config{ Peers: tt.peers, } + ServiceIPMappings := tailcfg.ServiceIPMappings{ + "svc:test-service": []netip.Addr{ + netip.MustParseAddr("100.64.1.2"), + netip.MustParseAddr("fd7a:abcd:1234::1"), + }, + } + svcIPMapJSON, err := json.Marshal(ServiceIPMappings) + if err != nil { + t.Fatalf("failed to marshal ServiceIPMappings: %v", err) + } + nm := &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "test-node", + Addresses: []netip.Prefix{ + pp("100.64.1.1/32"), + }, + CapMap: tailcfg.NodeCapMap{ + tailcfg.NodeAttrServiceHost: []tailcfg.RawMessage{ + tailcfg.RawMessage(svcIPMapJSON), + }, + }, + }).View(), + } - rcfg := lb.routerConfigLocked(cfg, prefs.View(), false) + rcfg := lb.routerConfigLocked(cfg, prefs.View(), nm, false) for _, p := range rcfg.Routes { found := false for _, r := range tt.wantEndpoints { diff --git a/ipn/ipnlocal/loglines_test.go b/ipn/ipnlocal/loglines_test.go index d831aa8b075dc..733c7381b2016 100644 --- a/ipn/ipnlocal/loglines_test.go +++ b/ipn/ipnlocal/loglines_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/netmapcache/netmapcache.go b/ipn/ipnlocal/netmapcache/netmapcache.go new file mode 100644 index 0000000000000..1b8347f0b8d18 --- /dev/null +++ b/ipn/ipnlocal/netmapcache/netmapcache.go @@ -0,0 +1,384 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package netmapcache implements a persistent cache for [netmap.NetworkMap] +// values, allowing a client to start up using stale but previously-valid state +// even if a connection to the control plane is not immediately available. +package netmapcache + +import ( + "cmp" + "context" + "crypto/sha256" + "encoding/hex" + jsonv1 "encoding/json" + "errors" + "fmt" + "io/fs" + "iter" + "os" + "path/filepath" + "slices" + "strings" + "time" + + "tailscale.com/feature/buildfeatures" + "tailscale.com/tailcfg" + "tailscale.com/types/netmap" + "tailscale.com/util/mak" + "tailscale.com/util/set" + "tailscale.com/wgengine/filter" +) + +var ( + // ErrKeyNotFound is a sentinel error reported by implementations of the [Store] + // interface when loading a key that is not found in the store. + ErrKeyNotFound = errors.New("storage key not found") + + // ErrCacheNotAvailable is a sentinel error reported by cache methods when + // the netmap caching feature is not enabled in the build. + ErrCacheNotAvailable = errors.New("netmap cache is not available") +) + +// A Cache manages a columnar cache of a [netmap.NetworkMap]. Each Cache holds +// a single netmap value; use [Cache.Store] to update or replace the cached +// value and [Cache.Load] to read the cached value. +type Cache struct { + store Store + + // wantKeys records the cache keys from the last write or load of a cached + // netmap. This is used to prune keys that are no longer referenced after an + // update. + wantKeys set.Set[cacheKey] + + // lastWrote records the last values written to each stored key. + // + // TODO(creachadair): This is meant to avoid disk writes, but I'm not + // convinced we need it. Or maybe just track hashes of the content rather + // than caching a complete copy. + lastWrote map[cacheKey]lastWrote +} + +// NewCache constructs a new empty [Cache] from the given [Store]. +// It will panic if s == nil. +func NewCache(s Store) *Cache { + if s == nil { + panic("a non-nil Store is required") + } + return &Cache{ + store: s, + wantKeys: make(set.Set[cacheKey]), + lastWrote: make(map[cacheKey]lastWrote), + } +} + +type lastWrote struct { + digest string + at time.Time +} + +func (c *Cache) writeJSON(ctx context.Context, key cacheKey, v any) error { + j, err := jsonv1.Marshal(v) + if err != nil { + return fmt.Errorf("JSON marshalling %q: %w", key, err) + } + + // TODO(creachadair): Maybe use a hash instead of the contents? Do we need + // this at all? + last, ok := c.lastWrote[key] + if ok && cacheDigest(j) == last.digest { + c.wantKeys.Add(key) + return nil + } + + if err := c.store.Store(ctx, string(key), j); err != nil { + return err + } + + // Track the storage keys the current map is using, for storage GC. + c.wantKeys.Add(key) + c.lastWrote[key] = lastWrote{ + digest: cacheDigest(j), + at: time.Now(), + } + return nil +} + +func (c *Cache) removeUnwantedKeys(ctx context.Context) error { + var errs []error + for key, err := range c.store.List(ctx, "") { + if err != nil { + errs = append(errs, err) + break + } + ckey := cacheKey(key) + if !c.wantKeys.Contains(ckey) { + if err := c.store.Remove(ctx, key); err != nil { + errs = append(errs, fmt.Errorf("remove key %q: %w", key, err)) + } + delete(c.lastWrote, ckey) // even if removal failed, we don't want it + } + } + return errors.Join(errs...) +} + +// FileStore implements the [Store] interface using a directory of files, in +// which each key is encoded as a filename in the directory. +// The caller is responsible to ensure the directory path exists before +// using the store methods. +type FileStore string + +// List implements part of the [Store] interface. +func (s FileStore) List(ctx context.Context, prefix string) iter.Seq2[string, error] { + return func(yield func(string, error) bool) { + des, err := os.ReadDir(string(s)) + if os.IsNotExist(err) { + return // nothing to read + } else if err != nil { + yield("", err) + return + } + + // os.ReadDir reports entries already sorted, and the encoding preserves that. + for _, de := range des { + key, err := hex.DecodeString(de.Name()) + if err != nil { + yield("", err) + return + } + name := string(key) + if !strings.HasPrefix(name, prefix) { + continue + } else if !yield(name, nil) { + return + } + } + } +} + +// Load implements part of the [Store] interface. +func (s FileStore) Load(ctx context.Context, key string) ([]byte, error) { + data, err := os.ReadFile(filepath.Join(string(s), hex.EncodeToString([]byte(key)))) + if errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("key %q not found: %w", key, ErrKeyNotFound) + } + return data, err +} + +// Store implements part of the [Store] interface. +func (s FileStore) Store(ctx context.Context, key string, value []byte) error { + return os.WriteFile(filepath.Join(string(s), hex.EncodeToString([]byte(key))), value, 0600) +} + +// Remove implements part of the [Store] interface. +func (s FileStore) Remove(ctx context.Context, key string) error { + err := os.Remove(filepath.Join(string(s), hex.EncodeToString([]byte(key)))) + if errors.Is(err, fs.ErrNotExist) { + return nil + } + return err +} + +// cacheKey is a type wrapper for strings used as cache keys. +type cacheKey string + +const ( + selfKey cacheKey = "self" + miscKey cacheKey = "msic" + dnsKey cacheKey = "dns" + derpMapKey cacheKey = "derpmap" + peerKeyPrefix cacheKey = "peer-" // + stable ID + userKeyPrefix cacheKey = "user-" // + profile ID + sshPolicyKey cacheKey = "ssh" + packetFilterKey cacheKey = "filter" +) + +// Store records nm in the cache, replacing any previously-cached values. +func (c *Cache) Store(ctx context.Context, nm *netmap.NetworkMap) error { + if !buildfeatures.HasCacheNetMap || nm == nil || nm.Cached { + return nil + } + if selfID := nm.User(); selfID == 0 { + return errors.New("no user in netmap") + } + + clear(c.wantKeys) + if err := c.writeJSON(ctx, miscKey, netmapMisc{ + MachineKey: &nm.MachineKey, + CollectServices: &nm.CollectServices, + DisplayMessages: &nm.DisplayMessages, + TKAEnabled: &nm.TKAEnabled, + TKAHead: &nm.TKAHead, + Domain: &nm.Domain, + DomainAuditLogID: &nm.DomainAuditLogID, + }); err != nil { + return err + } + if err := c.writeJSON(ctx, dnsKey, netmapDNS{DNS: &nm.DNS}); err != nil { + return err + } + if err := c.writeJSON(ctx, derpMapKey, netmapDERPMap{DERPMap: &nm.DERPMap}); err != nil { + return err + } + if err := c.writeJSON(ctx, selfKey, netmapNode{Node: &nm.SelfNode}); err != nil { + return err + + // N.B. The NodeKey and AllCaps fields can be recovered from SelfNode on + // load, and do not need to be stored separately. + } + for _, p := range nm.Peers { + key := peerKeyPrefix + cacheKey(p.StableID()) + if err := c.writeJSON(ctx, key, netmapNode{Node: &p}); err != nil { + return err + } + } + for uid, u := range nm.UserProfiles { + key := fmt.Sprintf("%s%d", userKeyPrefix, uid) + if err := c.writeJSON(ctx, cacheKey(key), netmapUserProfile{UserProfile: &u}); err != nil { + return err + } + } + if err := c.writeJSON(ctx, packetFilterKey, netmapPacketFilter{Rules: &nm.PacketFilterRules}); err != nil { + return err + } + + if buildfeatures.HasSSH && nm.SSHPolicy != nil { + if err := c.writeJSON(ctx, sshPolicyKey, netmapSSH{SSHPolicy: &nm.SSHPolicy}); err != nil { + return err + } + } + + return c.removeUnwantedKeys(ctx) +} + +// Load loads the cached [netmap.NetworkMap] value stored in c, if one is available. +// It reports [ErrCacheNotAvailable] if no cached data are available. +// On success, the Cached field of the returned network map is true. +func (c *Cache) Load(ctx context.Context) (*netmap.NetworkMap, error) { + if !buildfeatures.HasCacheNetMap { + return nil, ErrCacheNotAvailable + } + + nm := netmap.NetworkMap{Cached: true} + + // At minimum, we require that the cache contain a "self" node, or the data + // are not usable. + if self, err := c.store.Load(ctx, string(selfKey)); errors.Is(err, ErrKeyNotFound) { + return nil, ErrCacheNotAvailable + } else if err := jsonv1.Unmarshal(self, &netmapNode{Node: &nm.SelfNode}); err != nil { + return nil, err + } + c.wantKeys.Add(selfKey) + + // If we successfully recovered a SelfNode, pull out its related fields. + if s := nm.SelfNode; s.Valid() { + nm.NodeKey = s.Key() + nm.AllCaps = make(set.Set[tailcfg.NodeCapability]) + for _, c := range s.Capabilities().All() { + nm.AllCaps.Add(c) + } + for c := range s.CapMap().All() { + nm.AllCaps.Add(c) + } + } + + // Unmarshal the contents of each specified cache entry directly into the + // fields of the output. See the comment in types.go for more detail. + + if err := c.readJSON(ctx, miscKey, &netmapMisc{ + MachineKey: &nm.MachineKey, + CollectServices: &nm.CollectServices, + DisplayMessages: &nm.DisplayMessages, + TKAEnabled: &nm.TKAEnabled, + TKAHead: &nm.TKAHead, + Domain: &nm.Domain, + DomainAuditLogID: &nm.DomainAuditLogID, + }); err != nil { + return nil, err + } + + if err := c.readJSON(ctx, dnsKey, &netmapDNS{DNS: &nm.DNS}); err != nil { + return nil, err + } + if err := c.readJSON(ctx, derpMapKey, &netmapDERPMap{DERPMap: &nm.DERPMap}); err != nil { + return nil, err + } + + for key, err := range c.store.List(ctx, string(peerKeyPrefix)) { + if err != nil { + return nil, err + } + var peer tailcfg.NodeView + if err := c.readJSON(ctx, cacheKey(key), &netmapNode{Node: &peer}); err != nil { + return nil, err + } + nm.Peers = append(nm.Peers, peer) + } + slices.SortFunc(nm.Peers, func(a, b tailcfg.NodeView) int { return cmp.Compare(a.ID(), b.ID()) }) + for key, err := range c.store.List(ctx, string(userKeyPrefix)) { + if err != nil { + return nil, err + } + var up tailcfg.UserProfileView + if err := c.readJSON(ctx, cacheKey(key), &netmapUserProfile{UserProfile: &up}); err != nil { + return nil, err + } + mak.Set(&nm.UserProfiles, up.ID(), up) + } + if err := c.readJSON(ctx, sshPolicyKey, &netmapSSH{SSHPolicy: &nm.SSHPolicy}); err != nil { + return nil, err + } + if err := c.readJSON(ctx, packetFilterKey, &netmapPacketFilter{Rules: &nm.PacketFilterRules}); err != nil { + return nil, err + } else if r := nm.PacketFilterRules; r.Len() != 0 { + // Reconstitute packet match expressions from the filter rules, + nm.PacketFilter, err = filter.MatchesFromFilterRules(r.AsSlice()) + if err != nil { + return nil, err + } + } + + return &nm, nil +} + +func (c *Cache) readJSON(ctx context.Context, key cacheKey, value any) error { + data, err := c.store.Load(ctx, string(key)) + if errors.Is(err, ErrKeyNotFound) { + return nil + } else if err != nil { + return err + } + if err := jsonv1.Unmarshal(data, value); err != nil { + return err + } + c.wantKeys.Add(key) + c.lastWrote[key] = lastWrote{digest: cacheDigest(data), at: time.Now()} + return nil +} + +// Store is the interface to persistent key-value storage used by a [Cache]. +type Store interface { + // List lists all the stored keys having the specified prefixes, in + // lexicographic order. + // + // Each pair yielded by the iterator is either a valid storage key and a nil + // error, or an empty key and a non-nil error. After reporting an error, the + // iterator must immediately return. + List(ctx context.Context, prefix string) iter.Seq2[string, error] + + // Load fetches the contents of the specified key. + // If the key is not found in the store, Load must report [ErrKeyNotFound]. + Load(ctx context.Context, key string) ([]byte, error) + + // Store marshals and stores the contents of the specified value under key. + // If the key already exists, its contents are replaced. + Store(ctx context.Context, key string, value []byte) error + + // Remove removes the specified key from the store. If the key does not exist, + // Remove reports success (nil). + Remove(ctx context.Context, key string) error +} + +// cacheDigest computes a string digest of the specified data, for use in +// detecting cache hits. +func cacheDigest(data []byte) string { h := sha256.Sum256(data); return string(h[:]) } diff --git a/ipn/ipnlocal/netmapcache/netmapcache_test.go b/ipn/ipnlocal/netmapcache/netmapcache_test.go new file mode 100644 index 0000000000000..b5a46d2982a04 --- /dev/null +++ b/ipn/ipnlocal/netmapcache/netmapcache_test.go @@ -0,0 +1,418 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package netmapcache_test + +import ( + "context" + jsonv1 "encoding/json" + "errors" + "flag" + "fmt" + "iter" + "maps" + "net/netip" + "os" + "reflect" + "slices" + "strings" + "testing" + + "github.com/creachadair/mds/mtest" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "tailscale.com/ipn/ipnlocal/netmapcache" + "tailscale.com/tailcfg" + "tailscale.com/tka" + "tailscale.com/types/ipproto" + "tailscale.com/types/key" + "tailscale.com/types/netmap" + "tailscale.com/types/views" + "tailscale.com/util/set" + "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/filter/filtertype" +) + +// Input values for valid-looking placeholder values for keys, hashes, etc. +const ( + testNodeKeyString = "nodekey:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + testMachineKeyString = "mkey:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210" + testAUMHashString = "APPLEPEARPLUMCHERRYAPPLEPEARPLUMCHERRYAPPLEPEARPLUMA" // base32, no padding +) + +var keepTestOutput = flag.String("keep-output", "", "directory to keep test output (if empty, use a test temp)") + +var ( + testNode1 = (&tailcfg.Node{ + ID: 99001, + StableID: "n99001FAKE", + Name: "test1.example.com.", + }).View() + testNode2 = (&tailcfg.Node{ + ID: 99002, + StableID: "n99002FAKE", + Name: "test2.example.com.", + }).View() + + // The following fields are set in init. + testNodeKey key.NodePublic + testMachineKey key.MachinePublic + testAUMHash tka.AUMHash + testMap *netmap.NetworkMap +) + +func init() { + if err := testNodeKey.UnmarshalText([]byte(testNodeKeyString)); err != nil { + panic(fmt.Sprintf("invalid test nodekey %q: %v", testNodeKeyString, err)) + } + if err := testMachineKey.UnmarshalText([]byte(testMachineKeyString)); err != nil { + panic(fmt.Sprintf("invalid test machine key %q: %v", testMachineKeyString, err)) + } + if err := testAUMHash.UnmarshalText([]byte(testAUMHashString)); err != nil { + panic(fmt.Sprintf("invalid test AUM hash %q: %v", testAUMHashString, err)) + } + + pfRules := []tailcfg.FilterRule{ + { + SrcIPs: []string{"192.168.0.0/16"}, + DstPorts: []tailcfg.NetPortRange{{ + IP: "*", + Ports: tailcfg.PortRange{First: 2000, Last: 9999}, + }}, + IPProto: []int{1, 6, 17}, // ICMPv4, TCP, UDP + CapGrant: []tailcfg.CapGrant{{ + Dsts: []netip.Prefix{netip.MustParsePrefix("192.168.4.0/24")}, + CapMap: tailcfg.PeerCapMap{ + "tailscale.com/testcap": []tailcfg.RawMessage{`"apple"`, `"pear"`}, + }, + }}, + }, + } + pfMatch, err := filter.MatchesFromFilterRules(pfRules) + if err != nil { + panic(fmt.Sprintf("invalid packet filter rules: %v", err)) + } + + // The following network map must have a non-zero non-empty value for every + // field that is to be stored in the cache. The test checks for this using + // reflection, as a way to ensure that new fields added to the type are + // covered by a test (see checkFieldCoverage). + // + // The exact values are unimportant, except that they should be values that + // give us confidence that a network map round-tripped through the cache and + // compared will accurately reflect the information we care about. + testMap = &netmap.NetworkMap{ + Cached: false, // not cached, this is metadata for the cache machinery + + // These two fields must contain compatible data. + PacketFilterRules: views.SliceOf(pfRules), + PacketFilter: pfMatch, + + // Fields stored under the "self" key. + // Note that SelfNode must have a valid user in order to be considered + // cacheable. Moreover, it must mention all the capabilities we expect + // to see advertised in the AllCaps set, and its public key must match the + // one advertised in the NodeKey field. + SelfNode: (&tailcfg.Node{ + ID: 12345, + StableID: "n12345FAKE", + User: 30337, + Name: "test.example.com.", + Key: testNodeKey, + Capabilities: []tailcfg.NodeCapability{"cap1"}, + CapMap: map[tailcfg.NodeCapability][]tailcfg.RawMessage{ + "cap2": nil, + }, + }).View(), + AllCaps: set.Of[tailcfg.NodeCapability]("cap1", "cap2"), + NodeKey: testNodeKey, + + DNS: tailcfg.DNSConfig{Domains: []string{"example1.com", "example2.ac.uk"}}, // "dns" + + SSHPolicy: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{{ // "ssh" + SSHUsers: map[string]string{"amelie": "ubuntu"}, + Action: &tailcfg.SSHAction{Message: "hello", Accept: true}, + AcceptEnv: []string{"MAGIC_SSH_*"}, + }}}, + + DERPMap: &tailcfg.DERPMap{ // "derp" + HomeParams: &tailcfg.DERPHomeParams{ + RegionScore: map[int]float64{10: 0.31, 20: 0.141, 30: 0.592}, + }, + OmitDefaultRegions: true, + }, + + // Peers stored under "peer-" keys. + Peers: []tailcfg.NodeView{testNode1, testNode2}, + + // Profiles stored under "user-" keys. + UserProfiles: map[tailcfg.UserID]tailcfg.UserProfileView{ + 12345: (&tailcfg.UserProfile{ID: 12345, DisplayName: "me"}).View(), + 67890: (&tailcfg.UserProfile{ID: 67890, DisplayName: "you"}).View(), + }, + + // Fields stored under "misc" + MachineKey: testMachineKey, + CollectServices: true, + DisplayMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{ + "test-message-1": {Title: "hello", Text: "this is your wakeup call"}, + "test-message-2": {Title: "goodbye", Text: "good night", ImpactsConnectivity: true}, + }, + TKAEnabled: true, + TKAHead: testAUMHash, + Domain: "example.com", + DomainAuditLogID: "0f1e2d3c4b5a67890f1e2d3c4b5a67890f1e2d3c4b5a67890f1e2d3c4b5a6789", + } +} + +func TestNewStore(t *testing.T) { + mtest.MustPanicf(t, func() { netmapcache.NewCache(nil) }, "NewCache should panic for a nil store") +} + +func TestRoundTrip(t *testing.T) { + checkFieldCoverage(t, testMap) + + dir := *keepTestOutput + if dir == "" { + dir = t.TempDir() + } else if err := os.MkdirAll(dir, 0700); err != nil { + t.Fatalf("Create --keep-output directory: %v", err) + } + + tests := []struct { + name string + store netmapcache.Store + }{ + {"MemStore", make(testStore)}, + {"FileStore", netmapcache.FileStore(dir)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := netmapcache.NewCache(tt.store) + if err := c.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store netmap failed; %v", err) + } + + cmap, err := c.Load(t.Context()) + if err != nil { + t.Fatalf("Load netmap failed: %v", err) + } + + if !cmap.Cached { + t.Error("Cached map is not marked as such") + } + + if diff := diffNetMaps(cmap, testMap); diff != "" { + t.Fatalf("Cached map differs (-got, +want):\n%s", diff) + } + + }) + } + + t.Run("Twice", func(t *testing.T) { + // Verify that storing the same network map twice results in no change. + + s := make(testStore) + c := netmapcache.NewCache(s) + if err := c.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store 1 netmap failed: %v", err) + } + scp := maps.Clone(s) // for comparison, see below + + if err := c.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store 2 netmap failed; %v", err) + } + if diff := cmp.Diff(s, scp); diff != "" { + t.Errorf("Updated store (-got, +want):\n%s", diff) + } + }) +} + +func TestInvalidCache(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + c := netmapcache.NewCache(make(testStore)) + got, err := c.Load(t.Context()) + if !errors.Is(err, netmapcache.ErrCacheNotAvailable) { + t.Errorf("Load from empty cache: got %+v, %v; want nil, %v", got, err, netmapcache.ErrCacheNotAvailable) + } + }) + + t.Run("Incomplete", func(t *testing.T) { + s := make(testStore) + c := netmapcache.NewCache(s) + + if err := c.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store initial netmap: %v", err) + } + + // Drop the "self" node from the cache, and verify it makes the results + // unloadable. + if err := s.Remove(t.Context(), "self"); err != nil { + t.Fatalf("Remove self: %v", err) + } + + got, err := c.Load(t.Context()) + if !errors.Is(err, netmapcache.ErrCacheNotAvailable) { + t.Errorf("Load from invalid cache: got %+v, %v; want nil, %v", got, err, netmapcache.ErrCacheNotAvailable) + } + }) +} + +// skippedMapFields are the names of fields that should not be considered by +// network map caching, and thus skipped when comparing test results. +var skippedMapFields = []string{ + "Cached", +} + +// checkFieldCoverage logs an error in t if any of the fields of nm are zero +// valued, except those listed in skippedMapFields. +// +// This ensures if any new fields are added to the [netmap.NetworkMap] type in +// the future, the test will fail until non-trivial test data are added to this +// test, or the fields are recorded as skipped. It also helps ensure that +// changing the field types or deleting fields will make compilation fail, so +// the tests get updated. +func checkFieldCoverage(t *testing.T, nm *netmap.NetworkMap) { + t.Helper() + + mt := reflect.TypeOf(nm).Elem() + mv := reflect.ValueOf(nm).Elem() + for i := 0; i < mt.NumField(); i++ { + f := mt.Field(i) + if slices.Contains(skippedMapFields, f.Name) { + continue + } + fv := mv.Field(i) + if fv.IsZero() { + t.Errorf("Field %d (%q) of test value is zero (%+v). "+ + "A non-zero value is required for each cached field in the test value.", + i, f.Name, fv.Interface()) + } + } + + // Verify that skip-listed fields exist on the type. FieldByName thwarts the + // linker, but it's OK in a test. + for _, skip := range skippedMapFields { + if _, ok := mt.FieldByName(skip); !ok { + t.Errorf("Skipped field %q not found on type %T. "+ + "If a field was deleted from the type, you may need to update skippedMapFields.", + skip, nm) + } + } + if t.Failed() { + t.FailNow() + } +} + +func TestPartial(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + c := netmapcache.NewCache(make(testStore)) // empty + nm, err := c.Load(t.Context()) + if !errors.Is(err, netmapcache.ErrCacheNotAvailable) { + t.Errorf("Load empty cache: got %+v, %v; want %v", nm, err, netmapcache.ErrCacheNotAvailable) + } + }) + + t.Run("SelfOnly", func(t *testing.T) { + self := (&tailcfg.Node{ + ID: 24680, + StableID: "u24680FAKE", + User: 6174, + Name: "test.example.com.", + Key: testNodeKey, + }).View() + + // A cached netmap must at least have a self node to be loaded without error, + // but other parts can be omitted without error. + // + // Set up a cache store with only the self node populated, and verify we + // can load that back into something with the right shape. + data, err := jsonv1.Marshal(struct { + Node tailcfg.NodeView + }{Node: self}) + if err != nil { + t.Fatalf("Marshal test node: %v", err) + } + + s := netmapcache.FileStore(t.TempDir()) + if err := s.Store(t.Context(), "self", data); err != nil { + t.Fatalf("Write test cache: %v", err) + } + + c := netmapcache.NewCache(s) + got, err := c.Load(t.Context()) + if err != nil { + t.Fatalf("Load cached netmap: %v", err) + } + if diff := diffNetMaps(got, &netmap.NetworkMap{ + Cached: true, // because we loaded it + SelfNode: self, // what we originally stored + NodeKey: testNodeKey, // the self-related field is populated + }); diff != "" { + t.Errorf("Cached map differs (-got, +want):\n%s", diff) + } + }) +} + +// testStore is an in-memory implementation of the [netmapcache.Store] interface. +type testStore map[string][]byte + +func (t testStore) List(_ context.Context, prefix string) iter.Seq2[string, error] { + var matching []string + for key := range t { + if strings.HasPrefix(key, prefix) { + matching = append(matching, key) + } + } + slices.Sort(matching) + return func(yield func(string, error) bool) { + for _, key := range matching { + if !yield(key, nil) { + return + } + } + } +} + +func (t testStore) Load(_ context.Context, key string) ([]byte, error) { + val, ok := t[key] + if !ok { + return nil, netmapcache.ErrKeyNotFound + } + return val, nil +} + +func (t testStore) Store(_ context.Context, key string, value []byte) error { + t[key] = value + return nil +} + +func (t testStore) Remove(_ context.Context, key string) error { delete(t, key); return nil } + +func diffNetMaps(got, want *netmap.NetworkMap) string { + return cmp.Diff(got, want, + cmpopts.IgnoreFields(netmap.NetworkMap{}, skippedMapFields...), + cmpopts.IgnoreFields(filtertype.Match{}, "SrcsContains"), // function pointer + cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}, netip.Prefix{}), + cmp.Comparer(eqViewsSlice(eqFilterRules)), + cmp.Comparer(eqViewsSlice(func(a, b ipproto.Proto) bool { return a == b })), + ) +} + +func eqViewsSlice[T any](eqVal func(x, y T) bool) func(a, b views.Slice[T]) bool { + return func(a, b views.Slice[T]) bool { + if a.Len() != b.Len() { + return false + } + for i := range a.Len() { + if !eqVal(a.At(i), b.At(i)) { + return false + } + } + return true + } +} + +func eqFilterRules(a, b tailcfg.FilterRule) bool { + return cmp.Equal(a, b, cmpopts.EquateComparable(netip.Prefix{})) +} diff --git a/ipn/ipnlocal/netmapcache/types.go b/ipn/ipnlocal/netmapcache/types.go new file mode 100644 index 0000000000000..c9f9efc1e3b21 --- /dev/null +++ b/ipn/ipnlocal/netmapcache/types.go @@ -0,0 +1,59 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package netmapcache + +import ( + "tailscale.com/tailcfg" + "tailscale.com/tka" + "tailscale.com/types/key" + "tailscale.com/types/views" +) + +// The fields in the following wrapper types are all pointers, even when their +// target type is also a pointer, so that they can be used to unmarshal +// directly into the fields of another value. These wrappers intentionally do +// not omit zero or empty values, since we want the cache to reflect the value +// the object had at the time it was written, even if the default changes +// later. +// +// Moreover, these are all struct types so that each cached record will be a +// JSON object even if the underlying value marshals to an array or primitive +// type, and so that we have a seam if we want to replace or version the cached +// representation separately from the default JSON layout. + +type netmapMisc struct { + MachineKey *key.MachinePublic + CollectServices *bool + DisplayMessages *map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage + TKAEnabled *bool + TKAHead *tka.AUMHash + Domain *string + DomainAuditLogID *string +} + +type netmapSSH struct { + SSHPolicy **tailcfg.SSHPolicy +} + +type netmapDNS struct { + DNS *tailcfg.DNSConfig +} + +type netmapDERPMap struct { + DERPMap **tailcfg.DERPMap +} + +type netmapNode struct { + Node *tailcfg.NodeView +} + +type netmapUserProfile struct { + UserProfile *tailcfg.UserProfileView +} + +type netmapPacketFilter struct { + Rules *views.Slice[tailcfg.FilterRule] + + // Match expressions are derived from the rules. +} diff --git a/ipn/ipnlocal/netstack.go b/ipn/ipnlocal/netstack.go index f7ffd03058879..b331d93e329de 100644 --- a/ipn/ipnlocal/netstack.go +++ b/ipn/ipnlocal/netstack.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netstack diff --git a/ipn/ipnlocal/network-lock.go b/ipn/ipnlocal/network-lock.go index 246b26409b2b5..242fec0287c65 100644 --- a/ipn/ipnlocal/network-lock.go +++ b/ipn/ipnlocal/network-lock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/ipn/ipnlocal/network-lock_test.go b/ipn/ipnlocal/network-lock_test.go index e5df38bdb6d76..8aa0a877b8dd3 100644 --- a/ipn/ipnlocal/network-lock_test.go +++ b/ipn/ipnlocal/network-lock_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go index efef57ea492e7..b70d71cb934f2 100644 --- a/ipn/ipnlocal/node_backend.go +++ b/ipn/ipnlocal/node_backend.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal @@ -6,12 +6,14 @@ package ipnlocal import ( "cmp" "context" + "fmt" "net/netip" "slices" "sync" "sync/atomic" "go4.org/netipx" + "tailscale.com/appc" "tailscale.com/feature/buildfeatures" "tailscale.com/ipn" "tailscale.com/net/dns" @@ -29,7 +31,6 @@ import ( "tailscale.com/util/mak" "tailscale.com/util/slicesx" "tailscale.com/wgengine/filter" - "tailscale.com/wgengine/magicsock" ) // nodeBackend is node-specific [LocalBackend] state. It is usually the current node. @@ -77,9 +78,6 @@ type nodeBackend struct { // initialized once and immutable eventClient *eventbus.Client - filterPub *eventbus.Publisher[magicsock.FilterUpdate] - nodeViewsPub *eventbus.Publisher[magicsock.NodeViewsUpdate] - nodeMutsPub *eventbus.Publisher[magicsock.NodeMutationsUpdate] derpMapViewPub *eventbus.Publisher[tailcfg.DERPMapView] // TODO(nickkhyl): maybe use sync.RWMutex? @@ -120,11 +118,7 @@ func newNodeBackend(ctx context.Context, logf logger.Logf, bus *eventbus.Bus) *n // Default filter blocks everything and logs nothing. noneFilter := filter.NewAllowNone(logger.Discard, &netipx.IPSet{}) nb.filterAtomic.Store(noneFilter) - nb.filterPub = eventbus.Publish[magicsock.FilterUpdate](nb.eventClient) - nb.nodeViewsPub = eventbus.Publish[magicsock.NodeViewsUpdate](nb.eventClient) - nb.nodeMutsPub = eventbus.Publish[magicsock.NodeMutationsUpdate](nb.eventClient) nb.derpMapViewPub = eventbus.Publish[tailcfg.DERPMapView](nb.eventClient) - nb.filterPub.Publish(magicsock.FilterUpdate{Filter: nb.filterAtomic.Load()}) return nb } @@ -434,15 +428,11 @@ func (nb *nodeBackend) SetNetMap(nm *netmap.NetworkMap) { nb.netMap = nm nb.updateNodeByAddrLocked() nb.updatePeersLocked() - nv := magicsock.NodeViewsUpdate{} if nm != nil { - nv.SelfNode = nm.SelfNode - nv.Peers = nm.Peers nb.derpMapViewPub.Publish(nm.DERPMap.View()) } else { nb.derpMapViewPub.Publish(tailcfg.DERPMapView{}) } - nb.nodeViewsPub.Publish(nv) } func (nb *nodeBackend) updateNodeByAddrLocked() { @@ -518,9 +508,6 @@ func (nb *nodeBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo // call (e.g. its endpoints + online status both change) var mutableNodes map[tailcfg.NodeID]*tailcfg.Node - update := magicsock.NodeMutationsUpdate{ - Mutations: make([]netmap.NodeMutation, 0, len(muts)), - } for _, m := range muts { n, ok := mutableNodes[m.NodeIDBeingMutated()] if !ok { @@ -531,14 +518,12 @@ func (nb *nodeBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo } n = nv.AsStruct() mak.Set(&mutableNodes, nv.ID(), n) - update.Mutations = append(update.Mutations, m) } m.Apply(n) } for nid, n := range mutableNodes { nb.peers[nid] = n.View() } - nb.nodeMutsPub.Publish(update) return true } @@ -560,7 +545,6 @@ func (nb *nodeBackend) filter() *filter.Filter { func (nb *nodeBackend) setFilter(f *filter.Filter) { nb.filterAtomic.Store(f) - nb.filterPub.Publish(magicsock.FilterUpdate{Filter: f}) } func (nb *nodeBackend) dnsConfigForNetmap(prefs ipn.PrefsView, selfExpired bool, versionOS string) *dns.Config { @@ -694,8 +678,9 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. } dcfg := &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: map[dnsname.FQDN][]netip.Addr{}, + AcceptDNS: prefs.CorpDNS(), + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: map[dnsname.FQDN][]netip.Addr{}, } // selfV6Only is whether we only have IPv6 addresses ourselves. @@ -749,8 +734,20 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. dcfg.Hosts[fqdn] = ips } set(nm.SelfName(), nm.GetAddresses()) + if nm.AllCaps.Contains(tailcfg.NodeAttrDNSSubdomainResolve) { + if fqdn, err := dnsname.ToFQDN(nm.SelfName()); err == nil { + dcfg.SubdomainHosts.Make() + dcfg.SubdomainHosts.Add(fqdn) + } + } for _, peer := range peers { set(peer.Name(), peer.Addresses()) + if peer.CapMap().Contains(tailcfg.NodeAttrDNSSubdomainResolve) { + if fqdn, err := dnsname.ToFQDN(peer.Name()); err == nil { + dcfg.SubdomainHosts.Make() + dcfg.SubdomainHosts.Add(fqdn) + } + } } for _, rec := range nm.DNS.ExtraRecords { switch rec.Type { @@ -842,6 +839,25 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. // Add split DNS routes, with no regard to exit node configuration. addSplitDNSRoutes(nm.DNS.Routes) + // Add split DNS routes for conn25 + conn25DNSTargets := appc.PickSplitDNSPeers(nm.HasCap, nm.SelfNode, peers) + if conn25DNSTargets != nil { + var m map[string][]*dnstype.Resolver + for domain, candidateSplitDNSPeers := range conn25DNSTargets { + for _, peer := range candidateSplitDNSPeers { + base := peerAPIBase(nm, peer) + if base == "" { + continue + } + mak.Set(&m, domain, []*dnstype.Resolver{{Addr: fmt.Sprintf("%s/dns-query", base)}}) + break // Just make one resolver for the first peer we can get a peerAPIBase for. + } + } + if m != nil { + addSplitDNSRoutes(m) + } + } + // Set FallbackResolvers as the default resolvers in the // scenarios that can't handle a purely split-DNS config. See // https://github.com/tailscale/tailscale/issues/1743 for diff --git a/ipn/ipnlocal/node_backend_test.go b/ipn/ipnlocal/node_backend_test.go index f6698bd4bc920..f1f38dae6aee1 100644 --- a/ipn/ipnlocal/node_backend_test.go +++ b/ipn/ipnlocal/node_backend_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index 318d9bf6bb72f..aa4c1ef527c6c 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/peerapi_drive.go b/ipn/ipnlocal/peerapi_drive.go index 8dffacd9a2513..d42843577059b 100644 --- a/ipn/ipnlocal/peerapi_drive.go +++ b/ipn/ipnlocal/peerapi_drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/ipn/ipnlocal/peerapi_macios_ext.go b/ipn/ipnlocal/peerapi_macios_ext.go index f23b877bd663c..70c1fb850e249 100644 --- a/ipn/ipnlocal/peerapi_macios_ext.go +++ b/ipn/ipnlocal/peerapi_macios_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_macext && (darwin || ios) diff --git a/ipn/ipnlocal/peerapi_test.go b/ipn/ipnlocal/peerapi_test.go index 3c9f57f1fcf6a..63abf089c2abc 100644 --- a/ipn/ipnlocal/peerapi_test.go +++ b/ipn/ipnlocal/peerapi_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/prefs_metrics.go b/ipn/ipnlocal/prefs_metrics.go index 34c5f5504fac4..7e7a2a5e3a1d0 100644 --- a/ipn/ipnlocal/prefs_metrics.go +++ b/ipn/ipnlocal/prefs_metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/profiles.go b/ipn/ipnlocal/profiles.go index 7080e3c3edd50..430fa63152a77 100644 --- a/ipn/ipnlocal/profiles.go +++ b/ipn/ipnlocal/profiles.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/profiles_notwindows.go b/ipn/ipnlocal/profiles_notwindows.go index 0ca8f439cf9f4..389dedc9e7015 100644 --- a/ipn/ipnlocal/profiles_notwindows.go +++ b/ipn/ipnlocal/profiles_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/ipn/ipnlocal/profiles_test.go b/ipn/ipnlocal/profiles_test.go index 6be7f0e53f59e..ec92673e50b29 100644 --- a/ipn/ipnlocal/profiles_test.go +++ b/ipn/ipnlocal/profiles_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/profiles_windows.go b/ipn/ipnlocal/profiles_windows.go index c4beb22f9d42f..a0b5bbfdd49fb 100644 --- a/ipn/ipnlocal/profiles_windows.go +++ b/ipn/ipnlocal/profiles_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index a857147e1adab..d25251accd797 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/ipn/ipnlocal/serve_disabled.go b/ipn/ipnlocal/serve_disabled.go index a97112941d844..e9d2678a80d8b 100644 --- a/ipn/ipnlocal/serve_disabled.go +++ b/ipn/ipnlocal/serve_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_serve diff --git a/ipn/ipnlocal/serve_test.go b/ipn/ipnlocal/serve_test.go index 0892545cceec8..b3f48b105c8f7 100644 --- a/ipn/ipnlocal/serve_test.go +++ b/ipn/ipnlocal/serve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/ipn/ipnlocal/serve_unix_test.go b/ipn/ipnlocal/serve_unix_test.go index e57aafab212ae..2d1f0a1e34af8 100644 --- a/ipn/ipnlocal/serve_unix_test.go +++ b/ipn/ipnlocal/serve_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/ipn/ipnlocal/ssh.go b/ipn/ipnlocal/ssh.go index e2c2f50671386..52b3066584e08 100644 --- a/ipn/ipnlocal/ssh.go +++ b/ipn/ipnlocal/ssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ((linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9) && !ts_omit_ssh diff --git a/ipn/ipnlocal/ssh_stub.go b/ipn/ipnlocal/ssh_stub.go index 6b2e36015c2d7..9a997c9143f7b 100644 --- a/ipn/ipnlocal/ssh_stub.go +++ b/ipn/ipnlocal/ssh_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_ssh || ios || android || (!linux && !darwin && !freebsd && !openbsd && !plan9) diff --git a/ipn/ipnlocal/ssh_test.go b/ipn/ipnlocal/ssh_test.go index b24cd6732f605..bb293d10ac4d6 100644 --- a/ipn/ipnlocal/ssh_test.go +++ b/ipn/ipnlocal/ssh_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || (darwin && !ios) diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index 27d53fe01b599..39796ec325367 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal @@ -1300,8 +1300,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1356,8 +1357,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node2), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node2), }, }, { @@ -1404,8 +1406,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1436,8 +1439,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node3), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node3), }, }, { @@ -1500,8 +1504,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1529,8 +1534,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1560,8 +1566,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1593,11 +1600,6 @@ func TestEngineReconfigOnStateChange(t *testing.T) { tt.steps(t, lb, cc) } - // TODO(bradfitz): this whole event bus settling thing - // should be unnecessary once the bogus uses of eventbus - // are removed. (https://github.com/tailscale/tailscale/issues/16369) - lb.settleEventBus() - if gotState := lb.State(); gotState != tt.wantState { t.Errorf("State: got %v; want %v", gotState, tt.wantState) } diff --git a/ipn/ipnlocal/tailnetlock_disabled.go b/ipn/ipnlocal/tailnetlock_disabled.go index 85cf4bd3f4ea5..0668437b163c6 100644 --- a/ipn/ipnlocal/tailnetlock_disabled.go +++ b/ipn/ipnlocal/tailnetlock_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_tailnetlock diff --git a/ipn/ipnlocal/web_client.go b/ipn/ipnlocal/web_client.go index a3c9387e46fce..37dba93d0a49b 100644 --- a/ipn/ipnlocal/web_client.go +++ b/ipn/ipnlocal/web_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !ts_omit_webclient diff --git a/ipn/ipnlocal/web_client_stub.go b/ipn/ipnlocal/web_client_stub.go index 787867b4f450e..02798b4f709e9 100644 --- a/ipn/ipnlocal/web_client_stub.go +++ b/ipn/ipnlocal/web_client_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || android || ts_omit_webclient diff --git a/ipn/ipnserver/actor.go b/ipn/ipnserver/actor.go index 628e3c37cfc0b..c9a4c6e891f86 100644 --- a/ipn/ipnserver/actor.go +++ b/ipn/ipnserver/actor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver diff --git a/ipn/ipnserver/proxyconnect.go b/ipn/ipnserver/proxyconnect.go index 7d41273bdc52a..c8348a76c2b6a 100644 --- a/ipn/ipnserver/proxyconnect.go +++ b/ipn/ipnserver/proxyconnect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js diff --git a/ipn/ipnserver/proxyconnect_js.go b/ipn/ipnserver/proxyconnect_js.go index 368221e2269c8..b4a6aef3a43bd 100644 --- a/ipn/ipnserver/proxyconnect_js.go +++ b/ipn/ipnserver/proxyconnect_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index d473252e134a8..1f8abf0e20128 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnserver runs the LocalAPI HTTP server that communicates diff --git a/ipn/ipnserver/server_fortest.go b/ipn/ipnserver/server_fortest.go index 9aab3b276d31f..70148f030e6b0 100644 --- a/ipn/ipnserver/server_fortest.go +++ b/ipn/ipnserver/server_fortest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver diff --git a/ipn/ipnserver/server_test.go b/ipn/ipnserver/server_test.go index 713db9e50085e..9aa9c4c015f23 100644 --- a/ipn/ipnserver/server_test.go +++ b/ipn/ipnserver/server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver_test diff --git a/ipn/ipnserver/waiterset_test.go b/ipn/ipnserver/waiterset_test.go index b7d5ea144c408..b8a143212c1a3 100644 --- a/ipn/ipnserver/waiterset_test.go +++ b/ipn/ipnserver/waiterset_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index 213090b559692..4d219d131d528 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnstate captures the entire state of the Tailscale network. diff --git a/ipn/ipnstate/ipnstate_clone.go b/ipn/ipnstate/ipnstate_clone.go index 20ae43c5fb73e..9af066832b27f 100644 --- a/ipn/ipnstate/ipnstate_clone.go +++ b/ipn/ipnstate/ipnstate_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/ipn/lapitest/backend.go b/ipn/lapitest/backend.go index 7a1c276a7b229..b622d098f4f55 100644 --- a/ipn/lapitest/backend.go +++ b/ipn/lapitest/backend.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lapitest diff --git a/ipn/lapitest/client.go b/ipn/lapitest/client.go index 6d22e938b210e..c2c07dfbaf689 100644 --- a/ipn/lapitest/client.go +++ b/ipn/lapitest/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lapitest diff --git a/ipn/lapitest/example_test.go b/ipn/lapitest/example_test.go index 57479199a8123..648c97880cdb8 100644 --- a/ipn/lapitest/example_test.go +++ b/ipn/lapitest/example_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lapitest diff --git a/ipn/lapitest/opts.go b/ipn/lapitest/opts.go index 6eb1594da2607..5ed2f97573b90 100644 --- a/ipn/lapitest/opts.go +++ b/ipn/lapitest/opts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lapitest diff --git a/ipn/lapitest/server.go b/ipn/lapitest/server.go index 457a338ab9f5a..8fd3c8cdd361f 100644 --- a/ipn/lapitest/server.go +++ b/ipn/lapitest/server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lapitest provides utilities for black-box testing of LocalAPI ([ipnserver]). diff --git a/ipn/localapi/cert.go b/ipn/localapi/cert.go index 2313631cc3229..cd8afa03bf599 100644 --- a/ipn/localapi/cert.go +++ b/ipn/localapi/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !js && !ts_omit_acme diff --git a/ipn/localapi/debug.go b/ipn/localapi/debug.go index ae9cb01e02fe9..d1348abaafef5 100644 --- a/ipn/localapi/debug.go +++ b/ipn/localapi/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debug @@ -6,6 +6,7 @@ package localapi import ( + "cmp" "context" "encoding/json" "fmt" @@ -35,6 +36,7 @@ func init() { Register("dev-set-state-store", (*Handler).serveDevSetStateStore) Register("debug-bus-events", (*Handler).serveDebugBusEvents) Register("debug-bus-graph", (*Handler).serveEventBusGraph) + Register("debug-bus-queues", (*Handler).serveDebugBusQueues) Register("debug-derp-region", (*Handler).serveDebugDERPRegion) Register("debug-dial-types", (*Handler).serveDebugDialTypes) Register("debug-log", (*Handler).serveDebugLog) @@ -424,6 +426,62 @@ func (h *Handler) serveEventBusGraph(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(topics) } +func (h *Handler) serveDebugBusQueues(w http.ResponseWriter, r *http.Request) { + if r.Method != httpm.GET { + http.Error(w, "GET required", http.StatusMethodNotAllowed) + return + } + + bus, ok := h.LocalBackend().Sys().Bus.GetOK() + if !ok { + http.Error(w, "event bus not running", http.StatusPreconditionFailed) + return + } + + debugger := bus.Debugger() + + type clientQueue struct { + Name string `json:"name"` + SubscribeDepth int `json:"subscribeDepth"` + SubscribeTypes []string `json:"subscribeTypes,omitempty"` + PublishTypes []string `json:"publishTypes,omitempty"` + } + + publishQueue := debugger.PublishQueue() + clients := debugger.Clients() + result := struct { + PublishQueueDepth int `json:"publishQueueDepth"` + Clients []clientQueue `json:"clients"` + }{ + PublishQueueDepth: len(publishQueue), + } + + for _, c := range clients { + sq := debugger.SubscribeQueue(c) + cq := clientQueue{ + Name: c.Name(), + SubscribeDepth: len(sq), + } + for _, t := range debugger.SubscribeTypes(c) { + cq.SubscribeTypes = append(cq.SubscribeTypes, t.String()) + } + for _, t := range debugger.PublishTypes(c) { + cq.PublishTypes = append(cq.PublishTypes, t.String()) + } + result.Clients = append(result.Clients, cq) + } + + slices.SortFunc(result.Clients, func(a, b clientQueue) int { + if a.SubscribeDepth != b.SubscribeDepth { + return b.SubscribeDepth - a.SubscribeDepth + } + return cmp.Compare(a.Name, b.Name) + }) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} + func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) { if !buildfeatures.HasLogTail { http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented) diff --git a/ipn/localapi/debugderp.go b/ipn/localapi/debugderp.go index 3edbc0856c8a3..52987ee0a18e6 100644 --- a/ipn/localapi/debugderp.go +++ b/ipn/localapi/debugderp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debug diff --git a/ipn/localapi/disabled_stubs.go b/ipn/localapi/disabled_stubs.go index c744f34d5f5c5..0d16de880cf41 100644 --- a/ipn/localapi/disabled_stubs.go +++ b/ipn/localapi/disabled_stubs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || android || js diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 4648b2c49e849..ed25e875da409 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package localapi contains the HTTP server handlers for tailscaled's API server. @@ -28,13 +28,13 @@ import ( "tailscale.com/envknob" "tailscale.com/feature" "tailscale.com/feature/buildfeatures" - "tailscale.com/health/healthmsg" "tailscale.com/hostinfo" "tailscale.com/ipn" "tailscale.com/ipn/ipnauth" "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnstate" "tailscale.com/logtail" + "tailscale.com/net/neterror" "tailscale.com/net/netns" "tailscale.com/net/netutil" "tailscale.com/tailcfg" @@ -99,9 +99,6 @@ func init() { Register("check-udp-gro-forwarding", (*Handler).serveCheckUDPGROForwarding) Register("set-udp-gro-forwarding", (*Handler).serveSetUDPGROForwarding) } - if buildfeatures.HasUseExitNode && runtime.GOOS == "linux" { - Register("check-reverse-path-filtering", (*Handler).serveCheckReversePathFiltering) - } if buildfeatures.HasClientMetrics { Register("upload-client-metrics", (*Handler).serveUploadClientMetrics) } @@ -779,32 +776,6 @@ func (h *Handler) serveCheckSOMarkInUse(w http.ResponseWriter, r *http.Request) }) } -func (h *Handler) serveCheckReversePathFiltering(w http.ResponseWriter, r *http.Request) { - if !h.PermitRead { - http.Error(w, "reverse path filtering check access denied", http.StatusForbidden) - return - } - var warning string - - state := h.b.Sys().NetMon.Get().InterfaceState() - warn, err := netutil.CheckReversePathFiltering(state) - if err == nil && len(warn) > 0 { - var msg strings.Builder - msg.WriteString(healthmsg.WarnExitNodeUsage + ":\n") - for _, w := range warn { - msg.WriteString("- " + w + "\n") - } - msg.WriteString(healthmsg.DisableRPFilter) - warning = msg.String() - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(struct { - Warning string - }{ - Warning: warning, - }) -} - func (h *Handler) serveCheckUDPGROForwarding(w http.ResponseWriter, r *http.Request) { if !h.PermitRead { http.Error(w, "UDP GRO forwarding check access denied", http.StatusForbidden) @@ -913,7 +884,9 @@ func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) { h.b.WatchNotificationsAs(ctx, h.Actor, mask, f.Flush, func(roNotify *ipn.Notify) (keepGoing bool) { err := enc.Encode(roNotify) if err != nil { - h.logf("json.Encode: %v", err) + if !neterror.IsClosedPipeError(err) { + h.logf("json.Encode: %v", err) + } return false } f.Flush() diff --git a/ipn/localapi/localapi_drive.go b/ipn/localapi/localapi_drive.go index eb765ec2eabba..e1dee441e0fde 100644 --- a/ipn/localapi/localapi_drive.go +++ b/ipn/localapi/localapi_drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/ipn/localapi/localapi_test.go b/ipn/localapi/localapi_test.go index 5d228ffd69343..47e33457188ab 100644 --- a/ipn/localapi/localapi_test.go +++ b/ipn/localapi/localapi_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package localapi diff --git a/ipn/localapi/pprof.go b/ipn/localapi/pprof.go index 9476f721fb1ce..fabdb18e24d87 100644 --- a/ipn/localapi/pprof.go +++ b/ipn/localapi/pprof.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !js && !ts_omit_debug diff --git a/ipn/localapi/serve.go b/ipn/localapi/serve.go index efbbde06ff954..1f677f7ab3a05 100644 --- a/ipn/localapi/serve.go +++ b/ipn/localapi/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/ipn/localapi/syspolicy_api.go b/ipn/localapi/syspolicy_api.go index edb82e042f2ce..9962f342bd884 100644 --- a/ipn/localapi/syspolicy_api.go +++ b/ipn/localapi/syspolicy_api.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/ipn/localapi/tailnetlock.go b/ipn/localapi/tailnetlock.go index e5f999bb8847e..445f705056cf7 100644 --- a/ipn/localapi/tailnetlock.go +++ b/ipn/localapi/tailnetlock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/ipn/policy/policy.go b/ipn/policy/policy.go index 494a0dc408819..bbc78a254e141 100644 --- a/ipn/policy/policy.go +++ b/ipn/policy/policy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package policy contains various policy decisions that need to be diff --git a/ipn/prefs.go b/ipn/prefs.go index 9f98465d2d883..72e0cf8b78424 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index aa152843a5af9..347a91e50739c 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/serve.go b/ipn/serve.go index 240308f290edc..911b408b65026 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/serve_expand_test.go b/ipn/serve_expand_test.go index b977238fe32ff..33f808d62ec05 100644 --- a/ipn/serve_expand_test.go +++ b/ipn/serve_expand_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/serve_test.go b/ipn/serve_test.go index 5e0f4a43a38e7..8be39a1ed81ce 100644 --- a/ipn/serve_test.go +++ b/ipn/serve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/store.go b/ipn/store.go index 2034ae09a92f9..1bd3e5a3b4e6b 100644 --- a/ipn/store.go +++ b/ipn/store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/store/awsstore/store_aws.go b/ipn/store/awsstore/store_aws.go index 78b72d0bc8f45..e06e00eb3d3dd 100644 --- a/ipn/store/awsstore/store_aws.go +++ b/ipn/store/awsstore/store_aws.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_aws diff --git a/ipn/store/awsstore/store_aws_test.go b/ipn/store/awsstore/store_aws_test.go index 3cc23e48d4b12..ba2274bf1c09e 100644 --- a/ipn/store/awsstore/store_aws_test.go +++ b/ipn/store/awsstore/store_aws_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_aws diff --git a/ipn/store/kubestore/store_kube.go b/ipn/store/kubestore/store_kube.go index 5fbd795c2174d..f7d1b90cd1e2c 100644 --- a/ipn/store/kubestore/store_kube.go +++ b/ipn/store/kubestore/store_kube.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package kubestore contains an ipn.StateStore implementation using Kubernetes Secrets. diff --git a/ipn/store/kubestore/store_kube_test.go b/ipn/store/kubestore/store_kube_test.go index aea39d3bb51f8..1e6f711d686e2 100644 --- a/ipn/store/kubestore/store_kube_test.go +++ b/ipn/store/kubestore/store_kube_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubestore diff --git a/ipn/store/mem/store_mem.go b/ipn/store/mem/store_mem.go index 6f474ce993b43..247714c9a2b47 100644 --- a/ipn/store/mem/store_mem.go +++ b/ipn/store/mem/store_mem.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mem provides an in-memory ipn.StateStore implementation. diff --git a/ipn/store/stores.go b/ipn/store/stores.go index bf175da41d8aa..fd51f8c38540d 100644 --- a/ipn/store/stores.go +++ b/ipn/store/stores.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package store provides various implementation of ipn.StateStore. diff --git a/ipn/store/stores_test.go b/ipn/store/stores_test.go index 1f0fc0fef1bff..345b1c10376d9 100644 --- a/ipn/store/stores_test.go +++ b/ipn/store/stores_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package store diff --git a/ipn/store_test.go b/ipn/store_test.go index 4dd7321b9048d..fc42fdbec3610 100644 --- a/ipn/store_test.go +++ b/ipn/store_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/jsondb/db.go b/jsondb/db.go index 68bb05af45e8e..c45ab4cd39913 100644 --- a/jsondb/db.go +++ b/jsondb/db.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsondb provides a trivial "database": a Go object saved to diff --git a/jsondb/db_test.go b/jsondb/db_test.go index 655754f38e1a9..18797ebd174e6 100644 --- a/jsondb/db_test.go +++ b/jsondb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsondb diff --git a/k8s-operator/api-docs-config.yaml b/k8s-operator/api-docs-config.yaml index 214171ca35c0d..0bfb32be92f27 100644 --- a/k8s-operator/api-docs-config.yaml +++ b/k8s-operator/api-docs-config.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause processor: {} diff --git a/k8s-operator/api-proxy/doc.go b/k8s-operator/api-proxy/doc.go index 89d8909595fd3..a685b9907d710 100644 --- a/k8s-operator/api-proxy/doc.go +++ b/k8s-operator/api-proxy/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/api-proxy/proxy_events_test.go b/k8s-operator/api-proxy/proxy_events_test.go index e35be33a0e734..1426f170c5207 100644 --- a/k8s-operator/api-proxy/proxy_events_test.go +++ b/k8s-operator/api-proxy/proxy_events_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/api-proxy/proxy_test.go b/k8s-operator/api-proxy/proxy_test.go index 14e6554236234..5d1606d764e45 100644 --- a/k8s-operator/api-proxy/proxy_test.go +++ b/k8s-operator/api-proxy/proxy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/api.md b/k8s-operator/api.md index 3a4e692d902ec..5a60f66e039d0 100644 --- a/k8s-operator/api.md +++ b/k8s-operator/api.md @@ -16,8 +16,12 @@ - [ProxyClassList](#proxyclasslist) - [ProxyGroup](#proxygroup) - [ProxyGroupList](#proxygrouplist) +- [ProxyGroupPolicy](#proxygrouppolicy) +- [ProxyGroupPolicyList](#proxygrouppolicylist) - [Recorder](#recorder) - [RecorderList](#recorderlist) +- [Tailnet](#tailnet) +- [TailnetList](#tailnetlist) @@ -139,6 +143,7 @@ _Appears in:_ | `appConnector` _[AppConnector](#appconnector)_ | AppConnector defines whether the Connector device should act as a Tailscale app connector. A Connector that is
configured as an app connector cannot be a subnet router or an exit node. If this field is unset, the
Connector does not act as an app connector.
Note that you will need to manually configure the permissions and the domains for the app connector via the
Admin panel.
Note also that the main tested and supported use case of this config option is to deploy an app connector on
Kubernetes to access SaaS applications available on the public internet. Using the app connector to expose
cluster workloads or other internal workloads to tailnet might work, but this is not a use case that we have
tested or optimised for.
If you are using the app connector to access SaaS applications because you need a predictable egress IP that
can be whitelisted, it is also your responsibility to ensure that cluster traffic from the connector flows
via that predictable IP, for example by enforcing that cluster egress traffic is routed via an egress NAT
device with a static IP address.
https://tailscale.com/kb/1281/app-connectors | | | | `exitNode` _boolean_ | ExitNode defines whether the Connector device should act as a Tailscale exit node. Defaults to false.
This field is mutually exclusive with the appConnector field.
https://tailscale.com/kb/1103/exit-nodes | | | | `replicas` _integer_ | Replicas specifies how many devices to create. Set this to enable
high availability for app connectors, subnet routers, or exit nodes.
https://tailscale.com/kb/1115/high-availability. Defaults to 1. | | Minimum: 0
| +| `tailnet` _string_ | Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this
name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. | | | #### ConnectorStatus @@ -722,6 +727,81 @@ _Appears in:_ | `items` _[ProxyGroup](#proxygroup) array_ | | | | +#### ProxyGroupPolicy + + + + + + + +_Appears in:_ +- [ProxyGroupPolicyList](#proxygrouppolicylist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `tailscale.com/v1alpha1` | | | +| `kind` _string_ | `ProxyGroupPolicy` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[ProxyGroupPolicySpec](#proxygrouppolicyspec)_ | Spec describes the desired state of the ProxyGroupPolicy.
More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status | | | +| `status` _[ProxyGroupPolicyStatus](#proxygrouppolicystatus)_ | Status describes the status of the ProxyGroupPolicy. This is set
and managed by the Tailscale operator. | | | + + +#### ProxyGroupPolicyList + + + + + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `tailscale.com/v1alpha1` | | | +| `kind` _string_ | `ProxyGroupPolicyList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `items` _[ProxyGroupPolicy](#proxygrouppolicy) array_ | | | | + + +#### ProxyGroupPolicySpec + + + + + + + +_Appears in:_ +- [ProxyGroupPolicy](#proxygrouppolicy) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `ingress` _string array_ | Names of ProxyGroup resources that can be used by Ingress resources within this namespace. An empty list
denotes that no ingress via ProxyGroups is allowed within this namespace. | | | +| `egress` _string array_ | Names of ProxyGroup resources that can be used by Service resources within this namespace. An empty list
denotes that no egress via ProxyGroups is allowed within this namespace. | | | + + +#### ProxyGroupPolicyStatus + + + + + + + +_Appears in:_ +- [ProxyGroupPolicy](#proxygrouppolicy) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#condition-v1-meta) array_ | | | | + + #### ProxyGroupSpec @@ -741,6 +821,7 @@ _Appears in:_ | `hostnamePrefix` _[HostnamePrefix](#hostnameprefix)_ | HostnamePrefix is the hostname prefix to use for tailnet devices created
by the ProxyGroup. Each device will have the integer number from its
StatefulSet pod appended to this prefix to form the full hostname.
HostnamePrefix can contain lower case letters, numbers and dashes, it
must not start with a dash and must be between 1 and 62 characters long. | | Pattern: `^[a-z0-9][a-z0-9-]{0,61}$`
Type: string
| | `proxyClass` _string_ | ProxyClass is the name of the ProxyClass custom resource that contains
configuration options that should be applied to the resources created
for this ProxyGroup. If unset, and there is no default ProxyClass
configured, the operator will create resources with the default
configuration. | | | | `kubeAPIServer` _[KubeAPIServerConfig](#kubeapiserverconfig)_ | KubeAPIServer contains configuration specific to the kube-apiserver
ProxyGroup type. This field is only used when Type is set to "kube-apiserver". | | | +| `tailnet` _string_ | Tailnet specifies the tailnet this ProxyGroup should join. If blank, the default tailnet is used. When set, this
name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. | | | #### ProxyGroupStatus @@ -900,7 +981,8 @@ _Appears in:_ | `tags` _[Tags](#tags)_ | Tags that the Tailscale device will be tagged with. Defaults to [tag:k8s].
If you specify custom tags here, make sure you also make the operator
an owner of these tags.
See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.
Tags cannot be changed once a Recorder node has been created.
Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$. | | Pattern: `^tag:[a-zA-Z][a-zA-Z0-9-]*$`
Type: string
| | `enableUI` _boolean_ | Set to true to enable the Recorder UI. The UI lists and plays recorded sessions.
The UI will be served at :443. Defaults to false.
Corresponds to --ui tsrecorder flag https://tailscale.com/kb/1246/tailscale-ssh-session-recording#deploy-a-recorder-node.
Required if S3 storage is not set up, to ensure that recordings are accessible. | | | | `storage` _[Storage](#storage)_ | Configure where to store session recordings. By default, recordings will
be stored in a local ephemeral volume, and will not be persisted past the
lifetime of a specific pod. | | | -| `replicas` _integer_ | Replicas specifies how many instances of tsrecorder to run. Defaults to 1. | | Minimum: 0
| +| `replicas` _integer_ | Replicas specifies how many instances of tsrecorder to run. Defaults to 1. | 1 | Minimum: 0
| +| `tailnet` _string_ | Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this
name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. | | | #### RecorderStatefulSet @@ -1154,6 +1236,44 @@ _Appears in:_ +#### Tailnet + + + + + + + +_Appears in:_ +- [TailnetList](#tailnetlist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `tailscale.com/v1alpha1` | | | +| `kind` _string_ | `Tailnet` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[TailnetSpec](#tailnetspec)_ | Spec describes the desired state of the Tailnet.
More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status | | | +| `status` _[TailnetStatus](#tailnetstatus)_ | Status describes the status of the Tailnet. This is set
and managed by the Tailscale operator. | | | + + +#### TailnetCredentials + + + + + + + +_Appears in:_ +- [TailnetSpec](#tailnetspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `secretName` _string_ | The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and
"client_secret". | | | + + #### TailnetDevice @@ -1172,6 +1292,59 @@ _Appears in:_ | `staticEndpoints` _string array_ | StaticEndpoints are user configured, 'static' endpoints by which tailnet peers can reach this device. | | | +#### TailnetList + + + + + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `tailscale.com/v1alpha1` | | | +| `kind` _string_ | `TailnetList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `items` _[Tailnet](#tailnet) array_ | | | | + + +#### TailnetSpec + + + + + + + +_Appears in:_ +- [Tailnet](#tailnet) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `loginUrl` _string_ | URL of the control plane to be used by all resources managed by the operator using this Tailnet. | | | +| `credentials` _[TailnetCredentials](#tailnetcredentials)_ | Denotes the location of the OAuth credentials to use for authenticating with this Tailnet. | | | + + +#### TailnetStatus + + + + + + + +_Appears in:_ +- [Tailnet](#tailnet) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#condition-v1-meta) array_ | | | | + + #### TailscaleConfig diff --git a/k8s-operator/apis/doc.go b/k8s-operator/apis/doc.go index 0a1145ca8a0dc..3fea3d6e80e12 100644 --- a/k8s-operator/apis/doc.go +++ b/k8s-operator/apis/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/doc.go b/k8s-operator/apis/v1alpha1/doc.go index 467e73e17cb21..0fc5bb8ece622 100644 --- a/k8s-operator/apis/v1alpha1/doc.go +++ b/k8s-operator/apis/v1alpha1/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/register.go b/k8s-operator/apis/v1alpha1/register.go index 0880ac975732e..125d7419866ea 100644 --- a/k8s-operator/apis/v1alpha1/register.go +++ b/k8s-operator/apis/v1alpha1/register.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -67,6 +67,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { &RecorderList{}, &ProxyGroup{}, &ProxyGroupList{}, + &Tailnet{}, + &TailnetList{}, + &ProxyGroupPolicy{}, + &ProxyGroupPolicyList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/k8s-operator/apis/v1alpha1/types_connector.go b/k8s-operator/apis/v1alpha1/types_connector.go index 58457500f6c34..af2df58af2fd9 100644 --- a/k8s-operator/apis/v1alpha1/types_connector.go +++ b/k8s-operator/apis/v1alpha1/types_connector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -133,6 +133,12 @@ type ConnectorSpec struct { // +optional // +kubebuilder:validation:Minimum=0 Replicas *int32 `json:"replicas,omitempty"` + + // Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this + // name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Connector tailnet is immutable" + Tailnet string `json:"tailnet,omitempty"` } // SubnetRouter defines subnet routes that should be exposed to tailnet via a diff --git a/k8s-operator/apis/v1alpha1/types_proxyclass.go b/k8s-operator/apis/v1alpha1/types_proxyclass.go index 670df3b95097e..3c2fe76868ae3 100644 --- a/k8s-operator/apis/v1alpha1/types_proxyclass.go +++ b/k8s-operator/apis/v1alpha1/types_proxyclass.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/types_proxygroup.go b/k8s-operator/apis/v1alpha1/types_proxygroup.go index 28fd9e00973c5..00c196628ab86 100644 --- a/k8s-operator/apis/v1alpha1/types_proxygroup.go +++ b/k8s-operator/apis/v1alpha1/types_proxygroup.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -97,6 +97,12 @@ type ProxyGroupSpec struct { // ProxyGroup type. This field is only used when Type is set to "kube-apiserver". // +optional KubeAPIServer *KubeAPIServerConfig `json:"kubeAPIServer,omitempty"` + + // Tailnet specifies the tailnet this ProxyGroup should join. If blank, the default tailnet is used. When set, this + // name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ProxyGroup tailnet is immutable" + Tailnet string `json:"tailnet,omitempty"` } type ProxyGroupStatus struct { diff --git a/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go b/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go new file mode 100644 index 0000000000000..551811693f7c8 --- /dev/null +++ b/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go @@ -0,0 +1,63 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Code comments on these types should be treated as user facing documentation- +// they will appear on the ProxyGroupPolicy CRD i.e. if someone runs kubectl explain tailnet. + +var ProxyGroupPolicyKind = "ProxyGroupPolicy" + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Namespaced,shortName=pgp +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +type ProxyGroupPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitzero"` + + // Spec describes the desired state of the ProxyGroupPolicy. + // More info: + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + Spec ProxyGroupPolicySpec `json:"spec"` + + // Status describes the status of the ProxyGroupPolicy. This is set + // and managed by the Tailscale operator. + // +optional + Status ProxyGroupPolicyStatus `json:"status"` +} + +// +kubebuilder:object:root=true + +type ProxyGroupPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []ProxyGroupPolicy `json:"items"` +} + +type ProxyGroupPolicySpec struct { + // Names of ProxyGroup resources that can be used by Ingress resources within this namespace. An empty list + // denotes that no ingress via ProxyGroups is allowed within this namespace. + // +optional + Ingress []string `json:"ingress,omitempty"` + + // Names of ProxyGroup resources that can be used by Service resources within this namespace. An empty list + // denotes that no egress via ProxyGroups is allowed within this namespace. + // +optional + Egress []string `json:"egress,omitempty"` +} + +type ProxyGroupPolicyStatus struct { + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions"` +} diff --git a/k8s-operator/apis/v1alpha1/types_recorder.go b/k8s-operator/apis/v1alpha1/types_recorder.go index 67cffbf09e969..284c3b0ae48f4 100644 --- a/k8s-operator/apis/v1alpha1/types_recorder.go +++ b/k8s-operator/apis/v1alpha1/types_recorder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -80,7 +80,14 @@ type RecorderSpec struct { // Replicas specifies how many instances of tsrecorder to run. Defaults to 1. // +optional // +kubebuilder:validation:Minimum=0 + // +kubebuilder:default=1 Replicas *int32 `json:"replicas,omitzero"` + + // Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this + // name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Recorder tailnet is immutable" + Tailnet string `json:"tailnet,omitempty"` } type RecorderStatefulSet struct { diff --git a/k8s-operator/apis/v1alpha1/types_tailnet.go b/k8s-operator/apis/v1alpha1/types_tailnet.go new file mode 100644 index 0000000000000..b11b2cf17658c --- /dev/null +++ b/k8s-operator/apis/v1alpha1/types_tailnet.go @@ -0,0 +1,69 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Code comments on these types should be treated as user facing documentation- +// they will appear on the Tailnet CRD i.e. if someone runs kubectl explain tailnet. + +var TailnetKind = "Tailnet" + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,shortName=tn +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "TailnetReady")].reason`,description="Status of the deployed Tailnet resources." + +type Tailnet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitzero"` + + // Spec describes the desired state of the Tailnet. + // More info: + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + Spec TailnetSpec `json:"spec"` + + // Status describes the status of the Tailnet. This is set + // and managed by the Tailscale operator. + // +optional + Status TailnetStatus `json:"status"` +} + +// +kubebuilder:object:root=true + +type TailnetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []Tailnet `json:"items"` +} + +type TailnetSpec struct { + // URL of the control plane to be used by all resources managed by the operator using this Tailnet. + // +optional + LoginURL string `json:"loginUrl,omitempty"` + // Denotes the location of the OAuth credentials to use for authenticating with this Tailnet. + Credentials TailnetCredentials `json:"credentials"` +} + +type TailnetCredentials struct { + // The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and + // "client_secret". + SecretName string `json:"secretName"` +} + +type TailnetStatus struct { + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions"` +} + +// TailnetReady is set to True if the Tailnet is available for use by operator workloads. +const TailnetReady ConditionType = `TailnetReady` diff --git a/k8s-operator/apis/v1alpha1/types_tsdnsconfig.go b/k8s-operator/apis/v1alpha1/types_tsdnsconfig.go index 7991003b82dff..c1a2e7906fcd8 100644 --- a/k8s-operator/apis/v1alpha1/types_tsdnsconfig.go +++ b/k8s-operator/apis/v1alpha1/types_tsdnsconfig.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go index ff0f3f6ace415..2528c89f364d6 100644 --- a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go +++ b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go @@ -1,6 +1,6 @@ //go:build !ignore_autogenerated && !plan9 -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by controller-gen. DO NOT EDIT. @@ -832,6 +832,112 @@ func (in *ProxyGroupList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyGroupPolicy) DeepCopyInto(out *ProxyGroupPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyGroupPolicy. +func (in *ProxyGroupPolicy) DeepCopy() *ProxyGroupPolicy { + if in == nil { + return nil + } + out := new(ProxyGroupPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProxyGroupPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyGroupPolicyList) DeepCopyInto(out *ProxyGroupPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ProxyGroupPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyGroupPolicyList. +func (in *ProxyGroupPolicyList) DeepCopy() *ProxyGroupPolicyList { + if in == nil { + return nil + } + out := new(ProxyGroupPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProxyGroupPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyGroupPolicySpec) DeepCopyInto(out *ProxyGroupPolicySpec) { + *out = *in + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Egress != nil { + in, out := &in.Egress, &out.Egress + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyGroupPolicySpec. +func (in *ProxyGroupPolicySpec) DeepCopy() *ProxyGroupPolicySpec { + if in == nil { + return nil + } + out := new(ProxyGroupPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyGroupPolicyStatus) DeepCopyInto(out *ProxyGroupPolicyStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyGroupPolicyStatus. +func (in *ProxyGroupPolicyStatus) DeepCopy() *ProxyGroupPolicyStatus { + if in == nil { + return nil + } + out := new(ProxyGroupPolicyStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyGroupSpec) DeepCopyInto(out *ProxyGroupSpec) { *out = *in @@ -1365,6 +1471,48 @@ func (in Tags) DeepCopy() Tags { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tailnet) DeepCopyInto(out *Tailnet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tailnet. +func (in *Tailnet) DeepCopy() *Tailnet { + if in == nil { + return nil + } + out := new(Tailnet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Tailnet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TailnetCredentials) DeepCopyInto(out *TailnetCredentials) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TailnetCredentials. +func (in *TailnetCredentials) DeepCopy() *TailnetCredentials { + if in == nil { + return nil + } + out := new(TailnetCredentials) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TailnetDevice) DeepCopyInto(out *TailnetDevice) { *out = *in @@ -1390,6 +1538,76 @@ func (in *TailnetDevice) DeepCopy() *TailnetDevice { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TailnetList) DeepCopyInto(out *TailnetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Tailnet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TailnetList. +func (in *TailnetList) DeepCopy() *TailnetList { + if in == nil { + return nil + } + out := new(TailnetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TailnetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TailnetSpec) DeepCopyInto(out *TailnetSpec) { + *out = *in + out.Credentials = in.Credentials +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TailnetSpec. +func (in *TailnetSpec) DeepCopy() *TailnetSpec { + if in == nil { + return nil + } + out := new(TailnetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TailnetStatus) DeepCopyInto(out *TailnetStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TailnetStatus. +func (in *TailnetStatus) DeepCopy() *TailnetStatus { + if in == nil { + return nil + } + out := new(TailnetStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TailscaleConfig) DeepCopyInto(out *TailscaleConfig) { *out = *in diff --git a/k8s-operator/conditions.go b/k8s-operator/conditions.go index ae465a728f0ff..89b83dd5f83cc 100644 --- a/k8s-operator/conditions.go +++ b/k8s-operator/conditions.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -13,6 +13,7 @@ import ( xslices "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/tstime" ) @@ -91,6 +92,14 @@ func SetProxyGroupCondition(pg *tsapi.ProxyGroup, conditionType tsapi.ConditionT pg.Status.Conditions = conds } +// SetTailnetCondition ensures that Tailnet status has a condition with the +// given attributes. LastTransitionTime gets set every time condition's status +// changes. +func SetTailnetCondition(tn *tsapi.Tailnet, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, clock tstime.Clock, logger *zap.SugaredLogger) { + conds := updateCondition(tn.Status.Conditions, conditionType, status, reason, message, tn.Generation, clock, logger) + tn.Status.Conditions = conds +} + func updateCondition(conds []metav1.Condition, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) []metav1.Condition { newCondition := metav1.Condition{ Type: string(conditionType), @@ -187,3 +196,14 @@ func SvcIsReady(svc *corev1.Service) bool { cond := svc.Status.Conditions[idx] return cond.Status == metav1.ConditionTrue } + +func TailnetIsReady(tn *tsapi.Tailnet) bool { + idx := xslices.IndexFunc(tn.Status.Conditions, func(cond metav1.Condition) bool { + return cond.Type == string(tsapi.TailnetReady) + }) + if idx == -1 { + return false + } + cond := tn.Status.Conditions[idx] + return cond.Status == metav1.ConditionTrue && cond.ObservedGeneration == tn.Generation +} diff --git a/k8s-operator/conditions_test.go b/k8s-operator/conditions_test.go index 7eb65257d3414..940a300d88ba8 100644 --- a/k8s-operator/conditions_test.go +++ b/k8s-operator/conditions_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy.go b/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy.go new file mode 100644 index 0000000000000..0541a5cf3691b --- /dev/null +++ b/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy.go @@ -0,0 +1,391 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +// Package proxygrouppolicy provides reconciliation logic for the ProxyGroupPolicy custom resource definition. It is +// responsible for generating ValidatingAdmissionPolicy resources that limit users to a set number of ProxyGroup +// names that can be used within Service and Ingress resources via the "tailscale.com/proxy-group" annotation. +package proxygrouppolicy + +import ( + "context" + "fmt" + "sort" + "strconv" + "strings" + + admr "k8s.io/api/admissionregistration/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/types/ptr" + "tailscale.com/util/set" +) + +type ( + // The Reconciler type is a reconcile.TypedReconciler implementation used to manage the reconciliation of + // ProxyGroupPolicy custom resources. + Reconciler struct { + client.Client + } + + // The ReconcilerOptions type contains configuration values for the Reconciler. + ReconcilerOptions struct { + // The client for interacting with the Kubernetes API. + Client client.Client + } +) + +const reconcilerName = "proxygrouppolicy-reconciler" + +// NewReconciler returns a new instance of the Reconciler type. It watches specifically for changes to ProxyGroupPolicy +// custom resources. The ReconcilerOptions can be used to modify the behaviour of the Reconciler. +func NewReconciler(options ReconcilerOptions) *Reconciler { + return &Reconciler{ + Client: options.Client, + } +} + +// Register the Reconciler onto the given manager.Manager implementation. +func (r *Reconciler) Register(mgr manager.Manager) error { + return builder. + ControllerManagedBy(mgr). + For(&tsapi.ProxyGroupPolicy{}). + Named(reconcilerName). + Complete(r) +} + +func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + // Rather than working on a single ProxyGroupPolicy resource, we list all that exist within the + // same namespace as the one we're reconciling so that we can merge them into a single pair of + // ValidatingAdmissionPolicy resources. + var policies tsapi.ProxyGroupPolicyList + if err := r.List(ctx, &policies, client.InNamespace(req.Namespace)); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to list ProxyGroupPolicy resources %q: %w", req.NamespacedName, err) + } + + if len(policies.Items) == 0 { + // If we've got no items in the list, we go and delete any policies and bindings that + // may exist. + return r.delete(ctx, req.Namespace) + } + + return r.createOrUpdate(ctx, req.Namespace, policies) +} + +func (r *Reconciler) delete(ctx context.Context, namespace string) (reconcile.Result, error) { + ingress := "ts-ingress-" + namespace + egress := "ts-egress-" + namespace + + objects := []client.Object{ + &admr.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: ingress, + }, + }, + &admr.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ingress, + }, + }, + &admr.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: egress, + }, + }, + &admr.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: egress, + }, + }, + } + + for _, obj := range objects { + err := r.Delete(ctx, obj) + switch { + case apierrors.IsNotFound(err): + // A resource may have already been deleted in a previous reconciliation that failed for + // some reason, so we'll ignore it if it doesn't exist. + continue + case err != nil: + return reconcile.Result{}, fmt.Errorf("failed to delete %s %q: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err) + } + } + + return reconcile.Result{}, nil +} + +func (r *Reconciler) createOrUpdate(ctx context.Context, namespace string, policies tsapi.ProxyGroupPolicyList) (reconcile.Result, error) { + ingressNames := set.Set[string]{} + egressNames := set.Set[string]{} + + // If this namespace has multiple ProxyGroupPolicy resources, we'll reduce them down to just their distinct + // egress/ingress names. + for _, policy := range policies.Items { + ingressNames.AddSlice(policy.Spec.Ingress) + egressNames.AddSlice(policy.Spec.Egress) + } + + ingress, err := r.generateIngressPolicy(ctx, namespace, ingressNames) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to generate ingress policy: %w", err) + } + + ingressBinding, err := r.generatePolicyBinding(ctx, namespace, ingress) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to generate ingress policy binding: %w", err) + } + + egress, err := r.generateEgressPolicy(ctx, namespace, egressNames) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to generate egress policy: %w", err) + } + + egressBinding, err := r.generatePolicyBinding(ctx, namespace, egress) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to generate egress policy binding: %w", err) + } + + objects := []client.Object{ + ingress, + ingressBinding, + egress, + egressBinding, + } + + for _, obj := range objects { + // Attempt to perform an update first as we'll only create these once and continually update them, so it's + // more likely that an update is needed instead of creation. If the resource does not exist, we'll + // create it. + err = r.Update(ctx, obj) + switch { + case apierrors.IsNotFound(err): + if err = r.Create(ctx, obj); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to create %s %q: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err) + } + case err != nil: + return reconcile.Result{}, fmt.Errorf("failed to update %s %q: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err) + } + } + + return reconcile.Result{}, nil +} + +const ( + // ingressCEL enforces proxy-group annotation rules for Ingress resources. + // + // Logic: + // + // - If the object is NOT an Ingress → allow (this validation is irrelevant) + // - If the annotation is absent → allow (annotation is optional) + // - If the annotation is present → its value must be in the allowlist + // + // Empty allowlist behavior: + // If the list is empty, any present annotation will fail membership, + // effectively acting as "deny-all". + ingressCEL = `request.kind.kind != "Ingress" || !("tailscale.com/proxy-group" in object.metadata.annotations) || object.metadata.annotations["tailscale.com/proxy-group"] in [%s]` + + // ingressServiceCEL enforces proxy-group annotation rules for Services + // that are using the tailscale load balancer. + // + // Logic: + // + // - If the object is NOT a Service → allow + // - If Service does NOT use loadBalancerClass "tailscale" → allow + // (egress policy will handle those) + // - If annotation is absent → allow + // - If annotation is present → must be in allowlist + // + // This makes ingress policy apply ONLY to tailscale Services. + ingressServiceCEL = `request.kind.kind != "Service" || !((has(object.spec.loadBalancerClass) && object.spec.loadBalancerClass == "tailscale") || ("tailscale.com/expose" in object.metadata.annotations && object.metadata.annotations["tailscale.com/expose"] == "true")) || (!("tailscale.com/proxy-group" in object.metadata.annotations) || object.metadata.annotations["tailscale.com/proxy-group"] in [%s])` + // egressCEL enforces proxy-group annotation rules for Services that + // are NOT using the tailscale load balancer. + // + // Logic: + // + // - If Service uses loadBalancerClass "tailscale" → allow + // (ingress policy handles those) + // - If Service uses "tailscale.com/expose" → allow + // (ingress policy handles those) + // - If annotation is absent → allow + // - If annotation is present → must be in allowlist + // + // Empty allowlist behavior: + // Any present annotation is rejected ("deny-all"). + // + // This expression is mutually exclusive with ingressServiceCEL, + // preventing policy conflicts. + egressCEL = `((has(object.spec.loadBalancerClass) && object.spec.loadBalancerClass == "tailscale") || ("tailscale.com/expose" in object.metadata.annotations && object.metadata.annotations["tailscale.com/expose"] == "true")) || !("tailscale.com/proxy-group" in object.metadata.annotations) || object.metadata.annotations["tailscale.com/proxy-group"] in [%s]` +) + +func (r *Reconciler) generateIngressPolicy(ctx context.Context, namespace string, names set.Set[string]) (*admr.ValidatingAdmissionPolicy, error) { + name := "ts-ingress-" + namespace + + var policy admr.ValidatingAdmissionPolicy + err := r.Get(ctx, client.ObjectKey{Name: name}, &policy) + switch { + case apierrors.IsNotFound(err): + // If it's not found, we can create a new one. We only want the existing one for + // its resource version. + case err != nil: + return nil, fmt.Errorf("failed to get ValidatingAdmissionPolicy %q: %w", name, err) + } + + return &admr.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: policy.ResourceVersion, + }, + Spec: admr.ValidatingAdmissionPolicySpec{ + FailurePolicy: ptr.To(admr.Fail), + MatchConstraints: &admr.MatchResources{ + // The operator allows ingress via Ingress resources & Service resources (that use the "tailscale" load + // balancer class), so we have two resource rules here with multiple validation expressions that attempt + // to keep out of each other's way. + ResourceRules: []admr.NamedRuleWithOperations{ + { + RuleWithOperations: admr.RuleWithOperations{ + Operations: []admr.OperationType{ + admr.Create, + admr.Update, + }, + Rule: admr.Rule{ + APIGroups: []string{"networking.k8s.io"}, + APIVersions: []string{"*"}, + Resources: []string{"ingresses"}, + }, + }, + }, + { + RuleWithOperations: admr.RuleWithOperations{ + Operations: []admr.OperationType{ + admr.Create, + admr.Update, + }, + Rule: admr.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"services"}, + }, + }, + }, + }, + }, + Validations: []admr.Validation{ + generateValidation(names, ingressCEL), + generateValidation(names, ingressServiceCEL), + }, + }, + }, nil +} + +func (r *Reconciler) generateEgressPolicy(ctx context.Context, namespace string, names set.Set[string]) (*admr.ValidatingAdmissionPolicy, error) { + name := "ts-egress-" + namespace + + var policy admr.ValidatingAdmissionPolicy + err := r.Get(ctx, client.ObjectKey{Name: name}, &policy) + switch { + case apierrors.IsNotFound(err): + // If it's not found, we can create a new one. We only want the existing one for + // its resource version. + case err != nil: + return nil, fmt.Errorf("failed to get ValidatingAdmissionPolicy %q: %w", name, err) + } + + return &admr.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: policy.ResourceVersion, + }, + Spec: admr.ValidatingAdmissionPolicySpec{ + FailurePolicy: ptr.To(admr.Fail), + MatchConstraints: &admr.MatchResources{ + ResourceRules: []admr.NamedRuleWithOperations{ + { + RuleWithOperations: admr.RuleWithOperations{ + Operations: []admr.OperationType{ + admr.Create, + admr.Update, + }, + Rule: admr.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"services"}, + }, + }, + }, + }, + }, + Validations: []admr.Validation{ + generateValidation(names, egressCEL), + }, + }, + }, nil +} + +const ( + denyMessage = `Annotation "tailscale.com/proxy-group" cannot be used on this resource in this namespace` + messageFormat = `If set, annotation "tailscale.com/proxy-group" must be one of [%s]` +) + +func generateValidation(names set.Set[string], format string) admr.Validation { + values := names.Slice() + + // We use a sort here so that the order of the proxy-group names are consistent + // across reconciliation loops. + sort.Strings(values) + + quoted := make([]string, len(values)) + for i, v := range values { + quoted[i] = strconv.Quote(v) + } + + joined := strings.Join(quoted, ",") + message := fmt.Sprintf(messageFormat, strings.Join(values, ", ")) + if len(values) == 0 { + message = denyMessage + } + + return admr.Validation{ + Expression: fmt.Sprintf(format, joined), + Message: message, + } +} + +func (r *Reconciler) generatePolicyBinding(ctx context.Context, namespace string, policy *admr.ValidatingAdmissionPolicy) (*admr.ValidatingAdmissionPolicyBinding, error) { + var binding admr.ValidatingAdmissionPolicyBinding + err := r.Get(ctx, client.ObjectKey{Name: policy.Name}, &binding) + switch { + case apierrors.IsNotFound(err): + // If it's not found, we can create a new one. We only want the existing one for + // its resource version. + case err != nil: + return nil, fmt.Errorf("failed to get ValidatingAdmissionPolicyBinding %q: %w", policy.Name, err) + } + + return &admr.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: policy.Name, + ResourceVersion: binding.ResourceVersion, + }, + Spec: admr.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: policy.Name, + ValidationActions: []admr.ValidationAction{ + admr.Deny, + }, + MatchResources: &admr.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": namespace, + }, + }, + }, + }, + }, nil +} diff --git a/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy_test.go b/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy_test.go new file mode 100644 index 0000000000000..6710eac7406d6 --- /dev/null +++ b/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy_test.go @@ -0,0 +1,217 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package proxygrouppolicy_test + +import ( + "slices" + "strings" + "testing" + + admr "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler/proxygrouppolicy" +) + +func TestReconciler_Reconcile(t *testing.T) { + t.Parallel() + + tt := []struct { + Name string + Request reconcile.Request + ExpectedPolicyCount int + ExistingResources []client.Object + ExpectsError bool + }{ + { + Name: "single policy, denies all", + ExpectedPolicyCount: 2, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + }, + ExistingResources: []client.Object{ + &tsapi.ProxyGroupPolicy{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{}, + Egress: []string{}, + }, + }, + }, + }, + { + Name: "multiple policies merged", + ExpectedPolicyCount: 2, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + }, + ExistingResources: []client.Object{ + &tsapi.ProxyGroupPolicy{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{}, + Egress: []string{}, + }, + }, + &tsapi.ProxyGroupPolicy{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-one", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{ + "test-ingress", + }, + Egress: []string{}, + }, + }, + }, + }, + { + Name: "no policies, no child resources", + ExpectedPolicyCount: 0, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + bldr := fake.NewClientBuilder().WithScheme(tsapi.GlobalScheme) + bldr = bldr.WithObjects(tc.ExistingResources...) + + fc := bldr.Build() + opts := proxygrouppolicy.ReconcilerOptions{ + Client: fc, + } + + reconciler := proxygrouppolicy.NewReconciler(opts) + _, err := reconciler.Reconcile(t.Context(), tc.Request) + if tc.ExpectsError && err == nil { + t.Fatalf("expected error, got none") + } + + if !tc.ExpectsError && err != nil { + t.Fatalf("expected no error, got %v", err) + } + + var policies admr.ValidatingAdmissionPolicyList + if err = fc.List(t.Context(), &policies); err != nil { + t.Fatal(err) + } + + if len(policies.Items) != tc.ExpectedPolicyCount { + t.Fatalf("expected %d ValidatingAdmissionPolicy resources, got %d", tc.ExpectedPolicyCount, len(policies.Items)) + } + + var bindings admr.ValidatingAdmissionPolicyBindingList + if err = fc.List(t.Context(), &bindings); err != nil { + t.Fatal(err) + } + + if len(bindings.Items) != tc.ExpectedPolicyCount { + t.Fatalf("expected %d ValidatingAdmissionPolicyBinding resources, got %d", tc.ExpectedPolicyCount, len(bindings.Items)) + } + + for _, binding := range bindings.Items { + actual, ok := binding.Spec.MatchResources.NamespaceSelector.MatchLabels["kubernetes.io/metadata.name"] + if !ok || actual != metav1.NamespaceDefault { + t.Fatalf("expected binding to be for default namespace, got %v", actual) + } + + if !slices.Contains(binding.Spec.ValidationActions, admr.Deny) { + t.Fatalf("expected binding to be deny, got %v", binding.Spec.ValidationActions) + } + } + + for _, policy := range policies.Items { + // Each ValidatingAdmissionPolicy must be set to fail (rejecting resources). + if policy.Spec.FailurePolicy == nil || *policy.Spec.FailurePolicy != admr.Fail { + t.Fatalf("expected fail policy, got %v", *policy.Spec.FailurePolicy) + } + + // Each ValidatingAdmissionPolicy must have a matching ValidatingAdmissionPolicyBinding + bound := slices.ContainsFunc(bindings.Items, func(obj admr.ValidatingAdmissionPolicyBinding) bool { + return obj.Spec.PolicyName == policy.Name + }) + if !bound { + t.Fatalf("expected policy %s to be bound, but wasn't", policy.Name) + } + + // Each ValidatingAdmissionPolicy must be set to evaluate on creation and update of resources. + for _, rule := range policy.Spec.MatchConstraints.ResourceRules { + if !slices.Contains(rule.Operations, admr.Update) { + t.Fatal("expected ingress rule to act on update, but doesn't") + } + + if !slices.Contains(rule.Operations, admr.Create) { + t.Fatal("expected ingress rule to act on create, but doesn't") + } + } + + // Egress policies should only act on Service resources. + if strings.Contains(policy.Name, "egress") { + if len(policy.Spec.MatchConstraints.ResourceRules) != 1 { + t.Fatalf("expected exactly one matching resource, got %d", len(policy.Spec.MatchConstraints.ResourceRules)) + } + + rule := policy.Spec.MatchConstraints.ResourceRules[0] + + if !slices.Contains(rule.Resources, "services") { + t.Fatal("expected egress rule to act on services, but doesn't") + } + + if len(policy.Spec.Validations) != 1 { + t.Fatalf("expected exactly one validation, got %d", len(policy.Spec.Validations)) + } + } + + // Ingress policies should act on both Ingress and Service resources. + if strings.Contains(policy.Name, "ingress") { + if len(policy.Spec.MatchConstraints.ResourceRules) != 2 { + t.Fatalf("expected exactly two matching resources, got %d", len(policy.Spec.MatchConstraints.ResourceRules)) + } + + ingressRule := policy.Spec.MatchConstraints.ResourceRules[0] + if !slices.Contains(ingressRule.Resources, "ingresses") { + t.Fatal("expected ingress rule to act on ingresses, but doesn't") + } + + serviceRule := policy.Spec.MatchConstraints.ResourceRules[1] + if !slices.Contains(serviceRule.Resources, "services") { + t.Fatal("expected ingress rule to act on services, but doesn't") + } + + if len(policy.Spec.Validations) != 2 { + t.Fatalf("expected exactly two validations, got %d", len(policy.Spec.Validations)) + } + } + } + }) + } +} diff --git a/k8s-operator/reconciler/reconciler.go b/k8s-operator/reconciler/reconciler.go new file mode 100644 index 0000000000000..fcad7201e31e4 --- /dev/null +++ b/k8s-operator/reconciler/reconciler.go @@ -0,0 +1,39 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +// Package reconciler provides utilities for working with Kubernetes resources within controller reconciliation +// loops. +package reconciler + +import ( + "slices" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + // FinalizerName is the common finalizer used across all Tailscale Kubernetes resources. + FinalizerName = "tailscale.com/finalizer" +) + +// SetFinalizer adds the finalizer to the resource if not already present. +func SetFinalizer(obj client.Object) { + if idx := slices.Index(obj.GetFinalizers(), FinalizerName); idx >= 0 { + return + } + + obj.SetFinalizers(append(obj.GetFinalizers(), FinalizerName)) +} + +// RemoveFinalizer removes the finalizer from the resource if present. +func RemoveFinalizer(obj client.Object) { + idx := slices.Index(obj.GetFinalizers(), FinalizerName) + if idx < 0 { + return + } + + finalizers := obj.GetFinalizers() + obj.SetFinalizers(append(finalizers[:idx], finalizers[idx+1:]...)) +} diff --git a/k8s-operator/reconciler/reconciler_test.go b/k8s-operator/reconciler/reconciler_test.go new file mode 100644 index 0000000000000..2db77e7aad419 --- /dev/null +++ b/k8s-operator/reconciler/reconciler_test.go @@ -0,0 +1,42 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package reconciler_test + +import ( + "slices" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "tailscale.com/k8s-operator/reconciler" +) + +func TestFinalizers(t *testing.T) { + t.Parallel() + + object := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + StringData: map[string]string{ + "hello": "world", + }, + } + + reconciler.SetFinalizer(object) + + if !slices.Contains(object.Finalizers, reconciler.FinalizerName) { + t.Fatalf("object does not have finalizer %q: %v", reconciler.FinalizerName, object.Finalizers) + } + + reconciler.RemoveFinalizer(object) + + if slices.Contains(object.Finalizers, reconciler.FinalizerName) { + t.Fatalf("object still has finalizer %q: %v", reconciler.FinalizerName, object.Finalizers) + } +} diff --git a/k8s-operator/reconciler/tailnet/mocks_test.go b/k8s-operator/reconciler/tailnet/mocks_test.go new file mode 100644 index 0000000000000..4342556885013 --- /dev/null +++ b/k8s-operator/reconciler/tailnet/mocks_test.go @@ -0,0 +1,45 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package tailnet_test + +import ( + "context" + "io" + + "tailscale.com/internal/client/tailscale" +) + +type ( + MockTailnetClient struct { + ErrorOnDevices bool + ErrorOnKeys bool + ErrorOnServices bool + } +) + +func (m MockTailnetClient) Devices(_ context.Context, _ *tailscale.DeviceFieldsOpts) ([]*tailscale.Device, error) { + if m.ErrorOnDevices { + return nil, io.EOF + } + + return nil, nil +} + +func (m MockTailnetClient) Keys(_ context.Context) ([]string, error) { + if m.ErrorOnKeys { + return nil, io.EOF + } + + return nil, nil +} + +func (m MockTailnetClient) ListVIPServices(_ context.Context) (*tailscale.VIPServiceList, error) { + if m.ErrorOnServices { + return nil, io.EOF + } + + return nil, nil +} diff --git a/k8s-operator/reconciler/tailnet/tailnet.go b/k8s-operator/reconciler/tailnet/tailnet.go new file mode 100644 index 0000000000000..2e7004b698c93 --- /dev/null +++ b/k8s-operator/reconciler/tailnet/tailnet.go @@ -0,0 +1,327 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +// Package tailnet provides reconciliation logic for the Tailnet custom resource definition. It is responsible for +// ensuring the referenced OAuth credentials are valid and have the required scopes to be able to generate authentication +// keys, manage devices & manage VIP services. +package tailnet + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "go.uber.org/zap" + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "tailscale.com/internal/client/tailscale" + "tailscale.com/ipn" + operatorutils "tailscale.com/k8s-operator" + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler" + "tailscale.com/kube/kubetypes" + "tailscale.com/tstime" + "tailscale.com/util/clientmetric" + "tailscale.com/util/set" +) + +type ( + // The Reconciler type is a reconcile.TypedReconciler implementation used to manage the reconciliation of + // Tailnet custom resources. + Reconciler struct { + client.Client + + tailscaleNamespace string + clock tstime.Clock + logger *zap.SugaredLogger + clientFunc func(*tsapi.Tailnet, *corev1.Secret) TailscaleClient + + // Metrics related fields + mu sync.Mutex + tailnets set.Slice[types.UID] + } + + // The ReconcilerOptions type contains configuration values for the Reconciler. + ReconcilerOptions struct { + // The client for interacting with the Kubernetes API. + Client client.Client + // The namespace the operator is installed in. This reconciler expects Tailnet OAuth credentials to be stored + // in Secret resources within this namespace. + TailscaleNamespace string + // Controls which clock to use for performing time-based functions. This is typically modified for use + // in tests. + Clock tstime.Clock + // The logger to use for this Reconciler. + Logger *zap.SugaredLogger + // ClientFunc is a function that takes tailscale credentials and returns an implementation for the Tailscale + // HTTP API. This should generally be nil unless needed for testing. + ClientFunc func(*tsapi.Tailnet, *corev1.Secret) TailscaleClient + } + + // The TailscaleClient interface describes types that interact with the Tailscale HTTP API. + TailscaleClient interface { + Devices(context.Context, *tailscale.DeviceFieldsOpts) ([]*tailscale.Device, error) + Keys(ctx context.Context) ([]string, error) + ListVIPServices(ctx context.Context) (*tailscale.VIPServiceList, error) + } +) + +const reconcilerName = "tailnet-reconciler" + +// NewReconciler returns a new instance of the Reconciler type. It watches specifically for changes to Tailnet custom +// resources. The ReconcilerOptions can be used to modify the behaviour of the Reconciler. +func NewReconciler(options ReconcilerOptions) *Reconciler { + return &Reconciler{ + Client: options.Client, + tailscaleNamespace: options.TailscaleNamespace, + clock: options.Clock, + logger: options.Logger.Named(reconcilerName), + clientFunc: options.ClientFunc, + } +} + +// Register the Reconciler onto the given manager.Manager implementation. +func (r *Reconciler) Register(mgr manager.Manager) error { + return builder. + ControllerManagedBy(mgr). + For(&tsapi.Tailnet{}). + Named(reconcilerName). + Complete(r) +} + +var ( + // gaugeTailnetResources tracks the overall number of Tailnet resources currently managed by this operator instance. + gaugeTailnetResources = clientmetric.NewGauge(kubetypes.MetricTailnetCount) +) + +// Reconcile is invoked when a change occurs to Tailnet resources within the cluster. On create/update, the Tailnet +// resource is validated ensuring that the specified Secret exists and contains valid OAuth credentials that have +// required permissions to perform all necessary functions by the operator. +func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + var tailnet tsapi.Tailnet + err := r.Get(ctx, req.NamespacedName, &tailnet) + switch { + case apierrors.IsNotFound(err): + return reconcile.Result{}, nil + case err != nil: + return reconcile.Result{}, fmt.Errorf("failed to get Tailnet %q: %w", req.NamespacedName, err) + } + + if !tailnet.DeletionTimestamp.IsZero() { + return r.delete(ctx, &tailnet) + } + + return r.createOrUpdate(ctx, &tailnet) +} + +func (r *Reconciler) delete(ctx context.Context, tailnet *tsapi.Tailnet) (reconcile.Result, error) { + reconciler.RemoveFinalizer(tailnet) + if err := r.Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to remove finalizer from Tailnet %q: %w", tailnet.Name, err) + } + + r.mu.Lock() + r.tailnets.Remove(tailnet.UID) + r.mu.Unlock() + gaugeTailnetResources.Set(int64(r.tailnets.Len())) + + return reconcile.Result{}, nil +} + +// Constants for condition reasons. +const ( + ReasonInvalidOAuth = "InvalidOAuth" + ReasonInvalidSecret = "InvalidSecret" + ReasonValid = "TailnetValid" +) + +func (r *Reconciler) createOrUpdate(ctx context.Context, tailnet *tsapi.Tailnet) (reconcile.Result, error) { + r.mu.Lock() + r.tailnets.Add(tailnet.UID) + r.mu.Unlock() + gaugeTailnetResources.Set(int64(r.tailnets.Len())) + + name := types.NamespacedName{Name: tailnet.Spec.Credentials.SecretName, Namespace: r.tailscaleNamespace} + + var secret corev1.Secret + err := r.Get(ctx, name, &secret) + + // The referenced Secret does not exist within the tailscale namespace, so we'll mark the Tailnet as not ready + // for use. + if apierrors.IsNotFound(err) { + operatorutils.SetTailnetCondition( + tailnet, + tsapi.TailnetReady, + metav1.ConditionFalse, + ReasonInvalidSecret, + fmt.Sprintf("referenced secret %q does not exist in namespace %q", name.Name, r.tailscaleNamespace), + r.clock, + r.logger, + ) + + if err = r.Status().Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update Tailnet status for %q: %w", tailnet.Name, err) + } + + return reconcile.Result{}, nil + } + + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to get secret %q: %w", name, err) + } + + // We first ensure that the referenced secret contains the required fields. Otherwise, we set the Tailnet as + // invalid. The operator will not allow the use of this Tailnet while it is in an invalid state. + if ok := r.ensureSecret(tailnet, &secret); !ok { + if err = r.Status().Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update Tailnet status for %q: %w", tailnet.Name, err) + } + + return reconcile.Result{RequeueAfter: time.Minute / 2}, nil + } + + tsClient := r.createClient(ctx, tailnet, &secret) + + // Second, we ensure the OAuth credentials supplied in the secret are valid and have the required scopes to access + // the various API endpoints required by the operator. + if ok := r.ensurePermissions(ctx, tsClient, tailnet); !ok { + if err = r.Status().Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update Tailnet status for %q: %w", tailnet.Name, err) + } + + // We provide a requeue duration here as a user will likely want to go and modify their scopes and come back. + // This should save them having to delete and recreate the resource. + return reconcile.Result{RequeueAfter: time.Minute / 2}, nil + } + + operatorutils.SetTailnetCondition( + tailnet, + tsapi.TailnetReady, + metav1.ConditionTrue, + ReasonValid, + ReasonValid, + r.clock, + r.logger, + ) + + if err = r.Status().Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update Tailnet status for %q: %w", tailnet.Name, err) + } + + reconciler.SetFinalizer(tailnet) + if err = r.Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to add finalizer to Tailnet %q: %w", tailnet.Name, err) + } + + return reconcile.Result{}, nil +} + +// Constants for OAuth credential fields within the Secret referenced by the Tailnet. +const ( + clientIDKey = "client_id" + clientSecretKey = "client_secret" +) + +func (r *Reconciler) createClient(ctx context.Context, tailnet *tsapi.Tailnet, secret *corev1.Secret) TailscaleClient { + if r.clientFunc != nil { + return r.clientFunc(tailnet, secret) + } + + baseURL := ipn.DefaultControlURL + if tailnet.Spec.LoginURL != "" { + baseURL = tailnet.Spec.LoginURL + } + + credentials := clientcredentials.Config{ + ClientID: string(secret.Data[clientIDKey]), + ClientSecret: string(secret.Data[clientSecretKey]), + TokenURL: baseURL + "/api/v2/oauth/token", + } + + source := credentials.TokenSource(ctx) + httpClient := oauth2.NewClient(ctx, source) + + tsClient := tailscale.NewClient("-", nil) + tsClient.UserAgent = "tailscale-k8s-operator" + tsClient.HTTPClient = httpClient + tsClient.BaseURL = baseURL + + return tsClient +} + +func (r *Reconciler) ensurePermissions(ctx context.Context, tsClient TailscaleClient, tailnet *tsapi.Tailnet) bool { + // Perform basic list requests here to confirm that the OAuth credentials referenced on the Tailnet resource + // can perform the basic operations required for the operator to function. This has a caveat of only performing + // read actions, as we don't want to create arbitrary keys and VIP services. However, it will catch when a user + // has completely forgotten an entire scope that's required. + var errs error + if _, err := tsClient.Devices(ctx, nil); err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to list devices: %w", err)) + } + + if _, err := tsClient.Keys(ctx); err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to list auth keys: %w", err)) + } + + if _, err := tsClient.ListVIPServices(ctx); err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to list tailscale services: %w", err)) + } + + if errs != nil { + operatorutils.SetTailnetCondition( + tailnet, + tsapi.TailnetReady, + metav1.ConditionFalse, + ReasonInvalidOAuth, + errs.Error(), + r.clock, + r.logger, + ) + + return false + } + + return true +} + +func (r *Reconciler) ensureSecret(tailnet *tsapi.Tailnet, secret *corev1.Secret) bool { + var message string + + switch { + case len(secret.Data) == 0: + message = fmt.Sprintf("Secret %q is empty", secret.Name) + case len(secret.Data[clientIDKey]) == 0: + message = fmt.Sprintf("Secret %q is missing the client_id field", secret.Name) + case len(secret.Data[clientSecretKey]) == 0: + message = fmt.Sprintf("Secret %q is missing the client_secret field", secret.Name) + } + + if message == "" { + return true + } + + operatorutils.SetTailnetCondition( + tailnet, + tsapi.TailnetReady, + metav1.ConditionFalse, + ReasonInvalidSecret, + message, + r.clock, + r.logger, + ) + + return false +} diff --git a/k8s-operator/reconciler/tailnet/tailnet_test.go b/k8s-operator/reconciler/tailnet/tailnet_test.go new file mode 100644 index 0000000000000..0ed2ca598d720 --- /dev/null +++ b/k8s-operator/reconciler/tailnet/tailnet_test.go @@ -0,0 +1,411 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package tailnet_test + +import ( + "testing" + + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler/tailnet" + "tailscale.com/tstest" +) + +func TestReconciler_Reconcile(t *testing.T) { + t.Parallel() + clock := tstest.NewClock(tstest.ClockOpts{}) + logger, err := zap.NewDevelopment() + if err != nil { + t.Fatal(err) + } + + tt := []struct { + Name string + Request reconcile.Request + Tailnet *tsapi.Tailnet + Secret *corev1.Secret + ExpectsError bool + ExpectedConditions []metav1.Condition + ClientFunc func(*tsapi.Tailnet, *corev1.Secret) tailnet.TailscaleClient + }{ + { + Name: "ignores unknown tailnet requests", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + }, + { + Name: "invalid status for missing secret", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidSecret, + Message: `referenced secret "test" does not exist in namespace "tailscale"`, + }, + }, + }, + { + Name: "invalid status for empty secret", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidSecret, + Message: `Secret "test" is empty`, + }, + }, + }, + { + Name: "invalid status for missing client id", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_secret": []byte("test"), + }, + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidSecret, + Message: `Secret "test" is missing the client_id field`, + }, + }, + }, + { + Name: "invalid status for missing client secret", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + }, + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidSecret, + Message: `Secret "test" is missing the client_secret field`, + }, + }, + }, + { + Name: "invalid status for bad devices scope", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + "client_secret": []byte("test"), + }, + }, + ClientFunc: func(_ *tsapi.Tailnet, _ *corev1.Secret) tailnet.TailscaleClient { + return &MockTailnetClient{ErrorOnDevices: true} + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidOAuth, + Message: `failed to list devices: EOF`, + }, + }, + }, + { + Name: "invalid status for bad services scope", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + "client_secret": []byte("test"), + }, + }, + ClientFunc: func(_ *tsapi.Tailnet, _ *corev1.Secret) tailnet.TailscaleClient { + return &MockTailnetClient{ErrorOnServices: true} + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidOAuth, + Message: `failed to list tailscale services: EOF`, + }, + }, + }, + { + Name: "invalid status for bad keys scope", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + "client_secret": []byte("test"), + }, + }, + ClientFunc: func(_ *tsapi.Tailnet, _ *corev1.Secret) tailnet.TailscaleClient { + return &MockTailnetClient{ErrorOnKeys: true} + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidOAuth, + Message: `failed to list auth keys: EOF`, + }, + }, + }, + { + Name: "ready when valid and scopes are correct", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "default", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + "client_secret": []byte("test"), + }, + }, + ClientFunc: func(_ *tsapi.Tailnet, _ *corev1.Secret) tailnet.TailscaleClient { + return &MockTailnetClient{} + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionTrue, + Reason: tailnet.ReasonValid, + Message: tailnet.ReasonValid, + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + builder := fake.NewClientBuilder().WithScheme(tsapi.GlobalScheme) + if tc.Tailnet != nil { + builder = builder.WithObjects(tc.Tailnet).WithStatusSubresource(tc.Tailnet) + } + if tc.Secret != nil { + builder = builder.WithObjects(tc.Secret) + } + + fc := builder.Build() + opts := tailnet.ReconcilerOptions{ + Client: fc, + Clock: clock, + Logger: logger.Sugar(), + ClientFunc: tc.ClientFunc, + TailscaleNamespace: "tailscale", + } + + reconciler := tailnet.NewReconciler(opts) + _, err = reconciler.Reconcile(t.Context(), tc.Request) + if tc.ExpectsError && err == nil { + t.Fatalf("expected error, got none") + } + + if !tc.ExpectsError && err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(tc.ExpectedConditions) == 0 { + return + } + + var tn tsapi.Tailnet + if err = fc.Get(t.Context(), tc.Request.NamespacedName, &tn); err != nil { + t.Fatal(err) + } + + if len(tn.Status.Conditions) != len(tc.ExpectedConditions) { + t.Fatalf("expected %v condition(s), got %v", len(tc.ExpectedConditions), len(tn.Status.Conditions)) + } + + for i, expected := range tc.ExpectedConditions { + actual := tn.Status.Conditions[i] + + if actual.Type != expected.Type { + t.Errorf("expected %v, got %v", expected.Type, actual.Type) + } + + if actual.Status != expected.Status { + t.Errorf("expected %v, got %v", expected.Status, actual.Status) + } + + if actual.Reason != expected.Reason { + t.Errorf("expected %v, got %v", expected.Reason, actual.Reason) + } + + if actual.Message != expected.Message { + t.Errorf("expected %v, got %v", expected.Message, actual.Message) + } + } + + if err = fc.Delete(t.Context(), &tn); err != nil { + t.Fatal(err) + } + + if _, err = reconciler.Reconcile(t.Context(), tc.Request); err != nil { + t.Fatal(err) + } + + err = fc.Get(t.Context(), tc.Request.NamespacedName, &tn) + if !apierrors.IsNotFound(err) { + t.Fatalf("expected not found error, got %v", err) + } + }) + } +} diff --git a/k8s-operator/sessionrecording/fakes/fakes.go b/k8s-operator/sessionrecording/fakes/fakes.go index 94853df195f7c..26f57e4eb4b69 100644 --- a/k8s-operator/sessionrecording/fakes/fakes.go +++ b/k8s-operator/sessionrecording/fakes/fakes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/hijacker.go b/k8s-operator/sessionrecording/hijacker.go index 7345a407c8faa..cdbeeddb43ac8 100644 --- a/k8s-operator/sessionrecording/hijacker.go +++ b/k8s-operator/sessionrecording/hijacker.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/hijacker_test.go b/k8s-operator/sessionrecording/hijacker_test.go index fb45820a71b86..ac243c2e8bc82 100644 --- a/k8s-operator/sessionrecording/hijacker_test.go +++ b/k8s-operator/sessionrecording/hijacker_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/conn.go b/k8s-operator/sessionrecording/spdy/conn.go index 9fefca11fc2b8..682003055acb8 100644 --- a/k8s-operator/sessionrecording/spdy/conn.go +++ b/k8s-operator/sessionrecording/spdy/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/conn_test.go b/k8s-operator/sessionrecording/spdy/conn_test.go index 3c1cb8427d822..232fa8e2c2227 100644 --- a/k8s-operator/sessionrecording/spdy/conn_test.go +++ b/k8s-operator/sessionrecording/spdy/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/frame.go b/k8s-operator/sessionrecording/spdy/frame.go index 54b29d33a9622..7087db3c32166 100644 --- a/k8s-operator/sessionrecording/spdy/frame.go +++ b/k8s-operator/sessionrecording/spdy/frame.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/frame_test.go b/k8s-operator/sessionrecording/spdy/frame_test.go index 4896cdcbf78a5..1b7e54f4cc1fb 100644 --- a/k8s-operator/sessionrecording/spdy/frame_test.go +++ b/k8s-operator/sessionrecording/spdy/frame_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/zlib-reader.go b/k8s-operator/sessionrecording/spdy/zlib-reader.go index 1eb654be35632..2b63f7e2b592b 100644 --- a/k8s-operator/sessionrecording/spdy/zlib-reader.go +++ b/k8s-operator/sessionrecording/spdy/zlib-reader.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/tsrecorder/tsrecorder.go b/k8s-operator/sessionrecording/tsrecorder/tsrecorder.go index a5bdf7ddddeeb..40a96d6d29ac7 100644 --- a/k8s-operator/sessionrecording/tsrecorder/tsrecorder.go +++ b/k8s-operator/sessionrecording/tsrecorder/tsrecorder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/ws/conn.go b/k8s-operator/sessionrecording/ws/conn.go index a618f85fb7822..4762630ca7522 100644 --- a/k8s-operator/sessionrecording/ws/conn.go +++ b/k8s-operator/sessionrecording/ws/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/ws/conn_test.go b/k8s-operator/sessionrecording/ws/conn_test.go index 87205c4e6f610..0b4353698cd9f 100644 --- a/k8s-operator/sessionrecording/ws/conn_test.go +++ b/k8s-operator/sessionrecording/ws/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/ws/message.go b/k8s-operator/sessionrecording/ws/message.go index 35667ae21a5d0..36359996a7c12 100644 --- a/k8s-operator/sessionrecording/ws/message.go +++ b/k8s-operator/sessionrecording/ws/message.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/ws/message_test.go b/k8s-operator/sessionrecording/ws/message_test.go index f634f86dc55c2..07d55ce4dcbd5 100644 --- a/k8s-operator/sessionrecording/ws/message_test.go +++ b/k8s-operator/sessionrecording/ws/message_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/utils.go b/k8s-operator/utils.go index 2acbf338dbdd3..043a9d7b54c7a 100644 --- a/k8s-operator/utils.go +++ b/k8s-operator/utils.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/certs/certs.go b/kube/certs/certs.go index 8e2e5fb43a8ac..4c8ac88b6b624 100644 --- a/kube/certs/certs.go +++ b/kube/certs/certs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package certs implements logic to help multiple Kubernetes replicas share TLS @@ -53,6 +53,7 @@ func (cm *CertManager) EnsureCertLoops(ctx context.Context, sc *ipn.ServeConfig) currentDomains := make(map[string]bool) const httpsPort = "443" for _, service := range sc.Services { + // L7 Web handlers (HA Ingress). for hostPort := range service.Web { domain, port, err := net.SplitHostPort(string(hostPort)) if err != nil { @@ -63,6 +64,12 @@ func (cm *CertManager) EnsureCertLoops(ctx context.Context, sc *ipn.ServeConfig) } currentDomains[domain] = true } + // L4 TCP handlers with TLS termination (kube-apiserver proxy). + for _, handler := range service.TCP { + if handler != nil && handler.TerminateTLS != "" { + currentDomains[handler.TerminateTLS] = true + } + } } cm.mu.Lock() defer cm.mu.Unlock() diff --git a/kube/certs/certs_test.go b/kube/certs/certs_test.go index 8434f21ae6976..f3662f6c39ad4 100644 --- a/kube/certs/certs_test.go +++ b/kube/certs/certs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package certs @@ -127,6 +127,43 @@ func TestEnsureCertLoops(t *testing.T) { initialGoroutines: 2, // initially two loops (one per service) updatedGoroutines: 1, // one loop after removing service2 }, + { + name: "tcp_terminate_tls", + initialConfig: &ipn.ServeConfig{ + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:my-apiserver": { + TCP: map[uint16]*ipn.TCPPortHandler{ + 443: { + TCPForward: "localhost:80", + TerminateTLS: "my-apiserver.tailnetxyz.ts.net", + }, + }, + }, + }, + }, + initialGoroutines: 1, + }, + { + name: "tcp_terminate_tls_and_web", + initialConfig: &ipn.ServeConfig{ + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:my-apiserver": { + TCP: map[uint16]*ipn.TCPPortHandler{ + 443: { + TCPForward: "localhost:80", + TerminateTLS: "my-apiserver.tailnetxyz.ts.net", + }, + }, + }, + "svc:my-app": { + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "my-app.tailnetxyz.ts.net:443": {}, + }, + }, + }, + }, + initialGoroutines: 2, + }, { name: "add_domain", initialConfig: &ipn.ServeConfig{ @@ -171,6 +208,7 @@ func TestEnsureCertLoops(t *testing.T) { CertDomains: []string{ "my-app.tailnetxyz.ts.net", "my-other-app.tailnetxyz.ts.net", + "my-apiserver.tailnetxyz.ts.net", }, }, }, diff --git a/kube/egressservices/egressservices.go b/kube/egressservices/egressservices.go index 56c874f31dbb1..3828760afccf4 100644 --- a/kube/egressservices/egressservices.go +++ b/kube/egressservices/egressservices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package egressservices contains shared types for exposing tailnet services to diff --git a/kube/egressservices/egressservices_test.go b/kube/egressservices/egressservices_test.go index 806ad91be61cd..27d818cab970a 100644 --- a/kube/egressservices/egressservices_test.go +++ b/kube/egressservices/egressservices_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package egressservices diff --git a/kube/health/healthz.go b/kube/health/healthz.go index c8cfcc7ec01b4..53888922bb940 100644 --- a/kube/health/healthz.go +++ b/kube/health/healthz.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/ingressservices/ingressservices.go b/kube/ingressservices/ingressservices.go index f79410761af02..440582666a0a1 100644 --- a/kube/ingressservices/ingressservices.go +++ b/kube/ingressservices/ingressservices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ingressservices contains shared types for exposing Kubernetes Services to tailnet. diff --git a/kube/k8s-proxy/conf/conf.go b/kube/k8s-proxy/conf/conf.go index 5294952438896..62ef67feecb16 100644 --- a/kube/k8s-proxy/conf/conf.go +++ b/kube/k8s-proxy/conf/conf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/k8s-proxy/conf/conf_test.go b/kube/k8s-proxy/conf/conf_test.go index 3082be1ba9dcd..4034bf3cb7752 100644 --- a/kube/k8s-proxy/conf/conf_test.go +++ b/kube/k8s-proxy/conf/conf_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/kubeapi/api.go b/kube/kubeapi/api.go index e62bd6e2b2eb1..c3ed1a3b7e917 100644 --- a/kube/kubeapi/api.go +++ b/kube/kubeapi/api.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package kubeapi contains Kubernetes API types for internal consumption. diff --git a/kube/kubeclient/client.go b/kube/kubeclient/client.go index 0ed960f4ddcd4..5f5ab138eb65e 100644 --- a/kube/kubeclient/client.go +++ b/kube/kubeclient/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package kubeclient provides a client to interact with Kubernetes. diff --git a/kube/kubeclient/client_test.go b/kube/kubeclient/client_test.go index 8599e7e3c19e2..9778c7e6fa1ad 100644 --- a/kube/kubeclient/client_test.go +++ b/kube/kubeclient/client_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubeclient diff --git a/kube/kubeclient/fake_client.go b/kube/kubeclient/fake_client.go index 15ebb5f443f2a..7fb102764a939 100644 --- a/kube/kubeclient/fake_client.go +++ b/kube/kubeclient/fake_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubeclient diff --git a/kube/kubetypes/grants.go b/kube/kubetypes/grants.go index 50d7d760ff5a7..8f17a28546d94 100644 --- a/kube/kubetypes/grants.go +++ b/kube/kubetypes/grants.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package kubetypes contains types and constants related to the Tailscale diff --git a/kube/kubetypes/types.go b/kube/kubetypes/types.go index 44b01fe1ad1f5..9f1b29064acca 100644 --- a/kube/kubetypes/types.go +++ b/kube/kubetypes/types.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubetypes @@ -33,21 +33,22 @@ const ( MetricProxyGroupEgressCount = "k8s_proxygroup_egress_resources" MetricProxyGroupIngressCount = "k8s_proxygroup_ingress_resources" MetricProxyGroupAPIServerCount = "k8s_proxygroup_kube_apiserver_resources" + MetricTailnetCount = "k8s_tailnet_resources" // Keys that containerboot writes to state file that can be used to determine its state. // fields set in Tailscale state Secret. These are mostly used by the Tailscale Kubernetes operator to determine // the state of this tailscale device. - KeyDeviceID string = "device_id" // node stable ID of the device - KeyDeviceFQDN string = "device_fqdn" // device's tailnet hostname - KeyDeviceIPs string = "device_ips" // device's tailnet IPs - KeyPodUID string = "pod_uid" // Pod UID - // KeyCapVer contains Tailscale capability version of this proxy instance. - KeyCapVer string = "tailscale_capver" + KeyDeviceID = "device_id" // node stable ID of the device + KeyDeviceFQDN = "device_fqdn" // device's tailnet hostname + KeyDeviceIPs = "device_ips" // device's tailnet IPs + KeyPodUID = "pod_uid" // Pod UID + KeyCapVer = "tailscale_capver" // tailcfg.CurrentCapabilityVersion of this proxy instance. + KeyReissueAuthkey = "reissue_authkey" // Proxies will set this to the authkey that failed, or "no-authkey", if they can't log in. // KeyHTTPSEndpoint is a name of a field that can be set to the value of any HTTPS endpoint currently exposed by // this device to the tailnet. This is used by the Kubernetes operator Ingress proxy to communicate to the operator // that cluster workloads behind the Ingress can now be accessed via the given DNS name over HTTPS. - KeyHTTPSEndpoint string = "https_endpoint" - ValueNoHTTPS string = "no-https" + KeyHTTPSEndpoint = "https_endpoint" + ValueNoHTTPS = "no-https" // Pod's IPv4 address header key as returned by containerboot health check endpoint. PodIPv4Header string = "Pod-IPv4" diff --git a/kube/kubetypes/types_test.go b/kube/kubetypes/types_test.go index ea1846b3253e8..86b3962ef1b01 100644 --- a/kube/kubetypes/types_test.go +++ b/kube/kubetypes/types_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubetypes diff --git a/kube/localclient/fake-client.go b/kube/localclient/fake-client.go index 7f0a08316634e..a244ce31a10c9 100644 --- a/kube/localclient/fake-client.go +++ b/kube/localclient/fake-client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package localclient @@ -12,6 +12,29 @@ import ( type FakeLocalClient struct { FakeIPNBusWatcher + SetServeCalled bool + EditPrefsCalls []*ipn.MaskedPrefs + GetPrefsResult *ipn.Prefs +} + +func (m *FakeLocalClient) SetServeConfig(ctx context.Context, cfg *ipn.ServeConfig) error { + m.SetServeCalled = true + return nil +} + +func (m *FakeLocalClient) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) { + m.EditPrefsCalls = append(m.EditPrefsCalls, mp) + if m.GetPrefsResult == nil { + return &ipn.Prefs{}, nil + } + return m.GetPrefsResult, nil +} + +func (m *FakeLocalClient) GetPrefs(ctx context.Context) (*ipn.Prefs, error) { + if m.GetPrefsResult == nil { + return &ipn.Prefs{}, nil + } + return m.GetPrefsResult, nil } func (f *FakeLocalClient) WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (IPNBusWatcher, error) { diff --git a/kube/localclient/local-client.go b/kube/localclient/local-client.go index 550b3ae742c34..b8d40f4067c0e 100644 --- a/kube/localclient/local-client.go +++ b/kube/localclient/local-client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package localclient provides an interface for all the local.Client methods @@ -17,6 +17,8 @@ import ( // for easier testing. type LocalClient interface { WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (IPNBusWatcher, error) + SetServeConfig(context.Context, *ipn.ServeConfig) error + EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) CertIssuer } @@ -40,6 +42,14 @@ type localClient struct { lc *local.Client } +func (lc *localClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error { + return lc.lc.SetServeConfig(ctx, config) +} + +func (lc *localClient) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) { + return lc.lc.EditPrefs(ctx, mp) +} + func (lc *localClient) WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (IPNBusWatcher, error) { return lc.lc.WatchIPNBus(ctx, mask) } diff --git a/kube/metrics/metrics.go b/kube/metrics/metrics.go index 0db683008f91e..062f18b8b95b5 100644 --- a/kube/metrics/metrics.go +++ b/kube/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/services/services.go b/kube/services/services.go index a9e50975ca9f1..0c27f888f5f7d 100644 --- a/kube/services/services.go +++ b/kube/services/services.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package services manages graceful shutdown of Tailscale Services advertised @@ -12,9 +12,46 @@ import ( "tailscale.com/client/local" "tailscale.com/ipn" + "tailscale.com/kube/localclient" "tailscale.com/types/logger" ) +// EnsureServicesAdvertised is a function that gets called on containerboot +// startup and ensures that Services get advertised if they exist. +func EnsureServicesAdvertised(ctx context.Context, services []string, lc localclient.LocalClient, logf logger.Logf) error { + if _, err := lc.EditPrefs(ctx, &ipn.MaskedPrefs{ + AdvertiseServicesSet: true, + Prefs: ipn.Prefs{ + AdvertiseServices: services, + }, + }); err != nil { + // EditPrefs only returns an error if it fails _set_ its local prefs. + // If it fails to _persist_ the prefs in state, we don't get an error + // and we continue waiting below, as control will failover as usual. + return fmt.Errorf("error setting prefs AdvertiseServices: %w", err) + } + + // Services use the same (failover XOR regional routing) mechanism that + // HA subnet routers use. Unfortunately we don't yet get a reliable signal + // from control that it's responded to our unadvertisement, so the best we + // can do is wait for 20 seconds, where 15s is the approximate maximum time + // it should take for control to choose a new primary, and 5s is for buffer. + // + // Note: There is no guarantee that clients have been _informed_ of the new + // primary no matter how long we wait. We would need a mechanism to await + // netmap updates for peers to know for sure. + // + // See https://tailscale.com/kb/1115/high-availability for more details. + // TODO(tomhjp): Wait for a netmap update instead of sleeping when control + // supports that. + select { + case <-ctx.Done(): + return nil + case <-time.After(20 * time.Second): + return nil + } +} + // EnsureServicesNotAdvertised is a function that gets called on containerboot // or k8s-proxy termination and ensures that any currently advertised Services // get unadvertised to give clients time to switch to another node before this diff --git a/kube/state/state.go b/kube/state/state.go index 2605f0952f708..ebedb2f725b3d 100644 --- a/kube/state/state.go +++ b/kube/state/state.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/state/state_test.go b/kube/state/state_test.go index 8701aa1b7fa65..9b2ce69be5599 100644 --- a/kube/state/state_test.go +++ b/kube/state/state_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/license_test.go b/license_test.go index 9b62c48ed218e..cac195c49c5a4 100644 --- a/license_test.go +++ b/license_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscaleroot @@ -23,7 +23,7 @@ func normalizeLineEndings(b []byte) []byte { // directory tree have a correct-looking Tailscale license header. func TestLicenseHeaders(t *testing.T) { want := normalizeLineEndings([]byte(strings.TrimLeft(` -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause `, "\n"))) diff --git a/licenses/android.md b/licenses/android.md index 4dc8e6c6de06c..15098f0752e79 100644 --- a/licenses/android.md +++ b/licenses/android.md @@ -8,26 +8,26 @@ Client][]. See also the dependencies in the [Tailscale CLI][]. ## Go Packages - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE)) - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE)) - - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) + - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) + - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.26.1/LICENSE)) - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/ebf49471dced/LICENSE)) - - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE)) - - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE)) + - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) + - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) - [github.com/google/go-tpm](https://pkg.go.dev/github.com/google/go-tpm) ([Apache-2.0](https://github.com/google/go-tpm/blob/v0.9.4/LICENSE)) - [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.2.0/LICENSE)) + - [github.com/huin/goupnp](https://pkg.go.dev/github.com/huin/goupnp) ([BSD-2-Clause](https://github.com/huin/goupnp/blob/v1.3.0/LICENSE)) - [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/8c70d406f6d2/LICENSE)) - [github.com/jellydator/ttlcache/v3](https://pkg.go.dev/github.com/jellydator/ttlcache/v3) ([MIT](https://github.com/jellydator/ttlcache/blob/v3.1.0/LICENSE)) - - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.0/LICENSE)) - - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE)) - - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt)) + - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.2/LICENSE)) + - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.2/internal/snapref/LICENSE)) + - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE)) - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.21/LICENSE)) + - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.25/LICENSE)) - [github.com/pires/go-proxyproto](https://pkg.go.dev/github.com/pires/go-proxyproto) ([Apache-2.0](https://github.com/pires/go-proxyproto/blob/v0.8.1/LICENSE)) - - [github.com/huin/goupnp](https://pkg.go.dev/github.com/huin/goupnp) ([BSD-2-Clause](https://github.com/huin/goupnp/blob/v1.3.0/LICENSE)) - [github.com/tailscale/peercred](https://pkg.go.dev/github.com/tailscale/peercred) ([BSD-3-Clause](https://github.com/tailscale/peercred/blob/35a0c7bd7edc/LICENSE)) - [github.com/tailscale/tailscale-android/libtailscale](https://pkg.go.dev/github.com/tailscale/tailscale-android/libtailscale) ([BSD-3-Clause](https://github.com/tailscale/tailscale-android/blob/HEAD/LICENSE)) - [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/1d0488a3d7da/LICENSE)) @@ -36,16 +36,16 @@ Client][]. See also the dependencies in the [Tailscale CLI][]. - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/b7579e27:LICENSE)) - [golang.org/x/mobile](https://pkg.go.dev/golang.org/x/mobile) ([BSD-3-Clause](https://cs.opensource.google/go/x/mobile/+/81131f64:LICENSE)) - [golang.org/x/mod/semver](https://pkg.go.dev/golang.org/x/mod/semver) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) - - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.18.0:LICENSE)) - - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.38.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.37.0:LICENSE)) - - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.11.0:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) + - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) + - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) + - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) + - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - [golang.org/x/tools](https://pkg.go.dev/golang.org/x/tools) ([BSD-3-Clause](https://cs.opensource.google/go/x/tools/+/v0.39.0:LICENSE)) - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) diff --git a/licenses/apple.md b/licenses/apple.md index c3f2d3bb7a3c3..f7989fe250a63 100644 --- a/licenses/apple.md +++ b/licenses/apple.md @@ -11,55 +11,56 @@ See also the dependencies in the [Tailscale CLI][]. ## Go Packages - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) - - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.39.6/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.29.5/config/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.17.58/credentials/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.16.27/feature/ec2/imds/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.4.13/internal/configsources/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.7.13/internal/endpoints/v2/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.8.2/internal/ini/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.39.6/internal/sync/singleflight/LICENSE)) - - [github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.12.2/service/internal/accept-encoding/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.12.12/service/internal/presigned-url/LICENSE.txt)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE)) + - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.32.5/config/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.19.5/credentials/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.18.16/feature/ec2/imds/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.4.16/internal/configsources/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.7.16/internal/endpoints/v2/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.8.4/internal/ini/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/internal/sync/singleflight/LICENSE)) + - [github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.13.4/service/internal/accept-encoding/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.13.16/service/internal/presigned-url/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/signin](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/signin) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/signin/v1.0.4/service/signin/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.45.0/service/ssm/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.24.14/service/sso/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.28.13/service/ssooidc/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.33.13/service/sts/LICENSE.txt)) - - [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.23.2/LICENSE)) - - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.23.2/internal/sync/singleflight/LICENSE)) + - [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.30.7/service/sso/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.35.12/service/ssooidc/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.41.5/service/sts/LICENSE.txt)) + - [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.24.0/LICENSE)) + - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.24.0/internal/sync/singleflight/LICENSE)) - [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/65c67c9f46e6/LICENSE)) - - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) + - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.8.1/LICENSE)) - [github.com/digitalocean/go-smbios/smbios](https://pkg.go.dev/github.com/digitalocean/go-smbios/smbios) ([Apache-2.0](https://github.com/digitalocean/go-smbios/blob/390a4f403a8e/LICENSE.md)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE)) - - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) - - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/cc2cfa0554c3/LICENSE)) + - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) + - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.26.1/LICENSE)) + - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/4849db3c2f7e/LICENSE)) - [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/76236955d466/LICENSE)) - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) - - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE)) + - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) - [github.com/google/nftables](https://pkg.go.dev/github.com/google/nftables) ([Apache-2.0](https://github.com/google/nftables/blob/5e242ec57806/LICENSE)) - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) - [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.2.0/LICENSE)) + - [github.com/huin/goupnp](https://pkg.go.dev/github.com/huin/goupnp) ([BSD-2-Clause](https://github.com/huin/goupnp/blob/v1.3.0/LICENSE)) - [github.com/illarion/gonotify/v3](https://pkg.go.dev/github.com/illarion/gonotify/v3) ([MIT](https://github.com/illarion/gonotify/blob/v3.0.2/LICENSE)) - [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/15c9b8791914/LICENSE)) - [github.com/jellydator/ttlcache/v3](https://pkg.go.dev/github.com/jellydator/ttlcache/v3) ([MIT](https://github.com/jellydator/ttlcache/blob/v3.1.0/LICENSE)) - [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE)) - [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.4.1/LICENSE.md)) - - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.0/LICENSE)) - - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE)) - - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt)) + - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.2/LICENSE)) + - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.2/internal/snapref/LICENSE)) + - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE)) - [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md)) - [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/fbb4dce95f42/LICENSE.md)) - [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md)) - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md)) - - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.22/LICENSE)) + - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.25/LICENSE)) - [github.com/pires/go-proxyproto](https://pkg.go.dev/github.com/pires/go-proxyproto) ([Apache-2.0](https://github.com/pires/go-proxyproto/blob/v0.8.1/LICENSE)) - [github.com/prometheus-community/pro-bing](https://pkg.go.dev/github.com/prometheus-community/pro-bing) ([MIT](https://github.com/prometheus-community/pro-bing/blob/v0.4.0/LICENSE)) - [github.com/safchain/ethtool](https://pkg.go.dev/github.com/safchain/ethtool) ([Apache-2.0](https://github.com/safchain/ethtool/blob/v0.3.0/LICENSE)) - - [github.com/huin/goupnp](https://pkg.go.dev/github.com/huin/goupnp) ([BSD-2-Clause](https://github.com/huin/goupnp/blob/v1.3.0/LICENSE)) - [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/4d49adab4de7/LICENSE)) - [github.com/tailscale/peercred](https://pkg.go.dev/github.com/tailscale/peercred) ([BSD-3-Clause](https://github.com/tailscale/peercred/blob/35a0c7bd7edc/LICENSE)) - [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/1d0488a3d7da/LICENSE)) @@ -69,15 +70,15 @@ See also the dependencies in the [Tailscale CLI][]. - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE)) - - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/df929982:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) - - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.18.0:LICENSE)) - - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.38.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.37.0:LICENSE)) - - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE)) + - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/a4bb9ffd:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.49.0:LICENSE)) + - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) + - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE)) + - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) + - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/573d5e7127a8/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) ## Additional Dependencies diff --git a/licenses/licenses.go b/licenses/licenses.go index 5e59edb9f7b75..a4bf51befe773 100644 --- a/licenses/licenses.go +++ b/licenses/licenses.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package licenses provides utilities for working with open source licenses. diff --git a/licenses/tailscale.md b/licenses/tailscale.md index 85c0f33fc09d2..5050b38db2178 100644 --- a/licenses/tailscale.md +++ b/licenses/tailscale.md @@ -13,54 +13,54 @@ well as an [option for macOS][]. Some packages may only be included on certain architectures or operating systems. - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE)) - [fyne.io/systray](https://pkg.go.dev/fyne.io/systray) ([Apache-2.0](https://github.com/fyne-io/systray/blob/4856ac3adc3c/LICENSE)) - [github.com/Kodeworks/golang-image-ico](https://pkg.go.dev/github.com/Kodeworks/golang-image-ico) ([BSD-3-Clause](https://github.com/Kodeworks/golang-image-ico/blob/73f0f4cfade9/LICENSE)) - [github.com/akutz/memconn](https://pkg.go.dev/github.com/akutz/memconn) ([Apache-2.0](https://github.com/akutz/memconn/blob/v0.1.0/LICENSE)) - [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/1a75b4708caa/LICENSE)) - [github.com/anmitsu/go-shlex](https://pkg.go.dev/github.com/anmitsu/go-shlex) ([MIT](https://github.com/anmitsu/go-shlex/blob/38f4b401e2be/LICENSE)) - [github.com/atotto/clipboard](https://pkg.go.dev/github.com/atotto/clipboard) ([BSD-3-Clause](https://github.com/atotto/clipboard/blob/v0.1.4/LICENSE)) - - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.36.0/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.29.5/config/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.17.58/credentials/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.16.27/feature/ec2/imds/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.3.31/internal/configsources/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.6.31/internal/endpoints/v2/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.4.16/internal/configsources/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.7.16/internal/endpoints/v2/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.8.2/internal/ini/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.36.0/internal/sync/singleflight/LICENSE)) - - [github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.12.2/service/internal/accept-encoding/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.12.12/service/internal/presigned-url/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/internal/sync/singleflight/LICENSE)) + - [github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.13.4/service/internal/accept-encoding/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.13.16/service/internal/presigned-url/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.44.7/service/ssm/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.24.14/service/sso/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.28.13/service/ssooidc/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.33.13/service/sts/LICENSE.txt)) - - [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.22.2/LICENSE)) - - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.22.2/internal/sync/singleflight/LICENSE)) + - [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.41.5/service/sts/LICENSE.txt)) + - [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.24.0/LICENSE)) + - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.24.0/internal/sync/singleflight/LICENSE)) - [github.com/coder/websocket](https://pkg.go.dev/github.com/coder/websocket) ([ISC](https://github.com/coder/websocket/blob/v1.8.12/LICENSE.txt)) - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) - - [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.23/LICENSE)) + - [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.24/LICENSE)) - [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/a09d6be7affa/LICENSE)) - [github.com/digitalocean/go-smbios/smbios](https://pkg.go.dev/github.com/digitalocean/go-smbios/smbios) ([Apache-2.0](https://github.com/digitalocean/go-smbios/blob/390a4f403a8e/LICENSE.md)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - [github.com/fogleman/gg](https://pkg.go.dev/github.com/fogleman/gg) ([MIT](https://github.com/fogleman/gg/blob/v1.3.0/LICENSE.md)) - - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE)) - - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) + - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) + - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.26.1/LICENSE)) - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/ebf49471dced/LICENSE)) - [github.com/go-ole/go-ole](https://pkg.go.dev/github.com/go-ole/go-ole) ([MIT](https://github.com/go-ole/go-ole/blob/v1.3.0/LICENSE)) - [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/76236955d466/LICENSE)) - [github.com/golang/freetype/raster](https://pkg.go.dev/github.com/golang/freetype/raster) ([Unknown](Unknown)) - [github.com/golang/freetype/truetype](https://pkg.go.dev/github.com/golang/freetype/truetype) ([Unknown](Unknown)) - - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE)) - - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE)) + - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) + - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) - [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.2.0/LICENSE)) - [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/8c70d406f6d2/LICENSE)) - [github.com/jellydator/ttlcache/v3](https://pkg.go.dev/github.com/jellydator/ttlcache/v3) ([MIT](https://github.com/jellydator/ttlcache/blob/v3.1.0/LICENSE)) - [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE)) - [github.com/kballard/go-shellquote](https://pkg.go.dev/github.com/kballard/go-shellquote) ([MIT](https://github.com/kballard/go-shellquote/blob/95032a82bc51/LICENSE)) - - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.0/LICENSE)) - - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE)) - - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt)) + - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.2/LICENSE)) + - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.2/internal/snapref/LICENSE)) + - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE)) - [github.com/kr/fs](https://pkg.go.dev/github.com/kr/fs) ([BSD-3-Clause](https://github.com/kr/fs/blob/v0.1.0/LICENSE)) - [github.com/mattn/go-colorable](https://pkg.go.dev/github.com/mattn/go-colorable) ([MIT](https://github.com/mattn/go-colorable/blob/v0.1.13/LICENSE)) @@ -68,7 +68,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md)) - [github.com/peterbourgon/ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3) ([Apache-2.0](https://github.com/peterbourgon/ff/blob/v3.4.0/LICENSE)) - - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.21/LICENSE)) + - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.25/LICENSE)) - [github.com/pires/go-proxyproto](https://pkg.go.dev/github.com/pires/go-proxyproto) ([Apache-2.0](https://github.com/pires/go-proxyproto/blob/v0.8.1/LICENSE)) - [github.com/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) ([BSD-2-Clause](https://github.com/pkg/sftp/blob/v1.13.6/LICENSE)) - [github.com/prometheus-community/pro-bing](https://pkg.go.dev/github.com/prometheus-community/pro-bing) ([MIT](https://github.com/prometheus-community/pro-bing/blob/v0.4.0/LICENSE)) @@ -83,24 +83,24 @@ Some packages may only be included on certain architectures or operating systems - [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/v0.14.0/LICENSE)) - [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/d2acac8f3701/LICENSE)) - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) + - [go.yaml.in/yaml/v2](https://pkg.go.dev/go.yaml.in/yaml/v2) ([Apache-2.0](https://github.com/yaml/go-yaml/blob/v2.4.2/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/b7579e27:LICENSE)) - [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.27.0:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) - - [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.30.0:LICENSE)) - - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.18.0:LICENSE)) - - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.38.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.37.0:LICENSE)) - - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.11.0:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) + - [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.33.0:LICENSE)) + - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) + - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) + - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) + - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2)) - [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3)) - - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) - - [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.32.0/LICENSE)) - - [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([Apache-2.0](https://github.com/kubernetes-sigs/yaml/blob/v1.4.0/LICENSE)) - - [sigs.k8s.io/yaml/goyaml.v2](https://pkg.go.dev/sigs.k8s.io/yaml/goyaml.v2) ([Apache-2.0](https://github.com/kubernetes-sigs/yaml/blob/v1.4.0/goyaml.v2/LICENSE)) + - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/573d5e7127a8/LICENSE)) + - [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.34.0/LICENSE)) + - [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([Apache-2.0](https://github.com/kubernetes-sigs/yaml/blob/v1.6.0/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) - [tailscale.com/tempfork/gliderlabs/ssh](https://pkg.go.dev/tailscale.com/tempfork/gliderlabs/ssh) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/tempfork/gliderlabs/ssh/LICENSE)) - [tailscale.com/tempfork/spf13/cobra](https://pkg.go.dev/tailscale.com/tempfork/spf13/cobra) ([Apache-2.0](https://github.com/tailscale/tailscale/blob/HEAD/tempfork/spf13/cobra/LICENSE.txt)) diff --git a/licenses/windows.md b/licenses/windows.md index 0b8344b4d66d4..e8bcc932f332f 100644 --- a/licenses/windows.md +++ b/licenses/windows.md @@ -9,28 +9,28 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. ## Go Packages - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE)) - [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE)) - [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE)) - [github.com/beorn7/perks/quantile](https://pkg.go.dev/github.com/beorn7/perks/quantile) ([MIT](https://github.com/beorn7/perks/blob/v1.0.1/LICENSE)) - [github.com/cespare/xxhash/v2](https://pkg.go.dev/github.com/cespare/xxhash/v2) ([MIT](https://github.com/cespare/xxhash/blob/v2.3.0/LICENSE.txt)) - [github.com/coder/websocket](https://pkg.go.dev/github.com/coder/websocket) ([ISC](https://github.com/coder/websocket/blob/v1.8.12/LICENSE.txt)) - - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) + - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.8.1/LICENSE)) - [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/b75a8a7d7eb0/LICENSE)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE)) - - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/cc2cfa0554c3/LICENSE)) + - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) + - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/4849db3c2f7e/LICENSE)) - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) - - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE)) + - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) - [github.com/google/go-cmp/cmp](https://pkg.go.dev/github.com/google/go-cmp/cmp) ([BSD-3-Clause](https://github.com/google/go-cmp/blob/v0.7.0/LICENSE)) - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) - [github.com/gregjones/httpcache](https://pkg.go.dev/github.com/gregjones/httpcache) ([MIT](https://github.com/gregjones/httpcache/blob/901d90724c79/LICENSE.txt)) - [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.2.0/LICENSE)) - [github.com/jellydator/ttlcache/v3](https://pkg.go.dev/github.com/jellydator/ttlcache/v3) ([MIT](https://github.com/jellydator/ttlcache/blob/v3.1.0/LICENSE)) - [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.4.1/LICENSE.md)) - - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.0/LICENSE)) - - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE)) - - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt)) + - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.2/LICENSE)) + - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.2/internal/snapref/LICENSE)) + - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/fbb4dce95f42/LICENSE.md)) - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md)) @@ -39,7 +39,7 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. - [github.com/peterbourgon/diskv](https://pkg.go.dev/github.com/peterbourgon/diskv) ([MIT](https://github.com/peterbourgon/diskv/blob/v2.0.1/LICENSE)) - [github.com/prometheus/client_golang/prometheus](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus) ([Apache-2.0](https://github.com/prometheus/client_golang/blob/v1.23.2/LICENSE)) - [github.com/prometheus/client_model/go](https://pkg.go.dev/github.com/prometheus/client_model/go) ([Apache-2.0](https://github.com/prometheus/client_model/blob/v0.6.2/LICENSE)) - - [github.com/prometheus/common](https://pkg.go.dev/github.com/prometheus/common) ([Apache-2.0](https://github.com/prometheus/common/blob/v0.66.1/LICENSE)) + - [github.com/prometheus/common](https://pkg.go.dev/github.com/prometheus/common) ([Apache-2.0](https://github.com/prometheus/common/blob/v0.67.5/LICENSE)) - [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE)) - [github.com/tailscale/go-winio](https://pkg.go.dev/github.com/tailscale/go-winio) ([MIT](https://github.com/tailscale/go-winio/blob/c4f33415bf55/LICENSE)) - [github.com/tailscale/hujson](https://pkg.go.dev/github.com/tailscale/hujson) ([BSD-3-Clause](https://github.com/tailscale/hujson/blob/992244df8c5a/LICENSE)) @@ -48,20 +48,20 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. - [github.com/tailscale/xnet/webdav](https://pkg.go.dev/github.com/tailscale/xnet/webdav) ([BSD-3-Clause](https://github.com/tailscale/xnet/blob/8497ac4dab2e/LICENSE)) - [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.2.1/LICENSE)) - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) - - [go.yaml.in/yaml/v2](https://pkg.go.dev/go.yaml.in/yaml/v2) ([Apache-2.0](https://github.com/yaml/go-yaml/blob/v2.4.2/LICENSE)) + - [go.yaml.in/yaml/v2](https://pkg.go.dev/go.yaml.in/yaml/v2) ([Apache-2.0](https://github.com/yaml/go-yaml/blob/v2.4.3/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE)) - - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/df929982:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE)) + - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/a4bb9ffd:LICENSE)) - [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.27.0:LICENSE)) - - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) - - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.18.0:LICENSE)) - - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.38.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.37.0:LICENSE)) + - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.32.0:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.49.0:LICENSE)) + - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) + - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE)) - [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2)) - [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3)) - - [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.8/LICENSE)) + - [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) - [gopkg.in/Knetic/govaluate.v3](https://pkg.go.dev/gopkg.in/Knetic/govaluate.v3) ([MIT](https://github.com/Knetic/govaluate/blob/v3.0.0/LICENSE)) - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) diff --git a/log/filelogger/log.go b/log/filelogger/log.go index 599e5237b3e22..268cf1bba7583 100644 --- a/log/filelogger/log.go +++ b/log/filelogger/log.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package filelogger provides localdisk log writing & rotation, primarily for Windows diff --git a/log/filelogger/log_test.go b/log/filelogger/log_test.go index dfa489637f720..32c3d0e90bf1b 100644 --- a/log/filelogger/log_test.go +++ b/log/filelogger/log_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filelogger diff --git a/log/sockstatlog/logger.go b/log/sockstatlog/logger.go index 8ddfabb866745..30d16fbcc8c6c 100644 --- a/log/sockstatlog/logger.go +++ b/log/sockstatlog/logger.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sockstatlog provides a logger for capturing network socket stats for debugging. diff --git a/log/sockstatlog/logger_test.go b/log/sockstatlog/logger_test.go index e5c2feb2986d8..66228731e368e 100644 --- a/log/sockstatlog/logger_test.go +++ b/log/sockstatlog/logger_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sockstatlog diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index f7491783ad781..7a0027dad74ea 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package logpolicy manages the creation or reuse of logtail loggers, diff --git a/logpolicy/logpolicy_test.go b/logpolicy/logpolicy_test.go index c09e590bb8399..64e54467c496b 100644 --- a/logpolicy/logpolicy_test.go +++ b/logpolicy/logpolicy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logpolicy diff --git a/logpolicy/maybe_syspolicy.go b/logpolicy/maybe_syspolicy.go index 8b2836c97411c..7cdaabcc7c77a 100644 --- a/logpolicy/maybe_syspolicy.go +++ b/logpolicy/maybe_syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/logtail/buffer.go b/logtail/buffer.go index 6efdbda63ac8e..bc39783ea768a 100644 --- a/logtail/buffer.go +++ b/logtail/buffer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail diff --git a/logtail/config.go b/logtail/config.go index bf47dd8aa7b52..c504047a3f2bf 100644 --- a/logtail/config.go +++ b/logtail/config.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logtail diff --git a/logtail/example/logadopt/logadopt.go b/logtail/example/logadopt/logadopt.go index eba3f93112d62..f9104231644b4 100644 --- a/logtail/example/logadopt/logadopt.go +++ b/logtail/example/logadopt/logadopt.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command logadopt is a CLI tool to adopt a machine into a logtail collection. diff --git a/logtail/example/logreprocess/demo.sh b/logtail/example/logreprocess/demo.sh index 583929c12b4fe..89ff476c248cb 100755 --- a/logtail/example/logreprocess/demo.sh +++ b/logtail/example/logreprocess/demo.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # diff --git a/logtail/example/logreprocess/logreprocess.go b/logtail/example/logreprocess/logreprocess.go index aae65df9f1321..d434da1b187db 100644 --- a/logtail/example/logreprocess/logreprocess.go +++ b/logtail/example/logreprocess/logreprocess.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The logreprocess program tails a log and reprocesses it. diff --git a/logtail/example/logtail/logtail.go b/logtail/example/logtail/logtail.go index 0c9e442584410..24f98090f57c8 100644 --- a/logtail/example/logtail/logtail.go +++ b/logtail/example/logtail/logtail.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The logtail program logs stdin. diff --git a/logtail/filch/filch.go b/logtail/filch/filch.go index 88c72f233daab..8ae9123060e73 100644 --- a/logtail/filch/filch.go +++ b/logtail/filch/filch.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail @@ -297,12 +297,6 @@ func stderrWriteForTest(b []byte) int { return must.Get(os.Stderr.Write(b)) } -// waitIdleStderrForTest waits until there are no active stderrWriteForTest calls. -func waitIdleStderrForTest() { - activeStderrWriteForTest.Lock() - defer activeStderrWriteForTest.Unlock() -} - // rotateLocked swaps f.newer and f.older such that: // // - f.newer will be truncated and future writes will be appended to the end. @@ -316,7 +310,7 @@ func waitIdleStderrForTest() { // No data should be lost under this condition. // // - The writer exceeded a limit for f.newer. -// Data may be lost under this cxondition. +// Data may be lost under this condition. func (f *Filch) rotateLocked() error { f.rotateCalls.Add(1) @@ -329,7 +323,6 @@ func (f *Filch) rotateLocked() error { rdPos := pos - int64(len(f.unreadReadBuffer())) // adjust for data already read into the read buffer f.droppedBytes.Add(max(0, fi.Size()-rdPos)) } - f.resetReadBuffer() // Truncate the older file and write relative to the start. if err := f.older.Truncate(0); err != nil { @@ -339,6 +332,7 @@ func (f *Filch) rotateLocked() error { return err } } + f.resetReadBuffer() // Swap newer and older. f.newer, f.older = f.older, f.newer @@ -350,8 +344,15 @@ func (f *Filch) rotateLocked() error { // Note that mutex does not prevent stderr writes. prevSize := f.newlyWrittenBytes + f.newlyFilchedBytes f.newlyWrittenBytes, f.newlyFilchedBytes = 0, 0 + + // Hold the write lock around dup2 to prevent concurrent + // stderrWriteForTest calls from racing with dup2 on the same fd. + // On macOS, dup2 and write are not atomic with respect to each other, + // so a concurrent write can observe a bad file descriptor. + activeStderrWriteForTest.Lock() if f.OrigStderr != nil { if err := dup2Stderr(f.newer); err != nil { + activeStderrWriteForTest.Unlock() return err } } @@ -369,15 +370,15 @@ func (f *Filch) rotateLocked() error { // In rare cases, it is possible that [Filch.TryReadLine] consumes // the entire older file before the write commits, // leading to dropped stderr lines. - waitIdleStderrForTest() - if fi, err := f.older.Stat(); err != nil { + fi, err := f.older.Stat() + activeStderrWriteForTest.Unlock() + if err != nil { return err - } else { - filchedBytes := max(0, fi.Size()-prevSize) - f.writeBytes.Add(filchedBytes) - f.filchedBytes.Add(filchedBytes) - f.storedBytes.Set(fi.Size()) // newer has been truncated, so only older matters } + filchedBytes := max(0, fi.Size()-prevSize) + f.writeBytes.Add(filchedBytes) + f.filchedBytes.Add(filchedBytes) + f.storedBytes.Set(fi.Size()) // newer has been truncated, so only older matters // Start reading from the start of older. if _, err := f.older.Seek(0, io.SeekStart); err != nil { diff --git a/logtail/filch/filch_omit.go b/logtail/filch/filch_omit.go index 898978e2152ea..c4edc1bcd392f 100644 --- a/logtail/filch/filch_omit.go +++ b/logtail/filch/filch_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_logtail diff --git a/logtail/filch/filch_stub.go b/logtail/filch/filch_stub.go index f2aeeb9b9f819..0bb2c306c05dc 100644 --- a/logtail/filch/filch_stub.go +++ b/logtail/filch/filch_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail && (wasm || plan9 || tamago) diff --git a/logtail/filch/filch_test.go b/logtail/filch/filch_test.go index 1e33471809dbb..2538233cfd84c 100644 --- a/logtail/filch/filch_test.go +++ b/logtail/filch/filch_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filch @@ -381,3 +381,28 @@ func testMaxFileSize(t *testing.T, replaceStderr bool) { t.Errorf("readBytes = %v, want %v", f.readBytes.Value(), readBytes) } } + +// TestConcurrentSameFile tests that concurrent Filch operations on the same +// set of log files does not result in a panic. +// The exact behavior is undefined, but we should at least avoid a panic. +func TestConcurrentSameFile(t *testing.T) { + filePrefix := filepath.Join(t.TempDir(), "testlog") + f1 := must.Get(New(filePrefix, Options{MaxFileSize: 1000})) + defer f1.Close() + f2 := must.Get(New(filePrefix, Options{MaxFileSize: 1000})) + defer f2.Close() + var group sync.WaitGroup + for _, f := range []*Filch{f1, f2} { + group.Go(func() { + for range 1000 { + for range rand.IntN(10) { + f.Write([]byte("hello, world")) + } + for range rand.IntN(10) { + f.TryReadLine() + } + } + }) + } + group.Wait() +} diff --git a/logtail/filch/filch_unix.go b/logtail/filch/filch_unix.go index 27f1d02ee86aa..0817e131190bb 100644 --- a/logtail/filch/filch_unix.go +++ b/logtail/filch/filch_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail && !windows && !wasm && !plan9 && !tamago diff --git a/logtail/filch/filch_windows.go b/logtail/filch/filch_windows.go index b08b64db39f61..3bffe8662396d 100644 --- a/logtail/filch/filch_windows.go +++ b/logtail/filch/filch_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail && windows diff --git a/logtail/logtail.go b/logtail/logtail.go index ce50c1c0a7f52..ef296568da957 100644 --- a/logtail/logtail.go +++ b/logtail/logtail.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail diff --git a/logtail/logtail_omit.go b/logtail/logtail_omit.go index 814fd3be90d8e..21f18c980cce4 100644 --- a/logtail/logtail_omit.go +++ b/logtail/logtail_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_logtail diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index b618fc0d7bc65..67250ae0db03f 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logtail diff --git a/maths/ewma.go b/maths/ewma.go index 0897b73e4727f..1946081cf6d08 100644 --- a/maths/ewma.go +++ b/maths/ewma.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package maths contains additional mathematical functions or structures not diff --git a/maths/ewma_test.go b/maths/ewma_test.go index 307078a38ebdf..9fddf34e17193 100644 --- a/maths/ewma_test.go +++ b/maths/ewma_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package maths diff --git a/metrics/fds_linux.go b/metrics/fds_linux.go index 34740c2bb1c74..b0abf946b7ef6 100644 --- a/metrics/fds_linux.go +++ b/metrics/fds_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/metrics/fds_notlinux.go b/metrics/fds_notlinux.go index 2dae97cad86b9..1877830134ec4 100644 --- a/metrics/fds_notlinux.go +++ b/metrics/fds_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/metrics/metrics.go b/metrics/metrics.go index 092b56c41b6dc..6540dcc13d66d 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package metrics contains expvar & Prometheus types and code used by diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index a808d5a73eb3e..8478cdedc8de8 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/metrics/multilabelmap.go b/metrics/multilabelmap.go index 223a55a75bf1b..54d41bbae9ef2 100644 --- a/metrics/multilabelmap.go +++ b/metrics/multilabelmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/metrics/multilabelmap_test.go b/metrics/multilabelmap_test.go index 195696234e545..70554c63e50a0 100644 --- a/metrics/multilabelmap_test.go +++ b/metrics/multilabelmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/net/ace/ace.go b/net/ace/ace.go index 47e780313cadd..9b1264dc0927b 100644 --- a/net/ace/ace.go +++ b/net/ace/ace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ace implements a Dialer that dials via a Tailscale ACE (CONNECT) diff --git a/net/art/art_test.go b/net/art/art_test.go index daf8553ca020d..004f31b8ba3b5 100644 --- a/net/art/art_test.go +++ b/net/art/art_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package art diff --git a/net/art/stride_table.go b/net/art/stride_table.go index 5050df24500ce..6cdb0b5a0a1e9 100644 --- a/net/art/stride_table.go +++ b/net/art/stride_table.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package art diff --git a/net/art/stride_table_test.go b/net/art/stride_table_test.go index 4ccef1fe083cb..e797f40ee0ddc 100644 --- a/net/art/stride_table_test.go +++ b/net/art/stride_table_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package art diff --git a/net/art/table.go b/net/art/table.go index fa397577868a8..447a56b394182 100644 --- a/net/art/table.go +++ b/net/art/table.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package art provides a routing table that implements the Allotment Routing diff --git a/net/art/table_test.go b/net/art/table_test.go index a129c8484ddcd..5c35ac7dafd77 100644 --- a/net/art/table_test.go +++ b/net/art/table_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package art diff --git a/net/bakedroots/bakedroots.go b/net/bakedroots/bakedroots.go index b268b1546caac..70d947c6b725d 100644 --- a/net/bakedroots/bakedroots.go +++ b/net/bakedroots/bakedroots.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package bakedroots contains WebPKI CA roots we bake into the tailscaled binary, diff --git a/net/bakedroots/bakedroots_test.go b/net/bakedroots/bakedroots_test.go index 8ba502a7827e0..12a656d4edae9 100644 --- a/net/bakedroots/bakedroots_test.go +++ b/net/bakedroots/bakedroots_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package bakedroots diff --git a/net/batching/conn.go b/net/batching/conn.go index 77cdf8c849ca4..1631c33cfe448 100644 --- a/net/batching/conn.go +++ b/net/batching/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package batching implements a socket optimized for increased throughput. diff --git a/net/batching/conn_default.go b/net/batching/conn_default.go index 37d644f50624c..0d208578b6d06 100644 --- a/net/batching/conn_default.go +++ b/net/batching/conn_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/net/batching/conn_linux.go b/net/batching/conn_linux.go index bd7ac25be2a4d..373625b772738 100644 --- a/net/batching/conn_linux.go +++ b/net/batching/conn_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package batching diff --git a/net/batching/conn_linux_test.go b/net/batching/conn_linux_test.go index c2cc463ebc6ad..a15de4f671ec6 100644 --- a/net/batching/conn_linux_test.go +++ b/net/batching/conn_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package batching diff --git a/net/captivedetection/captivedetection.go b/net/captivedetection/captivedetection.go index 3ec820b794400..dfd4bbd875608 100644 --- a/net/captivedetection/captivedetection.go +++ b/net/captivedetection/captivedetection.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package captivedetection provides a way to detect if the system is connected to a network that has diff --git a/net/captivedetection/captivedetection_test.go b/net/captivedetection/captivedetection_test.go index 0778e07df393a..2aa660d88d0a4 100644 --- a/net/captivedetection/captivedetection_test.go +++ b/net/captivedetection/captivedetection_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package captivedetection diff --git a/net/captivedetection/endpoints.go b/net/captivedetection/endpoints.go index 57b3e53351a1a..5c1d31d0c35a4 100644 --- a/net/captivedetection/endpoints.go +++ b/net/captivedetection/endpoints.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package captivedetection diff --git a/net/captivedetection/rawconn.go b/net/captivedetection/rawconn.go index a7197d9df2577..3c6f65f84692e 100644 --- a/net/captivedetection/rawconn.go +++ b/net/captivedetection/rawconn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(ios || darwin) diff --git a/net/captivedetection/rawconn_apple.go b/net/captivedetection/rawconn_apple.go index 12b4446e62eb8..ee8e7c4c3f6b9 100644 --- a/net/captivedetection/rawconn_apple.go +++ b/net/captivedetection/rawconn_apple.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || darwin diff --git a/net/connectproxy/connectproxy.go b/net/connectproxy/connectproxy.go index 4bf6875029554..a63c6acf7b7c8 100644 --- a/net/connectproxy/connectproxy.go +++ b/net/connectproxy/connectproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package connectproxy contains some CONNECT proxy code. diff --git a/net/dns/config.go b/net/dns/config.go index 2425b304dffd8..0b09fe1a8f609 100644 --- a/net/dns/config.go +++ b/net/dns/config.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:generate go run tailscale.com/cmd/viewer --type=Config --clonefunc @@ -21,10 +21,15 @@ import ( "tailscale.com/net/tsaddr" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) // Config is a DNS configuration. type Config struct { + // AcceptDNS true if [Prefs.CorpDNS] is enabled (or --accept-dns=true). + // This should be used for error handling and health reporting + // purposes only. + AcceptDNS bool // DefaultResolvers are the DNS resolvers to use for DNS names // which aren't covered by more specific per-domain routes below. // If empty, the OS's default resolvers (the ones that predate @@ -48,6 +53,11 @@ type Config struct { // it to resolve, you also need to add appropriate routes to // Routes. Hosts map[dnsname.FQDN][]netip.Addr + // SubdomainHosts is a set of FQDNs from Hosts that should also + // resolve subdomain queries to the same IPs. For example, if + // "node.tailnet.ts.net" is in SubdomainHosts, then queries for + // "anything.node.tailnet.ts.net" will resolve to node's IPs. + SubdomainHosts set.Set[dnsname.FQDN] // OnlyIPv6, if true, uses the IPv6 service IP (for MagicDNS) // instead of the IPv4 version (100.100.100.100). OnlyIPv6 bool @@ -63,11 +73,9 @@ func (c *Config) serviceIPs(knobs *controlknobs.Knobs) []netip.Addr { return []netip.Addr{tsaddr.TailscaleServiceIPv6()} } - // TODO(bradfitz,mikeodr,raggi): include IPv6 here too; tailscale/tailscale#15404 - // And add a controlknobs knob to disable dual stack. - // - // For now, opt-in for testing. - if magicDNSDualStack() { + // See https://github.com/tailscale/tailscale/issues/15404 for the background + // on the opt-in debug knob and the controlknob opt-out. + if magicDNSDualStack() || !knobs.ShouldForceRegisterMagicDNSIPv4Only() { return []netip.Addr{ tsaddr.TailscaleServiceIP(), tsaddr.TailscaleServiceIPv6(), diff --git a/net/dns/dbus.go b/net/dns/dbus.go index c53e8b7205949..f80136196376a 100644 --- a/net/dns/dbus.go +++ b/net/dns/dbus.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_dbus diff --git a/net/dns/debian_resolvconf.go b/net/dns/debian_resolvconf.go index 63fd80c1274e8..128b26f2aceca 100644 --- a/net/dns/debian_resolvconf.go +++ b/net/dns/debian_resolvconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || freebsd || openbsd diff --git a/net/dns/direct.go b/net/dns/direct.go index 78495d4737d1d..ec2e42e75176f 100644 --- a/net/dns/direct.go +++ b/net/dns/direct.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android && !ios diff --git a/net/dns/direct_linux_test.go b/net/dns/direct_linux_test.go index 035763a45f30d..8199b41f3b973 100644 --- a/net/dns/direct_linux_test.go +++ b/net/dns/direct_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/dns/direct_test.go b/net/dns/direct_test.go index 07202502e231e..c96323571c1f9 100644 --- a/net/dns/direct_test.go +++ b/net/dns/direct_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/direct_unix_test.go b/net/dns/direct_unix_test.go index bffa6ade943c8..068c5633645a5 100644 --- a/net/dns/direct_unix_test.go +++ b/net/dns/direct_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/net/dns/dns_clone.go b/net/dns/dns_clone.go index 807bfce23df8b..291f96ec2b51f 100644 --- a/net/dns/dns_clone.go +++ b/net/dns/dns_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. @@ -6,10 +6,12 @@ package dns import ( + "maps" "net/netip" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) // Clone makes a deep copy of Config. @@ -43,15 +45,18 @@ func (src *Config) Clone() *Config { dst.Hosts[k] = append([]netip.Addr{}, src.Hosts[k]...) } } + dst.SubdomainHosts = maps.Clone(src.SubdomainHosts) return dst } // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _ConfigCloneNeedsRegeneration = Config(struct { + AcceptDNS bool DefaultResolvers []*dnstype.Resolver Routes map[dnsname.FQDN][]*dnstype.Resolver SearchDomains []dnsname.FQDN Hosts map[dnsname.FQDN][]netip.Addr + SubdomainHosts set.Set[dnsname.FQDN] OnlyIPv6 bool }{}) diff --git a/net/dns/dns_view.go b/net/dns/dns_view.go index c7ce376cba8db..70cb89dcaf128 100644 --- a/net/dns/dns_view.go +++ b/net/dns/dns_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. @@ -15,6 +15,7 @@ import ( "tailscale.com/types/dnstype" "tailscale.com/types/views" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) //go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type=Config @@ -86,6 +87,11 @@ func (v *ConfigView) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return nil } +// AcceptDNS true if [Prefs.CorpDNS] is enabled (or --accept-dns=true). +// This should be used for error handling and health reporting +// purposes only. +func (v ConfigView) AcceptDNS() bool { return v.Đļ.AcceptDNS } + // DefaultResolvers are the DNS resolvers to use for DNS names // which aren't covered by more specific per-domain routes below. // If empty, the OS's default resolvers (the ones that predate @@ -123,6 +129,14 @@ func (v ConfigView) Hosts() views.MapSlice[dnsname.FQDN, netip.Addr] { return views.MapSliceOf(v.Đļ.Hosts) } +// SubdomainHosts is a set of FQDNs from Hosts that should also +// resolve subdomain queries to the same IPs. For example, if +// "node.tailnet.ts.net" is in SubdomainHosts, then queries for +// "anything.node.tailnet.ts.net" will resolve to node's IPs. +func (v ConfigView) SubdomainHosts() views.Map[dnsname.FQDN, struct{}] { + return views.MapOf(v.Đļ.SubdomainHosts) +} + // OnlyIPv6, if true, uses the IPv6 service IP (for MagicDNS) // instead of the IPv4 version (100.100.100.100). func (v ConfigView) OnlyIPv6() bool { return v.Đļ.OnlyIPv6 } @@ -130,9 +144,11 @@ func (v ConfigView) Equal(v2 ConfigView) bool { return v.Đļ.Equal(v2.Đļ) } // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _ConfigViewNeedsRegeneration = Config(struct { + AcceptDNS bool DefaultResolvers []*dnstype.Resolver Routes map[dnsname.FQDN][]*dnstype.Resolver SearchDomains []dnsname.FQDN Hosts map[dnsname.FQDN][]netip.Addr + SubdomainHosts set.Set[dnsname.FQDN] OnlyIPv6 bool }{}) diff --git a/net/dns/flush_default.go b/net/dns/flush_default.go index eb6d9da417104..006373a513a76 100644 --- a/net/dns/flush_default.go +++ b/net/dns/flush_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/dns/flush_windows.go b/net/dns/flush_windows.go index d7c7b7fce515d..a3b1f6b3116af 100644 --- a/net/dns/flush_windows.go +++ b/net/dns/flush_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/ini.go b/net/dns/ini.go index 1e47d606e970f..623362bf56fe2 100644 --- a/net/dns/ini.go +++ b/net/dns/ini.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/net/dns/ini_test.go b/net/dns/ini_test.go index 3afe7009caa27..0e5b966a8674f 100644 --- a/net/dns/ini_test.go +++ b/net/dns/ini_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/net/dns/manager.go b/net/dns/manager.go index 4441c4f69ef70..889c542cf1f1d 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns @@ -46,6 +46,11 @@ var ( // be running. const maxActiveQueries = 256 +// ResponseMapper is a function that accepts the bytes representing +// a DNS response and returns bytes representing a DNS response. +// Used to observe and/or mutate DNS responses managed by this manager. +type ResponseMapper func([]byte) []byte + // We use file-ignore below instead of ignore because on some platforms, // the lint exception is necessary and on others it is not, // and plain ignore complains if the exception is unnecessary. @@ -67,8 +72,9 @@ type Manager struct { knobs *controlknobs.Knobs // or nil goos string // if empty, gets set to runtime.GOOS - mu sync.Mutex // guards following - config *Config // Tracks the last viable DNS configuration set by Set. nil on failures other than compilation failures or if set has never been called. + mu sync.Mutex // guards following + config *Config // Tracks the last viable DNS configuration set by Set. nil on failures other than compilation failures or if set has never been called. + queryResponseMapper ResponseMapper } // NewManager created a new manager from the given config. @@ -291,6 +297,8 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig // authoritative suffixes, even if we don't propagate MagicDNS to // the OS. rcfg.Hosts = cfg.Hosts + rcfg.SubdomainHosts = cfg.SubdomainHosts + rcfg.AcceptDNS = cfg.AcceptDNS routes := map[dnsname.FQDN][]*dnstype.Resolver{} // assigned conditionally to rcfg.Routes below. var propagateHostsToOS bool for suffix, resolvers := range cfg.Routes { @@ -388,9 +396,9 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig cfg, err := m.os.GetBaseConfig() if err == nil { baseCfg = &cfg - } else if isApple && err == ErrGetBaseConfigNotSupported { - // This is currently (2022-10-13) expected on certain iOS and macOS - // builds. + } else if (isApple || isNoopManager(m.os)) && err == ErrGetBaseConfigNotSupported { + // Expected when using noopManager (userspace networking) or on + // certain iOS/macOS builds. Continue without base config. } else { m.health.SetUnhealthy(osConfigurationReadWarnable, health.Args{health.ArgError: err.Error()}) return resolver.Config{}, OSConfig{}, err @@ -465,7 +473,16 @@ func (m *Manager) Query(ctx context.Context, bs []byte, family string, from neti return nil, errFullQueue } defer atomic.AddInt32(&m.activeQueriesAtomic, -1) - return m.resolver.Query(ctx, bs, family, from) + outbs, err := m.resolver.Query(ctx, bs, family, from) + if err != nil { + return outbs, err + } + m.mu.Lock() + defer m.mu.Unlock() + if m.queryResponseMapper != nil { + outbs = m.queryResponseMapper(outbs) + } + return outbs, err } const ( @@ -651,3 +668,9 @@ func CleanUp(logf logger.Logf, netMon *netmon.Monitor, bus *eventbus.Bus, health } var metricDNSQueryErrorQueue = clientmetric.NewCounter("dns_query_local_error_queue") + +func (m *Manager) SetQueryResponseMapper(fx ResponseMapper) { + m.mu.Lock() + defer m.mu.Unlock() + m.queryResponseMapper = fx +} diff --git a/net/dns/manager_darwin.go b/net/dns/manager_darwin.go index 01c920626e466..bb590aa4e7c14 100644 --- a/net/dns/manager_darwin.go +++ b/net/dns/manager_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_default.go b/net/dns/manager_default.go index 42e7d295d713f..b514a741799a4 100644 --- a/net/dns/manager_default.go +++ b/net/dns/manager_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (!linux || android) && !freebsd && !openbsd && !windows && !darwin && !illumos && !solaris && !plan9 diff --git a/net/dns/manager_freebsd.go b/net/dns/manager_freebsd.go index da3a821ce3cc4..deea462ed049d 100644 --- a/net/dns/manager_freebsd.go +++ b/net/dns/manager_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_linux.go b/net/dns/manager_linux.go index 4fbf6a8dbffa2..e68b2e7f9e266 100644 --- a/net/dns/manager_linux.go +++ b/net/dns/manager_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/dns/manager_linux_test.go b/net/dns/manager_linux_test.go index 605344c062de9..d48fe23e70a8b 100644 --- a/net/dns/manager_linux_test.go +++ b/net/dns/manager_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_openbsd.go b/net/dns/manager_openbsd.go index 766c82f981218..fe4641bbd1ee1 100644 --- a/net/dns/manager_openbsd.go +++ b/net/dns/manager_openbsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_plan9.go b/net/dns/manager_plan9.go index 47c996dad7cda..d7619f414d45f 100644 --- a/net/dns/manager_plan9.go +++ b/net/dns/manager_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO: man 6 ndb | grep -e 'suffix.*same line' diff --git a/net/dns/manager_plan9_test.go b/net/dns/manager_plan9_test.go index 806fdb68ed6ba..cc09b360e0c09 100644 --- a/net/dns/manager_plan9_test.go +++ b/net/dns/manager_plan9_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build plan9 diff --git a/net/dns/manager_solaris.go b/net/dns/manager_solaris.go index dcd8b1fd3951c..3324e2b07af23 100644 --- a/net/dns/manager_solaris.go +++ b/net/dns/manager_solaris.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_tcp_test.go b/net/dns/manager_tcp_test.go index 420efe40405df..67d6d15cd42ed 100644 --- a/net/dns/manager_tcp_test.go +++ b/net/dns/manager_tcp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns @@ -15,6 +15,7 @@ import ( "github.com/google/go-cmp/cmp" dns "golang.org/x/net/dns/dnsmessage" + "tailscale.com/control/controlknobs" "tailscale.com/health" "tailscale.com/net/netmon" "tailscale.com/net/tsdial" @@ -93,7 +94,8 @@ func TestDNSOverTCP(t *testing.T) { bus := eventbustest.NewBus(t) dialer := tsdial.NewDialer(netmon.NewStatic()) dialer.SetBus(bus) - m := NewManager(t.Logf, &f, health.NewTracker(bus), dialer, nil, nil, "", bus) + cknobs := &controlknobs.Knobs{} + m := NewManager(t.Logf, &f, health.NewTracker(bus), dialer, nil, cknobs, "", bus) m.resolver.TestOnlySetHook(f.SetResolver) m.Set(Config{ Hosts: hosts( diff --git a/net/dns/manager_test.go b/net/dns/manager_test.go index 18c88df9125c3..8a67aca5cd545 100644 --- a/net/dns/manager_test.go +++ b/net/dns/manager_test.go @@ -1,27 +1,40 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns import ( + "bytes" + "context" "errors" + "io" + "net/http" + "net/http/httptest" "net/netip" "reflect" "runtime" + "slices" "strings" + "sync" "testing" "testing/synctest" + "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + dns "golang.org/x/net/dns/dnsmessage" "tailscale.com/control/controlknobs" "tailscale.com/health" + "tailscale.com/net/dns/publicdns" "tailscale.com/net/dns/resolver" "tailscale.com/net/netmon" + "tailscale.com/net/tsaddr" "tailscale.com/net/tsdial" + "tailscale.com/tstest" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" "tailscale.com/util/eventbus/eventbustest" + "tailscale.com/util/httpm" ) type fakeOSConfigurator struct { @@ -160,6 +173,8 @@ func TestCompileHostEntries(t *testing.T) { } } +var serviceAddr46 = []netip.Addr{tsaddr.TailscaleServiceIP(), tsaddr.TailscaleServiceIPv6()} + func TestManager(t *testing.T) { if runtime.GOOS == "windows" { t.Skipf("test's assumptions break because of https://github.com/tailscale/corp/issues/1662") @@ -177,6 +192,7 @@ func TestManager(t *testing.T) { split bool bs OSConfig os OSConfig + knobs *controlknobs.Knobs rs resolver.Config goos string // empty means "linux" }{ @@ -219,7 +235,7 @@ func TestManager(t *testing.T) { "bar.tld.", "2.3.4.5"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, }, rs: resolver.Config{ Hosts: hosts( @@ -305,7 +321,7 @@ func TestManager(t *testing.T) { "bradfitz.ts.com.", "2.3.4.5"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -328,7 +344,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -347,7 +363,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("tailscale.com", "universe.tf"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -365,7 +381,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -374,6 +390,33 @@ func TestManager(t *testing.T) { "corp.com.", "2.2.2.2"), }, }, + { + name: "controlknob-disable-v6-registration", + in: Config{ + DefaultResolvers: mustRes("1.1.1.1", "9.9.9.9"), + SearchDomains: fqdns("tailscale.com", "universe.tf"), + Routes: upstreams("ts.com", ""), + Hosts: hosts( + "dave.ts.com.", "1.2.3.4", + "bradfitz.ts.com.", "2.3.4.5"), + }, + knobs: (func() *controlknobs.Knobs { + k := new(controlknobs.Knobs) + k.ForceRegisterMagicDNSIPv4Only.Store(true) + return k + })(), + os: OSConfig{ + Nameservers: mustIPs("100.100.100.100"), // without IPv6 + SearchDomains: fqdns("tailscale.com", "universe.tf"), + }, + rs: resolver.Config{ + Routes: upstreams(".", "1.1.1.1", "9.9.9.9"), + Hosts: hosts( + "dave.ts.com.", "1.2.3.4", + "bradfitz.ts.com.", "2.3.4.5"), + LocalDomains: fqdns("ts.com."), + }, + }, { name: "routes", in: Config{ @@ -385,7 +428,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("coffee.shop"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"), }, rs: resolver.Config{ @@ -420,7 +463,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("coffee.shop"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"), }, rs: resolver.Config{ @@ -440,7 +483,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), MatchDomains: fqdns("bigco.net", "corp.com"), }, @@ -465,7 +508,7 @@ func TestManager(t *testing.T) { }, split: false, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -490,7 +533,7 @@ func TestManager(t *testing.T) { }, split: false, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -515,7 +558,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("coffee.shop"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"), }, rs: resolver.Config{ @@ -537,7 +580,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), MatchDomains: fqdns("ts.com"), }, @@ -563,7 +606,7 @@ func TestManager(t *testing.T) { }, split: false, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -589,7 +632,7 @@ func TestManager(t *testing.T) { }, split: false, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -615,7 +658,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("coffee.shop"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"), }, rs: resolver.Config{ @@ -641,7 +684,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), MatchDomains: fqdns("corp.com", "ts.com"), }, @@ -671,7 +714,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -703,7 +746,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -728,7 +771,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("tailscale.com", "universe.tf"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -756,7 +799,7 @@ func TestManager(t *testing.T) { DefaultResolvers: mustRes("2a07:a8c0::c3:a884"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, }, rs: resolver.Config{ Routes: upstreams(".", "2a07:a8c0::c3:a884"), @@ -768,7 +811,7 @@ func TestManager(t *testing.T) { DefaultResolvers: mustRes("https://dns.nextdns.io/c3a884"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, }, rs: resolver.Config{ Routes: upstreams(".", "https://dns.nextdns.io/c3a884"), @@ -784,7 +827,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("optimistic-display.ts.net"), MatchDomains: fqdns("ts.net"), }, @@ -809,7 +852,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("optimistic-display.ts.net"), }, rs: resolver.Config{ @@ -832,7 +875,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("optimistic-display.ts.net"), }, rs: resolver.Config{ @@ -873,7 +916,7 @@ func TestManager(t *testing.T) { }, }, }, - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("ts.com", "universe.tf"), MatchDomains: fqdns("corp.com", "ts.com"), }, @@ -900,7 +943,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("ts.com", "universe.tf"), MatchDomains: fqdns("corp.com", "ts.com"), }, @@ -934,7 +977,10 @@ func TestManager(t *testing.T) { if goos == "" { goos = "linux" } - knobs := &controlknobs.Knobs{} + knobs := test.knobs + if knobs == nil { + knobs = &controlknobs.Knobs{} + } bus := eventbustest.NewBus(t) dialer := tsdial.NewDialer(netmon.NewStatic()) dialer.SetBus(bus) @@ -1116,3 +1162,195 @@ func TestTrampleRetrample(t *testing.T) { } }) } + +// TestSystemDNSDoHUpgrade tests that if the user doesn't configure DNS servers +// in their tailnet, and the system DNS happens to be a known DoH provider, +// queries will use DNS-over-HTTPS. +func TestSystemDNSDoHUpgrade(t *testing.T) { + var ( + // This is a non-routable TEST-NET-2 IP (RFC 5737). + testDoHResolverIP = netip.MustParseAddr("198.51.100.1") + // This is a non-routable TEST-NET-1 IP (RFC 5737). + testResponseIP = netip.MustParseAddr("192.0.2.1") + ) + const testDomain = "test.example.com." + + var ( + mu sync.Mutex + dohRequestSeen bool + receivedQuery []byte + ) + dohServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Logf("[DoH Server] received request: %v %v", r.Method, r.URL) + + if r.Method != httpm.POST { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + if r.Header.Get("Content-Type") != "application/dns-message" { + http.Error(w, "bad content type", http.StatusBadRequest) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "read error", http.StatusInternalServerError) + return + } + + mu.Lock() + defer mu.Unlock() + + dohRequestSeen = true + receivedQuery = body + + // Build a DNS response + response := buildTestDNSResponse(t, testDomain, testResponseIP) + w.Header().Set("Content-Type", "application/dns-message") + w.Write(response) + })) + t.Cleanup(dohServer.Close) + + // Register the test IP to route to our mock DoH server + cleanup := publicdns.RegisterTestDoHEndpoint(testDoHResolverIP, dohServer.URL) + t.Cleanup(cleanup) + + // This simulates a system with the single DoH-capable DNS server + // configured. + f := &fakeOSConfigurator{ + SplitDNS: false, // non-split DNS required to use the forwarder + BaseConfig: OSConfig{ + Nameservers: []netip.Addr{testDoHResolverIP}, + }, + } + + logf := tstest.WhileTestRunningLogger(t) + bus := eventbustest.NewBus(t) + dialer := tsdial.NewDialer(netmon.NewStatic()) + dialer.SetBus(bus) + m := NewManager(logf, f, health.NewTracker(bus), dialer, nil, &controlknobs.Knobs{}, "linux", bus) + t.Cleanup(func() { m.Down() }) + + // Set up hook to capture the resolver config + m.resolver.TestOnlySetHook(f.SetResolver) + + // Configure the manager with routes but no default resolvers, which + // reads BaseConfig from the OS configurator. + config := Config{ + Routes: upstreams("tailscale.com.", "10.0.0.1"), + SearchDomains: fqdns("tailscale.com."), + } + if err := m.Set(config); err != nil { + t.Fatal(err) + } + + // Verify the resolver config has our test IP in Routes["."] + if f.ResolverConfig.Routes == nil { + t.Fatal("ResolverConfig.Routes is nil (SetResolver hook not called)") + } + + const defaultRouteKey = "." + defaultRoute, ok := f.ResolverConfig.Routes[defaultRouteKey] + if !ok { + t.Fatalf("ResolverConfig.Routes[%q] not found", defaultRouteKey) + } + if !slices.ContainsFunc(defaultRoute, func(r *dnstype.Resolver) bool { + return r.Addr == testDoHResolverIP.String() + }) { + t.Errorf("test IP %v not found in Routes[%q], got: %v", testDoHResolverIP, defaultRouteKey, defaultRoute) + } + + // Build a DNS query to something not handled by our split DNS route + // (tailscale.com) above. + query := buildTestDNSQuery(t, testDomain) + + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) + defer cancel() + resp, err := m.Query(ctx, query, "udp", netip.MustParseAddrPort("127.0.0.1:12345")) + if err != nil { + t.Fatal(err) + } + if len(resp) == 0 { + t.Fatal("empty response") + } + + // Parse the response to verify we get our test IP back. + var parser dns.Parser + if _, err := parser.Start(resp); err != nil { + t.Fatalf("parsing response header: %v", err) + } + if err := parser.SkipAllQuestions(); err != nil { + t.Fatalf("skipping questions: %v", err) + } + answers, err := parser.AllAnswers() + if err != nil { + t.Fatalf("parsing answers: %v", err) + } + if len(answers) == 0 { + t.Fatal("no answers in response") + } + aRecord, ok := answers[0].Body.(*dns.AResource) + if !ok { + t.Fatalf("first answer is not A record: %T", answers[0].Body) + } + gotIP := netip.AddrFrom4(aRecord.A) + if gotIP != testResponseIP { + t.Errorf("wrong A record IP: got %v, want %v", gotIP, testResponseIP) + } + + // Also verify that our DoH server received the query. + mu.Lock() + defer mu.Unlock() + if !dohRequestSeen { + t.Error("DoH server never received request") + } + if !bytes.Equal(receivedQuery, query) { + t.Errorf("DoH server received wrong query:\ngot: %x\nwant: %x", receivedQuery, query) + } +} + +// buildTestDNSQuery builds a simple DNS A query for the given domain. +func buildTestDNSQuery(t *testing.T, domain string) []byte { + t.Helper() + + builder := dns.NewBuilder(nil, dns.Header{}) + builder.StartQuestions() + builder.Question(dns.Question{ + Name: dns.MustNewName(domain), + Type: dns.TypeA, + Class: dns.ClassINET, + }) + msg, err := builder.Finish() + if err != nil { + t.Fatal(err) + } + + return msg +} + +// buildTestDNSResponse builds a DNS response for the given query with the specified IP. +func buildTestDNSResponse(t *testing.T, domain string, ip netip.Addr) []byte { + t.Helper() + + builder := dns.NewBuilder(nil, dns.Header{Response: true}) + builder.StartQuestions() + builder.Question(dns.Question{ + Name: dns.MustNewName(domain), + Type: dns.TypeA, + Class: dns.ClassINET, + }) + + builder.StartAnswers() + builder.AResource(dns.ResourceHeader{ + Name: dns.MustNewName(domain), + Class: dns.ClassINET, + TTL: 300, + }, dns.AResource{A: ip.As4()}) + + msg, err := builder.Finish() + if err != nil { + t.Fatal(err) + } + + return msg +} diff --git a/net/dns/manager_windows.go b/net/dns/manager_windows.go index 1eccb9a16ff1d..1e412b2d20617 100644 --- a/net/dns/manager_windows.go +++ b/net/dns/manager_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns @@ -34,6 +34,7 @@ import ( "tailscale.com/util/syspolicy/policyclient" "tailscale.com/util/syspolicy/ptype" "tailscale.com/util/winutil" + "tailscale.com/util/winutil/winenv" ) const ( @@ -354,6 +355,10 @@ func (m *windowsManager) disableLocalDNSOverrideViaNRPT() bool { return m.knobs != nil && m.knobs.DisableLocalDNSOverrideViaNRPT.Load() } +func (m *windowsManager) disableHostsFileUpdates() bool { + return m.knobs != nil && m.knobs.DisableHostsFileUpdates.Load() +} + func (m *windowsManager) SetDNS(cfg OSConfig) error { // We can configure Windows DNS in one of two ways: // @@ -399,7 +404,15 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error { if err := m.setSplitDNS(resolvers, domains); err != nil { return err } - if err := m.setHosts(nil); err != nil { + var hosts []*HostEntry + if !m.disableHostsFileUpdates() && winenv.IsDomainJoined() { + // On domain-joined Windows devices the primary search domain (the one the device is joined to) + // always takes precedence over other search domains. This breaks MagicDNS when we are the primary + // resolver on the device (see #18712). To work around this Windows behavior, we should write MagicDNS + // host names the hosts file just as we do when we're not the primary resolver. + hosts = cfg.Hosts + } + if err := m.setHosts(hosts); err != nil { return err } if err := m.setPrimaryDNS(cfg.Nameservers, cfg.SearchDomains); err != nil { @@ -421,12 +434,14 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error { return err } - // As we are not the primary resolver in this setup, we need to - // explicitly set some single name hosts to ensure that we can resolve - // them quickly and get around the 2.3s delay that otherwise occurs due - // to multicast timeouts. - if err := m.setHosts(cfg.Hosts); err != nil { - return err + if !m.disableHostsFileUpdates() { + // As we are not the primary resolver in this setup, we need to + // explicitly set some single name hosts to ensure that we can resolve + // them quickly and get around the 2.3s delay that otherwise occurs due + // to multicast timeouts. + if err := m.setHosts(cfg.Hosts); err != nil { + return err + } } } diff --git a/net/dns/manager_windows_test.go b/net/dns/manager_windows_test.go index 5525096b35c55..d1c65ed2bffd6 100644 --- a/net/dns/manager_windows_test.go +++ b/net/dns/manager_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/nm.go b/net/dns/nm.go index a88d29b374ebb..99f032431ce57 100644 --- a/net/dns/nm.go +++ b/net/dns/nm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_networkmanager diff --git a/net/dns/noop.go b/net/dns/noop.go index 9466b57a0f477..aaf3a56ed68eb 100644 --- a/net/dns/noop.go +++ b/net/dns/noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns @@ -15,3 +15,8 @@ func (m noopManager) GetBaseConfig() (OSConfig, error) { func NewNoopManager() (noopManager, error) { return noopManager{}, nil } + +func isNoopManager(c OSConfigurator) bool { + _, ok := c.(noopManager) + return ok +} diff --git a/net/dns/nrpt_windows.go b/net/dns/nrpt_windows.go index 261ca337558ef..6d9cf71b7a427 100644 --- a/net/dns/nrpt_windows.go +++ b/net/dns/nrpt_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns @@ -318,7 +318,7 @@ func (db *nrptRuleDatabase) writeNRPTRule(ruleID string, servers, doms []string) } defer key.Close() - if err := writeNRPTValues(key, strings.Join(servers, "; "), doms); err != nil { + if err := writeNRPTValues(key, strings.Join(servers, ";"), doms); err != nil { return err } } diff --git a/net/dns/openresolv.go b/net/dns/openresolv.go index c9562b6a91d13..c3aaf3a6948c8 100644 --- a/net/dns/openresolv.go +++ b/net/dns/openresolv.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || freebsd || openbsd diff --git a/net/dns/osconfig.go b/net/dns/osconfig.go index af4c0f01fc75b..f871335ade38c 100644 --- a/net/dns/osconfig.go +++ b/net/dns/osconfig.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/osconfig_test.go b/net/dns/osconfig_test.go index c19db299f4b54..93b13c57d482b 100644 --- a/net/dns/osconfig_test.go +++ b/net/dns/osconfig_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/publicdns/publicdns.go b/net/dns/publicdns/publicdns.go index b8a7f88091617..3666bd77847c9 100644 --- a/net/dns/publicdns/publicdns.go +++ b/net/dns/publicdns/publicdns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package publicdns contains mapping and helpers for working with @@ -13,12 +13,14 @@ import ( "log" "math/big" "net/netip" + "slices" "sort" "strconv" "strings" "sync" "tailscale.com/feature/buildfeatures" + "tailscale.com/util/testenv" ) // dohOfIP maps from public DNS IPs to their DoH base URL. @@ -275,6 +277,26 @@ func populate() { addDoH("76.76.10.4", "https://freedns.controld.com/family") addDoH("2606:1a40::4", "https://freedns.controld.com/family") addDoH("2606:1a40:1::4", "https://freedns.controld.com/family") + + // CIRA Canadian Shield: https://www.cira.ca/en/canadian-shield/configure/summary-cira-canadian-shield-dns-resolver-addresses/ + + // CIRA Canadian Shield Private (DNS resolution only) + addDoH("149.112.121.10", "https://private.canadianshield.cira.ca/dns-query") + addDoH("149.112.122.10", "https://private.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bb::10", "https://private.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bc::10", "https://private.canadianshield.cira.ca/dns-query") + + // CIRA Canadian Shield Protected (Malware and phishing protection) + addDoH("149.112.121.20", "https://protected.canadianshield.cira.ca/dns-query") + addDoH("149.112.122.20", "https://protected.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bb::20", "https://protected.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bc::20", "https://protected.canadianshield.cira.ca/dns-query") + + // CIRA Canadian Shield Family (Protected + blocking adult content) + addDoH("149.112.121.30", "https://family.canadianshield.cira.ca/dns-query") + addDoH("149.112.122.30", "https://family.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bb::30", "https://family.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bc::30", "https://family.canadianshield.cira.ca/dns-query") } var ( @@ -347,3 +369,39 @@ func IPIsDoHOnlyServer(ip netip.Addr) bool { controlDv6RangeA.Contains(ip) || controlDv6RangeB.Contains(ip) || ip == controlDv4One || ip == controlDv4Two } + +var testMu sync.Mutex + +// RegisterTestDoHEndpoint registers a test DoH endpoint mapping for use in tests. +// It maps the given IP to the DoH base URL, and the URL back to the IP. +// +// This function panics if called outside of tests, and cannot be called +// concurrently with any usage of this package (i.e. before any DNS forwarders +// are created). It is safe to call concurrently with itself. +// +// It returns a cleanup function that removes the registration. +func RegisterTestDoHEndpoint(ip netip.Addr, dohBase string) func() { + if !testenv.InTest() { + panic("RegisterTestDoHEndpoint called outside of tests") + } + populateOnce.Do(populate) + + testMu.Lock() + defer testMu.Unlock() + + dohOfIP[ip] = dohBase + dohIPsOfBase[dohBase] = append(dohIPsOfBase[dohBase], ip) + + return func() { + testMu.Lock() + defer testMu.Unlock() + + delete(dohOfIP, ip) + dohIPsOfBase[dohBase] = slices.DeleteFunc(dohIPsOfBase[dohBase], func(addr netip.Addr) bool { + return addr == ip + }) + if len(dohIPsOfBase[dohBase]) == 0 { + delete(dohIPsOfBase, dohBase) + } + } +} diff --git a/net/dns/publicdns/publicdns_test.go b/net/dns/publicdns/publicdns_test.go index 6efeb2c6f96c8..4f494930b21e9 100644 --- a/net/dns/publicdns/publicdns_test.go +++ b/net/dns/publicdns/publicdns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package publicdns diff --git a/net/dns/resolvconf-workaround.sh b/net/dns/resolvconf-workaround.sh index aec6708a06da1..e0bb250207952 100644 --- a/net/dns/resolvconf-workaround.sh +++ b/net/dns/resolvconf-workaround.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # # This script is a workaround for a vpn-unfriendly behavior of the diff --git a/net/dns/resolvconf.go b/net/dns/resolvconf.go index ca584ffcc5f1f..990a25f2909ac 100644 --- a/net/dns/resolvconf.go +++ b/net/dns/resolvconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || freebsd || openbsd diff --git a/net/dns/resolvconffile/resolvconffile.go b/net/dns/resolvconffile/resolvconffile.go index 753000f6d33da..7a3b90474b5d0 100644 --- a/net/dns/resolvconffile/resolvconffile.go +++ b/net/dns/resolvconffile/resolvconffile.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package resolvconffile parses & serializes /etc/resolv.conf-style files. diff --git a/net/dns/resolvconffile/resolvconffile_test.go b/net/dns/resolvconffile/resolvconffile_test.go index 4f5ddd599899a..21d9e493d56df 100644 --- a/net/dns/resolvconffile/resolvconffile_test.go +++ b/net/dns/resolvconffile/resolvconffile_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolvconffile diff --git a/net/dns/resolvconfpath_default.go b/net/dns/resolvconfpath_default.go index 57e82c4c773ea..00caf30b24a64 100644 --- a/net/dns/resolvconfpath_default.go +++ b/net/dns/resolvconfpath_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !gokrazy diff --git a/net/dns/resolvconfpath_gokrazy.go b/net/dns/resolvconfpath_gokrazy.go index f0759b0e31a0f..ec382ae82e966 100644 --- a/net/dns/resolvconfpath_gokrazy.go +++ b/net/dns/resolvconfpath_gokrazy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build gokrazy diff --git a/net/dns/resolvd.go b/net/dns/resolvd.go index ad1a99c111997..56fedfef94daf 100644 --- a/net/dns/resolvd.go +++ b/net/dns/resolvd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build openbsd diff --git a/net/dns/resolved.go b/net/dns/resolved.go index d8f63c9d66006..754570fdc1779 100644 --- a/net/dns/resolved.go +++ b/net/dns/resolved.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_resolved diff --git a/net/dns/resolver/debug.go b/net/dns/resolver/debug.go index a41462e185e24..8d700ce54a143 100644 --- a/net/dns/resolver/debug.go +++ b/net/dns/resolver/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/resolver/doh_test.go b/net/dns/resolver/doh_test.go index a9c28476166fc..b7045c3211e6b 100644 --- a/net/dns/resolver/doh_test.go +++ b/net/dns/resolver/doh_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 797c5272ad651..6fec32d6a2685 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver @@ -63,6 +63,17 @@ func truncatedFlagSet(pkt []byte) bool { return (binary.BigEndian.Uint16(pkt[2:4]) & dnsFlagTruncated) != 0 } +// setTCFlag sets the TC (truncated) flag in the DNS packet header. +// The packet must be at least headerBytes in length. +func setTCFlag(packet []byte) { + if len(packet) < headerBytes { + return + } + flags := binary.BigEndian.Uint16(packet[2:4]) + flags |= dnsFlagTruncated + binary.BigEndian.PutUint16(packet[2:4], flags) +} + const ( // dohIdleConnTimeout is how long to keep idle HTTP connections // open to DNS-over-HTTPS servers. 10 seconds is a sensible @@ -131,47 +142,59 @@ func getRCode(packet []byte) dns.RCode { return dns.RCode(packet[3] & 0x0F) } -// clampEDNSSize attempts to limit the maximum EDNS response size. This is not -// an exhaustive solution, instead only easy cases are currently handled in the -// interest of speed and reduced complexity. Only OPT records at the very end of -// the message with no option codes are addressed. -// TODO: handle more situations if we discover that they happen often -func clampEDNSSize(packet []byte, maxSize uint16) { - // optFixedBytes is the size of an OPT record with no option codes. - const optFixedBytes = 11 - const edns0Version = 0 +// findOPTRecord finds and validates the EDNS OPT record at the end of a DNS packet. +// Returns the requested buffer size and a pointer to the OPT record bytes if valid, +// or (0, nil) if no valid OPT record is found. +// The OPT record must be at the very end of the packet with no option codes. +func findOPTRecord(packet []byte) (requestedSize uint16, opt []byte) { + const optFixedBytes = 11 // size of an OPT record with no option codes + const edns0Version = 0 // EDNS version number (currently only version 0 is defined) if len(packet) < headerBytes+optFixedBytes { - return + return 0, nil } arCount := binary.BigEndian.Uint16(packet[10:12]) if arCount == 0 { // OPT shows up in an AR, so there must be no OPT - return + return 0, nil } // https://datatracker.ietf.org/doc/html/rfc6891#section-6.1.2 - opt := packet[len(packet)-optFixedBytes:] + opt = packet[len(packet)-optFixedBytes:] if opt[0] != 0 { // OPT NAME must be 0 (root domain) - return + return 0, nil } if dns.Type(binary.BigEndian.Uint16(opt[1:3])) != dns.TypeOPT { // Not an OPT record - return + return 0, nil } - requestedSize := binary.BigEndian.Uint16(opt[3:5]) + requestedSize = binary.BigEndian.Uint16(opt[3:5]) // Ignore extended RCODE in opt[5] if opt[6] != edns0Version { // Be conservative and don't touch unknown versions. - return + return 0, nil } // Ignore flags in opt[6:9] if binary.BigEndian.Uint16(opt[9:11]) != 0 { // RDLEN must be 0 (no variable length data). We're at the end of the - // packet so this should be 0 anyway).. + // packet so this should be 0 anyway. + return 0, nil + } + + return requestedSize, opt +} + +// clampEDNSSize attempts to limit the maximum EDNS response size. This is not +// an exhaustive solution, instead only easy cases are currently handled in the +// interest of speed and reduced complexity. Only OPT records at the very end of +// the message with no option codes are addressed. +// TODO: handle more situations if we discover that they happen often +func clampEDNSSize(packet []byte, maxSize uint16) { + requestedSize, opt := findOPTRecord(packet) + if opt == nil { return } @@ -183,6 +206,57 @@ func clampEDNSSize(packet []byte, maxSize uint16) { binary.BigEndian.PutUint16(opt[3:5], maxSize) } +// getEDNSBufferSize extracts the EDNS buffer size from a DNS request packet. +// Returns (bufferSize, true) if a valid EDNS OPT record is found, +// or (0, false) if no EDNS OPT record is found or if there's an error. +func getEDNSBufferSize(packet []byte) (uint16, bool) { + requestedSize, opt := findOPTRecord(packet) + return requestedSize, opt != nil +} + +// checkResponseSizeAndSetTC sets the TC (truncated) flag in the DNS header when +// the response exceeds the maximum UDP size. If no EDNS OPT record is present +// in the request, it sets the TC flag when the response is bigger than 512 bytes +// per RFC 1035. If an EDNS OPT record is present, it sets the TC flag when the +// response is bigger than the EDNS buffer size. The response buffer is not +// truncated; only the TC flag is set. Returns the response unchanged except for +// the TC flag being set if needed. +func checkResponseSizeAndSetTC(response []byte, request []byte, family string, logf logger.Logf) []byte { + const defaultUDPSize = 512 // default maximum UDP DNS packet size per RFC 1035 + + // Only check for UDP queries; TCP can handle larger responses + if family != "udp" { + return response + } + + // Check if TC flag is already set + if len(response) < headerBytes { + return response + } + if truncatedFlagSet(response) { + // TC flag already set, nothing to do + return response + } + + ednsSize, hasEDNS := getEDNSBufferSize(request) + + // Determine maximum allowed size + var maxSize int + if hasEDNS { + maxSize = int(ednsSize) + } else { + // No EDNS: enforce default UDP size limit per RFC 1035 + maxSize = defaultUDPSize + } + + // Check if response exceeds maximum size + if len(response) > maxSize { + setTCFlag(response) + } + + return response +} + // dnsForwarderFailing should be raised when the forwarder is unable to reach the // upstream resolvers. This is a high severity warning as it results in "no internet". // This warning must be cleared when the forwarder is working again. @@ -249,6 +323,12 @@ type forwarder struct { // /etc/resolv.conf is missing/corrupt, and the peerapi ExitDNS stub // resolver lookup. cloudHostFallback []resolverAndDelay + + // acceptDNS tracks the CorpDNS pref (--accept-dns) + // This lets us skip health warnings if the forwarder receives inbound + // queries directly - but we didn't configure it with any upstream resolvers. + // That's an error, but not a health error if the user has disabled CorpDNS. + acceptDNS bool } func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, health *health.Tracker, knobs *controlknobs.Knobs) *forwarder { @@ -360,7 +440,7 @@ func cloudResolvers() []resolverAndDelay { // Resolver.SetConfig on reconfig. // // The memory referenced by routesBySuffix should not be modified. -func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolver) { +func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolver, acceptDNS bool) { routes := make([]route, 0, len(routesBySuffix)) cloudHostFallback := cloudResolvers() @@ -394,6 +474,7 @@ func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolve f.mu.Lock() defer f.mu.Unlock() + f.acceptDNS = acceptDNS f.routes = routes f.cloudHostFallback = cloudHostFallback } @@ -535,7 +616,13 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDe if !buildfeatures.HasPeerAPIClient { return nil, feature.ErrUnavailable } - return f.sendDoH(ctx, rr.name.Addr, f.dialer.PeerAPIHTTPClient(), fq.packet) + res, err := f.sendDoH(ctx, rr.name.Addr, f.dialer.PeerAPIHTTPClient(), fq.packet) + if err != nil { + return nil, err + } + // Check response size and set TC flag if needed (only for UDP queries) + res = checkResponseSizeAndSetTC(res, fq.packet, fq.family, f.logf) + return res, nil } if strings.HasPrefix(rr.name.Addr, "https://") { // Only known DoH providers are supported currently. Specifically, we @@ -546,7 +633,13 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDe // them. urlBase := rr.name.Addr if hc, ok := f.getKnownDoHClientForProvider(urlBase); ok { - return f.sendDoH(ctx, urlBase, hc, fq.packet) + res, err := f.sendDoH(ctx, urlBase, hc, fq.packet) + if err != nil { + return nil, err + } + // Check response size and set TC flag if needed (only for UDP queries) + res = checkResponseSizeAndSetTC(res, fq.packet, fq.family, f.logf) + return res, nil } metricDNSFwdErrorType.Add(1) return nil, fmt.Errorf("arbitrary https:// resolvers not supported yet") @@ -710,12 +803,15 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn f.logf("recv: packet too small (%d bytes)", n) } out = out[:n] + tcFlagAlreadySet := truncatedFlagSet(out) + txid := getTxID(out) if txid != fq.txid { metricDNSFwdUDPErrorTxID.Add(1) return nil, errTxIDMismatch } rcode := getRCode(out) + // don't forward transient errors back to the client when the server fails if rcode == dns.RCodeServerFailure { f.logf("recv: response code indicating server failure: %d", rcode) @@ -723,11 +819,9 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn return nil, errServerFailure } - if truncated { - // Set the truncated bit if it wasn't already. - flags := binary.BigEndian.Uint16(out[2:4]) - flags |= dnsFlagTruncated - binary.BigEndian.PutUint16(out[2:4], flags) + // Set the truncated bit if buffer was truncated during read and the flag isn't already set + if truncated && !tcFlagAlreadySet { + setTCFlag(out) // TODO(#2067): Remove any incomplete records? RFC 1035 section 6.2 // states that truncation should head drop so that the authority @@ -736,6 +830,8 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn // best we can do. } + out = checkResponseSizeAndSetTC(out, fq.packet, fq.family, f.logf) + if truncatedFlagSet(out) { metricDNSFwdTruncated.Add(1) } @@ -967,7 +1063,9 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo resolvers = f.resolvers(domain) if len(resolvers) == 0 { metricDNSFwdErrorNoUpstream.Add(1) - f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: ""}) + if f.acceptDNS { + f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: ""}) + } f.logf("no upstream resolvers set, returning SERVFAIL") res, err := servfailResponse(query) @@ -1067,7 +1165,9 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo for _, rr := range resolvers { resolverAddrs = append(resolverAddrs, rr.name.Addr) } - f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")}) + if f.acceptDNS { + f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")}) + } case responseChan <- res: if f.verboseFwd { f.logf("forwarder response(%d, %v, %d) = %d, %v", fq.txid, typ, len(domain), len(res.bs), firstErr) @@ -1092,7 +1192,9 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo for _, rr := range resolvers { resolverAddrs = append(resolverAddrs, rr.name.Addr) } - f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")}) + if f.acceptDNS { + f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")}) + } return fmt.Errorf("waiting for response or error from %v: %w", resolverAddrs, ctx.Err()) } } diff --git a/net/dns/resolver/forwarder_test.go b/net/dns/resolver/forwarder_test.go index 0b38008c8a9c2..6fd186c25a61c 100644 --- a/net/dns/resolver/forwarder_test.go +++ b/net/dns/resolver/forwarder_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver @@ -34,6 +34,46 @@ func (rr resolverAndDelay) String() string { return fmt.Sprintf("%v+%v", rr.name, rr.startDelay) } +// setTCFlagInPacket sets the TC flag in a DNS packet (for testing). +func setTCFlagInPacket(packet []byte) { + if len(packet) < headerBytes { + return + } + flags := binary.BigEndian.Uint16(packet[2:4]) + flags |= dnsFlagTruncated + binary.BigEndian.PutUint16(packet[2:4], flags) +} + +// clearTCFlagInPacket clears the TC flag in a DNS packet (for testing). +func clearTCFlagInPacket(packet []byte) { + if len(packet) < headerBytes { + return + } + flags := binary.BigEndian.Uint16(packet[2:4]) + flags &^= dnsFlagTruncated + binary.BigEndian.PutUint16(packet[2:4], flags) +} + +// verifyEDNSBufferSize verifies a request has the expected EDNS buffer size. +func verifyEDNSBufferSize(t *testing.T, request []byte, expectedSize uint16) { + t.Helper() + ednsSize, hasEDNS := getEDNSBufferSize(request) + if !hasEDNS { + t.Fatalf("request should have EDNS OPT record") + } + if ednsSize != expectedSize { + t.Fatalf("request EDNS size = %d, want %d", ednsSize, expectedSize) + } +} + +// setupForwarderWithTCPRetriesDisabled returns a forwarder modifier that disables TCP retries. +func setupForwarderWithTCPRetriesDisabled() func(*forwarder) { + return func(fwd *forwarder) { + fwd.controlKnobs = &controlknobs.Knobs{} + fwd.controlKnobs.DisableDNSForwarderTCPRetries.Store(true) + } +} + func TestResolversWithDelays(t *testing.T) { // query q := func(ss ...string) (ipps []*dnstype.Resolver) { @@ -428,22 +468,16 @@ func makeLargeResponse(tb testing.TB, domain string) (request, response []byte) } // Our request is a single A query for the domain in the answer, above. - builder = dns.NewBuilder(nil, dns.Header{}) - builder.StartQuestions() - builder.Question(dns.Question{ - Name: dns.MustNewName(domain), - Type: dns.TypeA, - Class: dns.ClassINET, - }) - request, err = builder.Finish() - if err != nil { - tb.Fatal(err) - } + request = makeTestRequest(tb, domain, dns.TypeA, 0) return } func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports ...uint16) ([]byte, error) { + return runTestQueryWithFamily(tb, request, "udp", modify, ports...) +} + +func runTestQueryWithFamily(tb testing.TB, request []byte, family string, modify func(*forwarder), ports ...uint16) ([]byte, error) { logf := tstest.WhileTestRunningLogger(tb) bus := eventbustest.NewBus(tb) netMon, err := netmon.New(bus, logf) @@ -467,7 +501,7 @@ func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports rpkt := packet{ bs: request, - family: "tcp", + family: family, addr: netip.MustParseAddrPort("127.0.0.1:12345"), } @@ -483,17 +517,29 @@ func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports } } -// makeTestRequest returns a new TypeA request for the given domain. -func makeTestRequest(tb testing.TB, domain string) []byte { +// makeTestRequest returns a new DNS request for the given domain. +// If queryType is 0, it defaults to TypeA. If ednsSize > 0, it adds an EDNS OPT record. +func makeTestRequest(tb testing.TB, domain string, queryType dns.Type, ednsSize uint16) []byte { tb.Helper() + if queryType == 0 { + queryType = dns.TypeA + } name := dns.MustNewName(domain) builder := dns.NewBuilder(nil, dns.Header{}) builder.StartQuestions() builder.Question(dns.Question{ Name: name, - Type: dns.TypeA, + Type: queryType, Class: dns.ClassINET, }) + if ednsSize > 0 { + builder.StartAdditionals() + builder.OPTResource(dns.ResourceHeader{ + Name: dns.MustNewName("."), + Type: dns.TypeOPT, + Class: dns.Class(ednsSize), + }, dns.OPTResource{}) + } request, err := builder.Finish() if err != nil { tb.Fatal(err) @@ -549,6 +595,365 @@ func beVerbose(f *forwarder) { f.verboseFwd = true } +// makeEDNSResponse creates a DNS response of approximately the specified size +// with TXT records and an OPT record. The response will NOT have the TC flag set +// (simulating a non-compliant server that doesn't set TC when response exceeds EDNS buffer). +// The actual size may vary significantly due to DNS packet structure constraints. +func makeEDNSResponse(tb testing.TB, domain string, targetSize int) []byte { + tb.Helper() + // Use makeResponseOfSize with includeOPT=true + // Allow significant variance since DNS packet sizes are hard to predict exactly + // Use a combination of fixed tolerance (200 bytes) and percentage (25%) for larger targets + response := makeResponseOfSize(tb, domain, targetSize, true) + actualSize := len(response) + maxVariance := 200 + if targetSize > 400 { + // For larger targets, allow 25% variance + maxVariance = targetSize * 25 / 100 + } + if actualSize < targetSize-maxVariance || actualSize > targetSize+maxVariance { + tb.Fatalf("response size = %d, want approximately %d (variance: %d, allowed: Âą%d)", + actualSize, targetSize, actualSize-targetSize, maxVariance) + } + return response +} + +func TestEDNSBufferSizeTruncation(t *testing.T) { + const domain = "edns-test.example.com." + const ednsBufferSize = 500 // Small EDNS buffer + const responseSize = 800 // Response exceeds EDNS but < maxResponseBytes + + // Create a response that exceeds EDNS buffer size but doesn't have TC flag set + response := makeEDNSResponse(t, domain, responseSize) + + // Create a request with EDNS buffer size + request := makeTestRequest(t, domain, dns.TypeTXT, ednsBufferSize) + verifyEDNSBufferSize(t, request, ednsBufferSize) + + // Verify response doesn't have TC flag set initially + if truncatedFlagSet(response) { + t.Fatal("test response should not have TC flag set initially") + } + + // Set up test DNS server + port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) { + verifyEDNSBufferSize(t, gotRequest, ednsBufferSize) + }) + + // Disable TCP retries to ensure we test UDP path + resp := mustRunTestQuery(t, request, setupForwarderWithTCPRetriesDisabled(), port) + + // Verify the response has TC flag set by forwarder + if !truncatedFlagSet(resp) { + t.Errorf("TC flag not set in response (response size=%d, EDNS=%d)", len(resp), ednsBufferSize) + } + + // Verify response size is preserved (not truncated by buffer) + if len(resp) != len(response) { + t.Errorf("response size = %d, want %d (response should not be truncated by buffer)", len(resp), len(response)) + } + + // Verify response size exceeds EDNS buffer + if len(resp) <= int(ednsBufferSize) { + t.Errorf("response size = %d, should exceed EDNS buffer size %d", len(resp), ednsBufferSize) + } +} + +// makeResponseOfSize creates a DNS response of approximately the specified size +// with TXT records. The response will NOT have the TC flag set initially. +// If includeOPT is true, an OPT record is added to the response. +func makeResponseOfSize(tb testing.TB, domain string, targetSize int, includeOPT bool) []byte { + tb.Helper() + name := dns.MustNewName(domain) + + // Estimate how many TXT records we need + // Each TXT record with ~200 bytes of data adds roughly 220-230 bytes to the packet + // (including DNS headers, name compression, etc.) + bytesPerRecord := 220 + baseSize := 50 // Approximate base packet size (header + question) + if includeOPT { + baseSize += 11 // OPT record adds ~11 bytes + } + estimatedRecords := (targetSize - baseSize) / bytesPerRecord + if estimatedRecords < 1 { + estimatedRecords = 1 + } + + // Start with estimated records and adjust + txtLen := 200 + var response []byte + var err error + + for attempt := 0; attempt < 10; attempt++ { + testBuilder := dns.NewBuilder(nil, dns.Header{ + Response: true, + Authoritative: true, + RCode: dns.RCodeSuccess, + }) + testBuilder.StartQuestions() + testBuilder.Question(dns.Question{ + Name: name, + Type: dns.TypeTXT, + Class: dns.ClassINET, + }) + testBuilder.StartAnswers() + + for i := 0; i < estimatedRecords; i++ { + txtValue := strings.Repeat("x", txtLen) + testBuilder.TXTResource(dns.ResourceHeader{ + Name: name, + Type: dns.TypeTXT, + Class: dns.ClassINET, + TTL: 300, + }, dns.TXTResource{ + TXT: []string{txtValue}, + }) + } + + // Optionally add OPT record + if includeOPT { + testBuilder.StartAdditionals() + testBuilder.OPTResource(dns.ResourceHeader{ + Name: dns.MustNewName("."), + Type: dns.TypeOPT, + Class: dns.Class(4096), + }, dns.OPTResource{}) + } + + response, err = testBuilder.Finish() + if err != nil { + tb.Fatal(err) + } + + actualSize := len(response) + // Stop if we've reached or slightly exceeded the target + // Allow up to 20% overshoot to avoid excessive iterations + if actualSize >= targetSize && actualSize <= targetSize*120/100 { + break + } + // If we've overshot significantly, we're done (better than undershooting) + if actualSize > targetSize*120/100 { + break + } + + // Adjust for next attempt + needed := targetSize - actualSize + additionalRecords := (needed / bytesPerRecord) + 1 + estimatedRecords += additionalRecords + if estimatedRecords > 200 { + // If we need too many records, increase TXT length instead + txtLen = 255 // Max single TXT string length + bytesPerRecord = 280 // Adjusted estimate + estimatedRecords = (targetSize - baseSize) / bytesPerRecord + if estimatedRecords < 1 { + estimatedRecords = 1 + } + } + } + + // Ensure TC flag is NOT set initially + clearTCFlagInPacket(response) + + return response +} + +func TestCheckResponseSizeAndSetTC(t *testing.T) { + const domain = "test.example.com." + logf := func(format string, args ...any) { + // Silent logger for tests + } + + tests := []struct { + name string + responseSize int + requestHasEDNS bool + ednsSize uint16 + family string + responseTCSet bool // Whether response has TC flag set initially + wantTCSet bool // Whether TC flag should be set after function call + skipIfNotExact bool // Skip test if we can't hit exact size (for edge cases) + }{ + // Default UDP size (512 bytes) without EDNS + { + name: "UDP_noEDNS_small_should_not_set_TC", + responseSize: 400, + requestHasEDNS: false, + family: "udp", + wantTCSet: false, + }, + { + name: "UDP_noEDNS_512bytes_should_not_set_TC", + responseSize: 512, + requestHasEDNS: false, + family: "udp", + wantTCSet: false, + skipIfNotExact: true, + }, + { + name: "UDP_noEDNS_513bytes_should_set_TC", + responseSize: 513, + requestHasEDNS: false, + family: "udp", + wantTCSet: true, + skipIfNotExact: true, + }, + { + name: "UDP_noEDNS_large_should_set_TC", + responseSize: 600, + requestHasEDNS: false, + family: "udp", + wantTCSet: true, + }, + + // EDNS edge cases + { + name: "UDP_EDNS_small_under_limit_should_not_set_TC", + responseSize: 450, + requestHasEDNS: true, + ednsSize: 500, + family: "udp", + wantTCSet: false, + }, + { + name: "UDP_EDNS_at_limit_should_not_set_TC", + responseSize: 500, + requestHasEDNS: true, + ednsSize: 500, + family: "udp", + wantTCSet: false, + }, + { + name: "UDP_EDNS_over_limit_should_set_TC", + responseSize: 550, + requestHasEDNS: true, + ednsSize: 500, + family: "udp", + wantTCSet: true, + }, + { + name: "UDP_EDNS_large_over_limit_should_set_TC", + responseSize: 1500, + requestHasEDNS: true, + ednsSize: 1200, + family: "udp", + wantTCSet: true, + }, + + // Early return paths + { + name: "TCP_query_should_skip", + responseSize: 1000, + family: "tcp", + wantTCSet: false, + }, + { + name: "response_too_small_should_skip", + responseSize: headerBytes - 1, + family: "udp", + wantTCSet: false, + }, + { + name: "response_exactly_headerBytes_should_not_set_TC", + responseSize: headerBytes, + family: "udp", + wantTCSet: false, + }, + { + name: "response_TC_already_set_should_skip", + responseSize: 600, + family: "udp", + responseTCSet: true, + wantTCSet: true, // Should remain set + }, + { + name: "UDP_noEDNS_large_TC_already_set_should_skip", + responseSize: 600, + requestHasEDNS: false, + family: "udp", + responseTCSet: true, + wantTCSet: true, // Should remain set + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var response []byte + + // Create response of specified size + if tt.responseSize < headerBytes { + // For too-small test, create minimal invalid packet + response = make([]byte, tt.responseSize) + // Don't set any flags, just make it too small + } else { + response = makeResponseOfSize(t, domain, tt.responseSize, false) + actualSize := len(response) + + // Only adjust expectations for UDP queries that go through size checking + // TCP queries and other early-return cases should keep their original expectations + if tt.family == "udp" && !tt.responseTCSet && actualSize >= headerBytes { + // Determine the maximum allowed size based on request + var maxSize int + if tt.requestHasEDNS { + maxSize = int(tt.ednsSize) + } else { + maxSize = 512 // default UDP size per RFC 1035 + } + + // For edge cases where exact size matters, verify we're close enough + if tt.skipIfNotExact { + // For 512/513 byte tests, we need to be very close + if actualSize < tt.responseSize-10 || actualSize > tt.responseSize+10 { + t.Skipf("skipping: could not create response close to target size %d (got %d)", tt.responseSize, actualSize) + } + // Function sets TC if response > maxSize, so adjust expectation based on actual size + tt.wantTCSet = actualSize > maxSize + } else { + // For non-exact tests, adjust expectation based on actual response size + // The function sets TC if actualSize > maxSize + tt.wantTCSet = actualSize > maxSize + } + } + } + + // Set TC flag initially if requested + if tt.responseTCSet { + setTCFlagInPacket(response) + } + + // Create request with or without EDNS + var ednsSize uint16 + if tt.requestHasEDNS { + ednsSize = tt.ednsSize + } + request := makeTestRequest(t, domain, dns.TypeTXT, ednsSize) + + // Call the function + result := checkResponseSizeAndSetTC(response, request, tt.family, logf) + + // Verify response size is preserved (function should not truncate, only set flag) + if len(result) != len(response) { + t.Errorf("response size changed: got %d, want %d", len(result), len(response)) + } + + // Verify TC flag state + if len(result) >= headerBytes { + hasTC := truncatedFlagSet(result) + if hasTC != tt.wantTCSet { + t.Errorf("TC flag: got %v, want %v (response size=%d)", hasTC, tt.wantTCSet, len(result)) + } + } else if tt.responseSize >= headerBytes { + // If we expected a valid response but got too small, that's unexpected + t.Errorf("response too small (%d bytes) but expected valid response", len(result)) + } + + // Verify response pointer is same (should be in-place modification) + if &result[0] != &response[0] { + t.Errorf("function should modify response in place, but got new slice") + } + }) + } +} + func TestForwarderTCPFallback(t *testing.T) { const domain = "large-dns-response.tailscale.com." @@ -569,7 +974,10 @@ func TestForwarderTCPFallback(t *testing.T) { } }) - resp := mustRunTestQuery(t, request, beVerbose, port) + resp, err := runTestQueryWithFamily(t, request, "tcp", beVerbose, port) + if err != nil { + t.Fatalf("error making request: %v", err) + } if !bytes.Equal(resp, largeResponse) { t.Errorf("invalid response\ngot: %+v\nwant: %+v", resp, largeResponse) } @@ -636,17 +1044,13 @@ func TestForwarderTCPFallbackDisabled(t *testing.T) { resp := mustRunTestQuery(t, request, func(fwd *forwarder) { fwd.verboseFwd = true - // Disable retries for this test. - fwd.controlKnobs = &controlknobs.Knobs{} - fwd.controlKnobs.DisableDNSForwarderTCPRetries.Store(true) + setupForwarderWithTCPRetriesDisabled()(fwd) }, port) wantResp := append([]byte(nil), largeResponse[:maxResponseBytes]...) // Set the truncated flag on the expected response, since that's what we expect. - flags := binary.BigEndian.Uint16(wantResp[2:4]) - flags |= dnsFlagTruncated - binary.BigEndian.PutUint16(wantResp[2:4], flags) + setTCFlagInPacket(wantResp) if !bytes.Equal(resp, wantResp) { t.Errorf("invalid response\ngot (%d): %+v\nwant (%d): %+v", len(resp), resp, len(wantResp), wantResp) @@ -664,7 +1068,7 @@ func TestForwarderTCPFallbackError(t *testing.T) { response := makeTestResponse(t, domain, dns.RCodeServerFailure) // Our request is a single A query for the domain in the answer, above. - request := makeTestRequest(t, domain) + request := makeTestRequest(t, domain, dns.TypeA, 0) var sawRequest atomic.Bool port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) { @@ -695,7 +1099,7 @@ func TestForwarderTCPFallbackError(t *testing.T) { // returns a successful response, we propagate it. func TestForwarderWithManyResolvers(t *testing.T) { const domain = "example.com." - request := makeTestRequest(t, domain) + request := makeTestRequest(t, domain, dns.TypeA, 0) tests := []struct { name string @@ -837,20 +1241,7 @@ func TestNXDOMAINIncludesQuestion(t *testing.T) { }() // Our request is a single PTR query for the domain in the answer, above. - request := func() []byte { - builder := dns.NewBuilder(nil, dns.Header{}) - builder.StartQuestions() - builder.Question(dns.Question{ - Name: dns.MustNewName(domain), - Type: dns.TypePTR, - Class: dns.ClassINET, - }) - request, err := builder.Finish() - if err != nil { - t.Fatal(err) - } - return request - }() + request := makeTestRequest(t, domain, dns.TypePTR, 0) port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) { }) @@ -868,7 +1259,7 @@ func TestNXDOMAINIncludesQuestion(t *testing.T) { func TestForwarderVerboseLogs(t *testing.T) { const domain = "test.tailscale.com." response := makeTestResponse(t, domain, dns.RCodeServerFailure) - request := makeTestRequest(t, domain) + request := makeTestRequest(t, domain, dns.TypeA, 0) port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) { if !bytes.Equal(request, gotRequest) { diff --git a/net/dns/resolver/macios_ext.go b/net/dns/resolver/macios_ext.go index e3f979c194d91..c9b6626523d84 100644 --- a/net/dns/resolver/macios_ext.go +++ b/net/dns/resolver/macios_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_macext && (darwin || ios) diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index 3185cbe2b35ff..d0601de7bfe25 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package resolver implements a stub DNS resolver that can also serve @@ -39,6 +39,7 @@ import ( "tailscale.com/util/clientmetric" "tailscale.com/util/cloudenv" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) const dnsSymbolicFQDN = "magicdns.localhost-tailscale-daemon." @@ -69,6 +70,9 @@ type packet struct { // Else forward the query to the most specific matching entry in Routes. // Else return SERVFAIL. type Config struct { + // True if [Prefs.CorpDNS] is true or --accept-dns=true was specified. + // This should only be used for error handling and health reporting. + AcceptDNS bool // Routes is a map of DNS name suffix to the resolvers to use for // queries within that suffix. // Queries only match the most specific suffix. @@ -79,6 +83,12 @@ type Config struct { // LocalDomains is a list of DNS name suffixes that should not be // routed to upstream resolvers. LocalDomains []dnsname.FQDN + // SubdomainHosts is a set of FQDNs from Hosts that should also + // resolve subdomain queries to the same IPs. If a query like + // "sub.node.tailnet.ts.net" doesn't match Hosts directly, and + // "node.tailnet.ts.net" is in SubdomainHosts, the query resolves + // to the IPs for "node.tailnet.ts.net". + SubdomainHosts set.Set[dnsname.FQDN] } // WriteToBufioWriter write a debug version of c for logs to w, omitting @@ -214,10 +224,11 @@ type Resolver struct { closed chan struct{} // mu guards the following fields from being updated while used. - mu syncs.Mutex - localDomains []dnsname.FQDN - hostToIP map[dnsname.FQDN][]netip.Addr - ipToHost map[netip.Addr]dnsname.FQDN + mu syncs.Mutex + localDomains []dnsname.FQDN + hostToIP map[dnsname.FQDN][]netip.Addr + ipToHost map[netip.Addr]dnsname.FQDN + subdomainHosts set.Set[dnsname.FQDN] } type ForwardLinkSelector interface { @@ -271,13 +282,14 @@ func (r *Resolver) SetConfig(cfg Config) error { } } - r.forwarder.setRoutes(cfg.Routes) + r.forwarder.setRoutes(cfg.Routes, cfg.AcceptDNS) r.mu.Lock() defer r.mu.Unlock() r.localDomains = cfg.LocalDomains r.hostToIP = cfg.Hosts r.ipToHost = reverse + r.subdomainHosts = cfg.SubdomainHosts return nil } @@ -328,7 +340,12 @@ func (r *Resolver) Query(ctx context.Context, bs []byte, family string, from net return (<-responses).bs, nil } - return out, err + if err != nil { + return out, err + } + + out = checkResponseSizeAndSetTC(out, bs, family, r.logf) + return out, nil } // GetUpstreamResolvers returns the resolvers that would be used to resolve @@ -642,9 +659,18 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr, r.mu.Lock() hosts := r.hostToIP localDomains := r.localDomains + subdomainHosts := r.subdomainHosts r.mu.Unlock() addrs, found := hosts[domain] + if !found { + for parent := domain.Parent(); parent != ""; parent = parent.Parent() { + if subdomainHosts.Contains(parent) { + addrs, found = hosts[parent] + break + } + } + } if !found { for _, suffix := range localDomains { if suffix.Contains(domain) { diff --git a/net/dns/resolver/tsdns_server_test.go b/net/dns/resolver/tsdns_server_test.go index 82fd3bebf232c..9e18918b9d6e4 100644 --- a/net/dns/resolver/tsdns_server_test.go +++ b/net/dns/resolver/tsdns_server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index f0dbb48b33f6e..8ee22dd1384c0 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver @@ -32,6 +32,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/util/dnsname" "tailscale.com/util/eventbus/eventbustest" + "tailscale.com/util/set" ) var ( @@ -429,6 +430,56 @@ func TestResolveLocal(t *testing.T) { } } +func TestResolveLocalSubdomain(t *testing.T) { + r := newResolver(t) + defer r.Close() + + // Configure with SubdomainHosts set for test1.ipn.dev + cfg := Config{ + Hosts: map[dnsname.FQDN][]netip.Addr{ + "test1.ipn.dev.": {testipv4}, + "test2.ipn.dev.": {testipv6}, + }, + LocalDomains: []dnsname.FQDN{"ipn.dev."}, + SubdomainHosts: set.Of[dnsname.FQDN]("test1.ipn.dev."), + } + r.SetConfig(cfg) + + tests := []struct { + name string + qname dnsname.FQDN + qtype dns.Type + ip netip.Addr + code dns.RCode + }{ + // Exact matches still work + {"exact-ipv4", "test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess}, + {"exact-ipv6", "test2.ipn.dev.", dns.TypeAAAA, testipv6, dns.RCodeSuccess}, + + // Subdomain of test1 resolves (test1 has SubdomainHosts set) + {"subdomain-ipv4", "foo.test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess}, + {"subdomain-deep", "bar.foo.test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess}, // Multi-level subdomain + + // Subdomain of test2 does NOT resolve (test2 lacks SubdomainHosts) + {"subdomain-no-cap", "foo.test2.ipn.dev.", dns.TypeAAAA, netip.Addr{}, dns.RCodeNameError}, + + // Non-existent parent still returns NXDOMAIN + {"subdomain-no-parent", "foo.test3.ipn.dev.", dns.TypeA, netip.Addr{}, dns.RCodeNameError}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ip, code := r.resolveLocal(tt.qname, tt.qtype) + if code != tt.code { + t.Errorf("code = %v; want %v", code, tt.code) + } + if ip != tt.ip { + t.Errorf("ip = %v; want %v", ip, tt.ip) + } + }) + } +} + func TestResolveLocalReverse(t *testing.T) { r := newResolver(t) defer r.Close() @@ -1521,3 +1572,102 @@ func TestServfail(t *testing.T) { t.Errorf("response was %X, want %X", pkt, wantPkt) } } + +// TestLocalResponseTCFlagIntegration tests that checkResponseSizeAndSetTC is +// correctly applied to local DNS responses through the Resolver.Query integration path. +// This complements the unit test in forwarder_test.go by verifying the end-to-end behavior. +func TestLocalResponseTCFlagIntegration(t *testing.T) { + r := newResolver(t) + defer r.Close() + + r.SetConfig(dnsCfg) + + tests := []struct { + name string + query []byte + family string + wantTCSet bool + desc string + }{ + { + name: "UDP_small_local_response_no_TC", + query: dnspacket("test1.ipn.dev.", dns.TypeA, noEdns), + family: "udp", + wantTCSet: false, + desc: "Small local response (< 512 bytes) should not have TC flag set", + }, + { + name: "TCP_local_response_no_TC", + query: dnspacket("test1.ipn.dev.", dns.TypeA, noEdns), + family: "tcp", + wantTCSet: false, + desc: "TCP queries should skip TC flag setting (even for large responses)", + }, + { + name: "UDP_EDNS_request_small_response", + query: dnspacket("test1.ipn.dev.", dns.TypeA, 1500), + family: "udp", + wantTCSet: false, + desc: "Small response with EDNS request should not have TC flag set", + }, + { + name: "UDP_IPv6_response_no_TC", + query: dnspacket("test2.ipn.dev.", dns.TypeAAAA, noEdns), + family: "udp", + wantTCSet: false, + desc: "Small IPv6 local response should not have TC flag set", + }, + { + name: "UDP_reverse_lookup_no_TC", + query: dnspacket("4.3.2.1.in-addr.arpa.", dns.TypePTR, noEdns), + family: "udp", + wantTCSet: false, + desc: "Small reverse lookup response should not have TC flag set", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + response, err := r.Query(context.Background(), tt.query, tt.family, netip.AddrPort{}) + if err != nil { + t.Fatalf("Query failed: %v", err) + } + + if len(response) < headerBytes { + t.Fatalf("Response too small: %d bytes", len(response)) + } + + hasTC := truncatedFlagSet(response) + if hasTC != tt.wantTCSet { + t.Errorf("%s: TC flag = %v, want %v (response size=%d bytes)", tt.desc, hasTC, tt.wantTCSet, len(response)) + } + + // Verify response is valid by parsing it (if possible) + // Note: unpackResponse may not support all record types (e.g., PTR) + parsed, err := unpackResponse(response) + if err == nil { + // Verify the truncated field in parsed response matches the flag + if parsed.truncated != hasTC { + t.Errorf("Parsed truncated field (%v) doesn't match TC flag (%v)", parsed.truncated, hasTC) + } + } else { + // For unsupported types, just verify we can parse the header + var parser dns.Parser + h, err := parser.Start(response) + if err != nil { + t.Errorf("Failed to parse DNS header: %v", err) + } else { + // Verify header truncated flag matches + if h.Truncated != hasTC { + t.Errorf("Header truncated field (%v) doesn't match TC flag (%v)", h.Truncated, hasTC) + } + } + } + + // Verify response size is reasonable (local responses are typically small) + if len(response) > 1000 { + t.Logf("Warning: Local response is unusually large: %d bytes", len(response)) + } + }) + } +} diff --git a/net/dns/utf.go b/net/dns/utf.go index 0c1db69acb33b..b18cdebb4eb56 100644 --- a/net/dns/utf.go +++ b/net/dns/utf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/utf_test.go b/net/dns/utf_test.go index b5fd372622519..7ae5a6854d34c 100644 --- a/net/dns/utf_test.go +++ b/net/dns/utf_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/wsl_windows.go b/net/dns/wsl_windows.go index 81e8593160c02..c2400746b8a2d 100644 --- a/net/dns/wsl_windows.go +++ b/net/dns/wsl_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dnscache/dnscache.go b/net/dnscache/dnscache.go index e222b983f0287..8300917248773 100644 --- a/net/dnscache/dnscache.go +++ b/net/dnscache/dnscache.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dnscache contains a minimal DNS cache that makes a bunch of diff --git a/net/dnscache/dnscache_test.go b/net/dnscache/dnscache_test.go index 58bb6cd7f594c..9306c62cc90ee 100644 --- a/net/dnscache/dnscache_test.go +++ b/net/dnscache/dnscache_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnscache diff --git a/net/dnscache/messagecache.go b/net/dnscache/messagecache.go index 040706b9c7746..9bdedf19e6308 100644 --- a/net/dnscache/messagecache.go +++ b/net/dnscache/messagecache.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnscache diff --git a/net/dnscache/messagecache_test.go b/net/dnscache/messagecache_test.go index 0bedfa5ad78e7..79fa49360a281 100644 --- a/net/dnscache/messagecache_test.go +++ b/net/dnscache/messagecache_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnscache diff --git a/net/dnsfallback/dnsfallback.go b/net/dnsfallback/dnsfallback.go index 74b625970302b..5467127762ecd 100644 --- a/net/dnsfallback/dnsfallback.go +++ b/net/dnsfallback/dnsfallback.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dnsfallback contains a DNS fallback mechanism diff --git a/net/dnsfallback/dnsfallback_test.go b/net/dnsfallback/dnsfallback_test.go index 7f881057450e7..4e816c3405e3a 100644 --- a/net/dnsfallback/dnsfallback_test.go +++ b/net/dnsfallback/dnsfallback_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnsfallback diff --git a/net/dnsfallback/update-dns-fallbacks.go b/net/dnsfallback/update-dns-fallbacks.go index 384e77e104cdc..173b464582257 100644 --- a/net/dnsfallback/update-dns-fallbacks.go +++ b/net/dnsfallback/update-dns-fallbacks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/net/flowtrack/flowtrack.go b/net/flowtrack/flowtrack.go index 8b3d799f7bbf4..5df34a5095219 100644 --- a/net/flowtrack/flowtrack.go +++ b/net/flowtrack/flowtrack.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // // Original implementation (from same author) from which this was derived was: diff --git a/net/flowtrack/flowtrack_test.go b/net/flowtrack/flowtrack_test.go index 1a13f7753a547..21e2021e1216f 100644 --- a/net/flowtrack/flowtrack_test.go +++ b/net/flowtrack/flowtrack_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package flowtrack diff --git a/net/ipset/ipset.go b/net/ipset/ipset.go index 27c1e27ed4180..92cec9d0be854 100644 --- a/net/ipset/ipset.go +++ b/net/ipset/ipset.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipset provides code for creating efficient IP-in-set lookup functions diff --git a/net/ipset/ipset_test.go b/net/ipset/ipset_test.go index 2df4939cb99ad..291416f380ef6 100644 --- a/net/ipset/ipset_test.go +++ b/net/ipset/ipset_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipset diff --git a/net/ktimeout/ktimeout.go b/net/ktimeout/ktimeout.go index 7cd4391435ed0..abe049b06380a 100644 --- a/net/ktimeout/ktimeout.go +++ b/net/ktimeout/ktimeout.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ktimeout configures kernel TCP stack timeouts via the provided diff --git a/net/ktimeout/ktimeout_default.go b/net/ktimeout/ktimeout_default.go index f1b11661b1335..2304245b3fe21 100644 --- a/net/ktimeout/ktimeout_default.go +++ b/net/ktimeout/ktimeout_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/net/ktimeout/ktimeout_linux.go b/net/ktimeout/ktimeout_linux.go index 84286b647bcba..634c32119b569 100644 --- a/net/ktimeout/ktimeout_linux.go +++ b/net/ktimeout/ktimeout_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ktimeout diff --git a/net/ktimeout/ktimeout_linux_test.go b/net/ktimeout/ktimeout_linux_test.go index 0330923a96c13..dc3dbe12b9363 100644 --- a/net/ktimeout/ktimeout_linux_test.go +++ b/net/ktimeout/ktimeout_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ktimeout diff --git a/net/ktimeout/ktimeout_test.go b/net/ktimeout/ktimeout_test.go index b534f046caddb..f361d35cbbbe5 100644 --- a/net/ktimeout/ktimeout_test.go +++ b/net/ktimeout/ktimeout_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ktimeout diff --git a/net/memnet/conn.go b/net/memnet/conn.go index a9e1fd39901a0..8cab63403bc5e 100644 --- a/net/memnet/conn.go +++ b/net/memnet/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/conn_test.go b/net/memnet/conn_test.go index 743ce5248cb9d..340c4c6ee00eb 100644 --- a/net/memnet/conn_test.go +++ b/net/memnet/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/listener.go b/net/memnet/listener.go index dded97995bbc1..5d751b12e7f1a 100644 --- a/net/memnet/listener.go +++ b/net/memnet/listener.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/listener_test.go b/net/memnet/listener_test.go index b6ceb3dfa94cf..6c767ed57be7a 100644 --- a/net/memnet/listener_test.go +++ b/net/memnet/listener_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/memnet.go b/net/memnet/memnet.go index db9e3872f6f26..25b1062a19cec 100644 --- a/net/memnet/memnet.go +++ b/net/memnet/memnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package memnet implements an in-memory network implementation. diff --git a/net/memnet/memnet_test.go b/net/memnet/memnet_test.go index 38086cec05f3c..d5a53ba81cb9f 100644 --- a/net/memnet/memnet_test.go +++ b/net/memnet/memnet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/pipe.go b/net/memnet/pipe.go index 47163508353a6..8caca57df2f81 100644 --- a/net/memnet/pipe.go +++ b/net/memnet/pipe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/pipe_test.go b/net/memnet/pipe_test.go index a86d65388e27d..ebd9dd8c2323f 100644 --- a/net/memnet/pipe_test.go +++ b/net/memnet/pipe_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/netaddr/netaddr.go b/net/netaddr/netaddr.go index a04acd57aa670..7057a8eec58e8 100644 --- a/net/netaddr/netaddr.go +++ b/net/netaddr/netaddr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netaddr is a transitional package while we finish migrating from inet.af/netaddr diff --git a/net/netcheck/captiveportal.go b/net/netcheck/captiveportal.go index ad11f19a05b6b..310e98ce73a79 100644 --- a/net/netcheck/captiveportal.go +++ b/net/netcheck/captiveportal.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_captiveportal diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index c5a3d2392007e..ebcdc4eaca4e3 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netcheck checks the network conditions from the current host. diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index 6830e7f27075c..ab7f58febcb3b 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netcheck diff --git a/net/netcheck/standalone.go b/net/netcheck/standalone.go index b4523a832d463..88d5b4cc5a7f1 100644 --- a/net/netcheck/standalone.go +++ b/net/netcheck/standalone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netcheck diff --git a/net/neterror/neterror.go b/net/neterror/neterror.go index e2387440d33d5..43b96999841a1 100644 --- a/net/neterror/neterror.go +++ b/net/neterror/neterror.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package neterror classifies network errors. diff --git a/net/neterror/neterror_js.go b/net/neterror/neterror_js.go new file mode 100644 index 0000000000000..591367120fd85 --- /dev/null +++ b/net/neterror/neterror_js.go @@ -0,0 +1,20 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build js || wasip1 || wasm + +package neterror + +import ( + "errors" + "io" + "io/fs" +) + +// Reports whether err resulted from reading or writing to a closed or broken pipe. +func IsClosedPipeError(err error) bool { + // Libraries may also return root errors like fs.ErrClosed/io.ErrClosedPipe + // due to a closed socket. + return errors.Is(err, fs.ErrClosed) || + errors.Is(err, io.ErrClosedPipe) +} diff --git a/net/neterror/neterror_linux.go b/net/neterror/neterror_linux.go index 857367fe8ebb5..9add4fd1d213c 100644 --- a/net/neterror/neterror_linux.go +++ b/net/neterror/neterror_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package neterror diff --git a/net/neterror/neterror_linux_test.go b/net/neterror/neterror_linux_test.go index 5b99060741351..d8846219afad2 100644 --- a/net/neterror/neterror_linux_test.go +++ b/net/neterror/neterror_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package neterror diff --git a/net/neterror/neterror_plan9.go b/net/neterror/neterror_plan9.go new file mode 100644 index 0000000000000..a60c4dd6496fa --- /dev/null +++ b/net/neterror/neterror_plan9.go @@ -0,0 +1,24 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build plan9 + +package neterror + +import ( + "errors" + "io" + "io/fs" + "strings" +) + +// Reports whether err resulted from reading or writing to a closed or broken pipe. +func IsClosedPipeError(err error) bool { + // Libraries may also return root errors like fs.ErrClosed/io.ErrClosedPipe + // due to a closed socket. + // For a raw syscall error, check for error string containing "closed pipe", + // per the note set by the system: https://9p.io/magic/man2html/2/pipe + return errors.Is(err, fs.ErrClosed) || + errors.Is(err, io.ErrClosedPipe) || + strings.Contains(err.Error(), "closed pipe") +} diff --git a/net/neterror/neterror_posix.go b/net/neterror/neterror_posix.go new file mode 100644 index 0000000000000..71dda6b4cf3a8 --- /dev/null +++ b/net/neterror/neterror_posix.go @@ -0,0 +1,32 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 && !js && !wasip1 && !wasm + +package neterror + +import ( + "errors" + "io" + "io/fs" + "runtime" + "syscall" +) + +// Reports whether err resulted from reading or writing to a closed or broken pipe. +func IsClosedPipeError(err error) bool { + // 232 is Windows error code ERROR_NO_DATA, "The pipe is being closed". + if runtime.GOOS == "windows" && errors.Is(err, syscall.Errno(232)) { + return true + } + + // EPIPE/ENOTCONN are common errors when a send fails due to a closed + // socket. There is some platform and version inconsistency in which + // error is returned, but the meaning is the same. + // Libraries may also return root errors like fs.ErrClosed/io.ErrClosedPipe + // due to a closed socket. + return errors.Is(err, syscall.EPIPE) || + errors.Is(err, syscall.ENOTCONN) || + errors.Is(err, fs.ErrClosed) || + errors.Is(err, io.ErrClosedPipe) +} diff --git a/net/neterror/neterror_windows.go b/net/neterror/neterror_windows.go index bf112f5ed7ab7..4b0b2c8024c31 100644 --- a/net/neterror/neterror_windows.go +++ b/net/neterror/neterror_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package neterror diff --git a/net/netkernelconf/netkernelconf.go b/net/netkernelconf/netkernelconf.go index 3ea502b377fdf..7840074c9fbd2 100644 --- a/net/netkernelconf/netkernelconf.go +++ b/net/netkernelconf/netkernelconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netkernelconf contains code for checking kernel netdev config. diff --git a/net/netkernelconf/netkernelconf_default.go b/net/netkernelconf/netkernelconf_default.go index 3e160e5edf5b0..8e3f2061d999c 100644 --- a/net/netkernelconf/netkernelconf_default.go +++ b/net/netkernelconf/netkernelconf_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || android diff --git a/net/netkernelconf/netkernelconf_linux.go b/net/netkernelconf/netkernelconf_linux.go index 2a4f0a049f56d..b8c165ac558cf 100644 --- a/net/netkernelconf/netkernelconf_linux.go +++ b/net/netkernelconf/netkernelconf_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/netknob/netknob.go b/net/netknob/netknob.go index 53171f4243f8d..a870af824a3ed 100644 --- a/net/netknob/netknob.go +++ b/net/netknob/netknob.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netknob has Tailscale network knobs. diff --git a/net/netmon/defaultroute_bsd.go b/net/netmon/defaultroute_bsd.go index 9195ae0730ebc..741948599758e 100644 --- a/net/netmon/defaultroute_bsd.go +++ b/net/netmon/defaultroute_bsd.go @@ -1,11 +1,10 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -// Common code for FreeBSD. This might also work on other -// BSD systems (e.g. OpenBSD) but has not been tested. +// Common code for FreeBSD and OpenBSD. // Not used on iOS or macOS. See defaultroute_darwin.go. -//go:build freebsd +//go:build freebsd || openbsd package netmon diff --git a/net/netmon/defaultroute_darwin.go b/net/netmon/defaultroute_darwin.go index 57f7e22b7ddce..121535937bc22 100644 --- a/net/netmon/defaultroute_darwin.go +++ b/net/netmon/defaultroute_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || ios diff --git a/net/netmon/interfaces_android.go b/net/netmon/interfaces_android.go index 26104e879a393..2cd7f23f6f164 100644 --- a/net/netmon/interfaces_android.go +++ b/net/netmon/interfaces_android.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_bsd.go b/net/netmon/interfaces_bsd.go index 86bc5615d2321..4c09aa55eeb31 100644 --- a/net/netmon/interfaces_bsd.go +++ b/net/netmon/interfaces_bsd.go @@ -1,10 +1,10 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Common code for FreeBSD and Darwin. This might also work on other // BSD systems (e.g. OpenBSD) but has not been tested. -//go:build darwin || freebsd +//go:build darwin || freebsd || openbsd package netmon diff --git a/net/netmon/interfaces_freebsd.go b/net/netmon/interfaces_bsdroute.go similarity index 80% rename from net/netmon/interfaces_freebsd.go rename to net/netmon/interfaces_bsdroute.go index 654eb5316384a..7ac28c4b576fd 100644 --- a/net/netmon/interfaces_freebsd.go +++ b/net/netmon/interfaces_bsdroute.go @@ -1,9 +1,9 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -// This might work on other BSDs, but only tested on FreeBSD. +// FreeBSD and OpenBSD routing table functions. -//go:build freebsd +//go:build freebsd || openbsd package netmon diff --git a/net/netmon/interfaces_darwin.go b/net/netmon/interfaces_darwin.go index 126040350bdb2..c0f588fd20c1b 100644 --- a/net/netmon/interfaces_darwin.go +++ b/net/netmon/interfaces_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_darwin_test.go b/net/netmon/interfaces_darwin_test.go index c3d40a6f0e34e..e4b84a144a432 100644 --- a/net/netmon/interfaces_darwin_test.go +++ b/net/netmon/interfaces_darwin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_default_route_test.go b/net/netmon/interfaces_default_route_test.go index e231eea9ac794..76424aef7af2f 100644 --- a/net/netmon/interfaces_default_route_test.go +++ b/net/netmon/interfaces_default_route_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || (darwin && !ts_macext) diff --git a/net/netmon/interfaces_defaultrouteif_todo.go b/net/netmon/interfaces_defaultrouteif_todo.go index df0820fa94eb4..55d284153815e 100644 --- a/net/netmon/interfaces_defaultrouteif_todo.go +++ b/net/netmon/interfaces_defaultrouteif_todo.go @@ -1,7 +1,7 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build !linux && !windows && !darwin && !freebsd && !android +//go:build !linux && !windows && !darwin && !freebsd && !android && !openbsd package netmon diff --git a/net/netmon/interfaces_linux.go b/net/netmon/interfaces_linux.go index a9b93c0a1ff49..64cb0b9af2ce6 100644 --- a/net/netmon/interfaces_linux.go +++ b/net/netmon/interfaces_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android diff --git a/net/netmon/interfaces_linux_test.go b/net/netmon/interfaces_linux_test.go index 4f740ac28ba08..5a29c4b8b3ada 100644 --- a/net/netmon/interfaces_linux_test.go +++ b/net/netmon/interfaces_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_test.go b/net/netmon/interfaces_test.go index e4274819f90df..bd81eb96a42bf 100644 --- a/net/netmon/interfaces_test.go +++ b/net/netmon/interfaces_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_windows.go b/net/netmon/interfaces_windows.go index d6625ead3cd05..070b08ba658e2 100644 --- a/net/netmon/interfaces_windows.go +++ b/net/netmon/interfaces_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_windows_test.go b/net/netmon/interfaces_windows_test.go index 91db7bcc5266c..13526612eb477 100644 --- a/net/netmon/interfaces_windows_test.go +++ b/net/netmon/interfaces_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/loghelper.go b/net/netmon/loghelper.go index 2876e9b12481c..bddbd4d616462 100644 --- a/net/netmon/loghelper.go +++ b/net/netmon/loghelper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/loghelper_test.go b/net/netmon/loghelper_test.go index 468a12505f322..aec5206443aa4 100644 --- a/net/netmon/loghelper_test.go +++ b/net/netmon/loghelper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon.go b/net/netmon/netmon.go index e18bc392dd196..1d51379d86e31 100644 --- a/net/netmon/netmon.go +++ b/net/netmon/netmon.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package monitor provides facilities for monitoring network @@ -201,12 +201,7 @@ func (cd *ChangeDelta) AnyInterfaceUp() bool { if cd.new == nil { return false } - for _, ifi := range cd.new.Interface { - if ifi.IsUp() { - return true - } - } - return false + return cd.new.AnyInterfaceUp() } // isInterestingInterfaceChange reports whether any interfaces have changed in a meaningful way. diff --git a/net/netmon/netmon_darwin.go b/net/netmon/netmon_darwin.go index 042f9a3b750c2..588cbf6161845 100644 --- a/net/netmon/netmon_darwin.go +++ b/net/netmon/netmon_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon_darwin_test.go b/net/netmon/netmon_darwin_test.go index 84c67cf6fa3e2..e57b5ca84c146 100644 --- a/net/netmon/netmon_darwin_test.go +++ b/net/netmon/netmon_darwin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon_freebsd.go b/net/netmon/netmon_freebsd.go index 3a4fb44d8f0a0..8e99532c589b6 100644 --- a/net/netmon/netmon_freebsd.go +++ b/net/netmon/netmon_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon_linux.go b/net/netmon/netmon_linux.go index aa5253f9be28b..b7d87f995634f 100644 --- a/net/netmon/netmon_linux.go +++ b/net/netmon/netmon_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android diff --git a/net/netmon/netmon_linux_test.go b/net/netmon/netmon_linux_test.go index 75d7c646559f1..c6c12e850f3fe 100644 --- a/net/netmon/netmon_linux_test.go +++ b/net/netmon/netmon_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/netmon/netmon_polling.go b/net/netmon/netmon_polling.go index 3b5ef6fe9206f..bdeb43005782b 100644 --- a/net/netmon/netmon_polling.go +++ b/net/netmon/netmon_polling.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (!linux && !freebsd && !windows && !darwin) || android diff --git a/net/netmon/netmon_test.go b/net/netmon/netmon_test.go index 50519b4a9c531..97c203274cd8f 100644 --- a/net/netmon/netmon_test.go +++ b/net/netmon/netmon_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon_windows.go b/net/netmon/netmon_windows.go index e8966faf00f46..91c137de0e328 100644 --- a/net/netmon/netmon_windows.go +++ b/net/netmon/netmon_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/polling.go b/net/netmon/polling.go index 2a3e44cba0b9d..806f0b0451fe1 100644 --- a/net/netmon/polling.go +++ b/net/netmon/polling.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !darwin diff --git a/net/netmon/state.go b/net/netmon/state.go index 79dd8a01ba9e1..cdfa1d0fbe552 100644 --- a/net/netmon/state.go +++ b/net/netmon/state.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon @@ -41,7 +41,12 @@ func isProblematicInterface(nif *net.Interface) bool { // DoS each other by doing traffic amplification, both of them // preferring/trying to use each other for transport. See: // https://github.com/tailscale/tailscale/issues/1208 - if strings.HasPrefix(name, "zt") || (runtime.GOOS == "windows" && strings.Contains(name, "ZeroTier")) { + // TODO(https://github.com/tailscale/tailscale/issues/18824): maybe exclude + // "WireGuard tunnel 0" as well on Windows (NetBird), but the name seems too + // generic where there is not a platform standard (on Linux wt0 is at least + // explicitly different from the WireGuard conventional default of wg0). + if strings.HasPrefix(name, "zt") || name == "wt0" /* NetBird */ || + (runtime.GOOS == "windows" && strings.Contains(name, "ZeroTier")) { return true } return false diff --git a/net/netns/mksyscall.go b/net/netns/mksyscall.go index ff2c0b8610657..2a8a2176b9c84 100644 --- a/net/netns/mksyscall.go +++ b/net/netns/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/netns.go b/net/netns/netns.go index 81ab5e2a212a6..5d692c787eae8 100644 --- a/net/netns/netns.go +++ b/net/netns/netns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netns contains the common code for using the Go net package diff --git a/net/netns/netns_android.go b/net/netns/netns_android.go index 162e5c79a62fa..e747f61f40e50 100644 --- a/net/netns/netns_android.go +++ b/net/netns/netns_android.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build android diff --git a/net/netns/netns_darwin.go b/net/netns/netns_darwin.go index ff05a3f3139c3..e5d01542edfb4 100644 --- a/net/netns/netns_darwin.go +++ b/net/netns/netns_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/net/netns/netns_darwin_test.go b/net/netns/netns_darwin_test.go index 2030c169ef68b..768b095b82739 100644 --- a/net/netns/netns_darwin_test.go +++ b/net/netns/netns_darwin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/netns_default.go b/net/netns/netns_default.go index 58c5936640e4f..33f4c1333e395 100644 --- a/net/netns/netns_default.go +++ b/net/netns/netns_default.go @@ -1,7 +1,7 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build !linux && !windows && !darwin +//go:build !linux && !windows && !darwin && !openbsd package netns diff --git a/net/netns/netns_dw.go b/net/netns/netns_dw.go index b9f750e8a6657..82494737130b6 100644 --- a/net/netns/netns_dw.go +++ b/net/netns/netns_dw.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || windows diff --git a/net/netns/netns_linux.go b/net/netns/netns_linux.go index 609f524b5cc01..02b2dd89b197f 100644 --- a/net/netns/netns_linux.go +++ b/net/netns/netns_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/netns/netns_linux_test.go b/net/netns/netns_linux_test.go index a5000f37f0a44..e467ee41405d6 100644 --- a/net/netns/netns_linux_test.go +++ b/net/netns/netns_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/netns_openbsd.go b/net/netns/netns_openbsd.go new file mode 100644 index 0000000000000..47968bd42f35e --- /dev/null +++ b/net/netns/netns_openbsd.go @@ -0,0 +1,178 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build openbsd + +package netns + +import ( + "fmt" + "os/exec" + "strconv" + "strings" + "sync" + "syscall" + + "golang.org/x/sys/unix" + "tailscale.com/net/netmon" + "tailscale.com/types/logger" +) + +var ( + bypassMu sync.Mutex + bypassRtable int +) + +// Called by the router when exit node routes are configured. +func SetBypassRtable(rtable int) { + bypassMu.Lock() + defer bypassMu.Unlock() + bypassRtable = rtable +} + +func GetBypassRtable() int { + bypassMu.Lock() + defer bypassMu.Unlock() + return bypassRtable +} + +func control(logf logger.Logf, _ *netmon.Monitor) func(network, address string, c syscall.RawConn) error { + return func(network, address string, c syscall.RawConn) error { + return controlC(logf, network, address, c) + } +} + +func controlC(logf logger.Logf, _, address string, c syscall.RawConn) error { + if isLocalhost(address) { + return nil + } + + rtable := GetBypassRtable() + if rtable == 0 { + return nil + } + + return bindToRtable(c, rtable, logf) +} + +func bindToRtable(c syscall.RawConn, rtable int, logf logger.Logf) error { + var sockErr error + err := c.Control(func(fd uintptr) { + sockErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RTABLE, rtable) + }) + if sockErr != nil { + logf("netns: SO_RTABLE(%d): %v", rtable, sockErr) + } + if err != nil { + return fmt.Errorf("RawConn.Control: %w", err) + } + return sockErr +} + +// SetupBypassRtable creates a bypass rtable with the existing default route +// in it routing through its existing physical interface. It should be called +// by the router when exit node routes are being added. +// Returns the rtable number. +func SetupBypassRtable(logf logger.Logf) (int, error) { + bypassMu.Lock() + defer bypassMu.Unlock() + + if bypassRtable != 0 { + return bypassRtable, nil + } + + gw, err := getPhysicalGateway() + if err != nil { + return 0, fmt.Errorf("getPhysicalGateway: %w", err) + } + + rtable, err := findAvailableRtable() + if err != nil { + return 0, fmt.Errorf("findAvailableRtable: %w", err) + } + + // Add the existing default route interface to the new bypass rtable + out, err := exec.Command("route", "-T", strconv.Itoa(rtable), "-qn", "add", "default", gw).CombinedOutput() + if err != nil { + return 0, fmt.Errorf("route -T%d add default %s: %w\n%s", rtable, gw, err, out) + } + + bypassRtable = rtable + logf("netns: created bypass rtable %d with default route via %s", rtable, gw) + return rtable, nil +} + +func CleanupBypassRtable(logf logger.Logf) { + bypassMu.Lock() + defer bypassMu.Unlock() + + if bypassRtable == 0 { + return + } + + // Delete the default route from the bypass rtable which should clear it + out, err := exec.Command("route", "-T", strconv.Itoa(bypassRtable), "-qn", "delete", "default").CombinedOutput() + if err != nil { + logf("netns: failed to clear bypass route: %v\n%s", err, out) + } else { + logf("netns: cleared bypass rtable %d", bypassRtable) + } + + bypassRtable = 0 +} + +// getPhysicalGateway returns the default gateway IP that goes through a +// physical interface (not tun). +func getPhysicalGateway() (string, error) { + out, err := exec.Command("route", "-n", "show", "-inet").CombinedOutput() + if err != nil { + return "", fmt.Errorf("route show: %w", err) + } + + // Parse the routing table looking for default routes not via tun + for _, line := range strings.Split(string(out), "\n") { + fields := strings.Fields(line) + if len(fields) < 8 { + continue + } + // Format: Destination Gateway Flags Refs Use Mtu Prio Iface + dest := fields[0] + gateway := fields[1] + iface := fields[7] + + if dest == "default" && !strings.HasPrefix(iface, "tun") { + return gateway, nil + } + } + + return "", fmt.Errorf("no physical default gateway found") +} + +func findAvailableRtable() (int, error) { + for i := 1; i <= 255; i++ { + out, err := exec.Command("route", "-T", strconv.Itoa(i), "-n", "show", "-inet").CombinedOutput() + if err != nil { + // rtable doesn't exist, consider it available + return i, nil + } + // Check if the output only contains the header (no actual routes) + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + hasRoutes := false + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "Routing") || strings.HasPrefix(line, "Destination") { + continue + } + hasRoutes = true + break + } + if !hasRoutes { + return i, nil + } + } + return 0, fmt.Errorf("no available rtable") +} + +func UseSocketMark() bool { + return false +} diff --git a/net/netns/netns_test.go b/net/netns/netns_test.go index 82f919b946d4a..9ecc19b424f95 100644 --- a/net/netns/netns_test.go +++ b/net/netns/netns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netns contains the common code for using the Go net package diff --git a/net/netns/netns_windows.go b/net/netns/netns_windows.go index afbda0f47ece6..686c813f6b1d1 100644 --- a/net/netns/netns_windows.go +++ b/net/netns/netns_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/netns_windows_test.go b/net/netns/netns_windows_test.go index 390604f465041..67e7b3de86c09 100644 --- a/net/netns/netns_windows_test.go +++ b/net/netns/netns_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/socks.go b/net/netns/socks.go index 9a137db7f5b18..7746e91778353 100644 --- a/net/netns/socks.go +++ b/net/netns/socks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !js && !android && !ts_omit_useproxy diff --git a/net/netstat/netstat.go b/net/netstat/netstat.go index 53c7d7757eac6..44b421d5d15b5 100644 --- a/net/netstat/netstat.go +++ b/net/netstat/netstat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netstat returns the local machine's network connection table. diff --git a/net/netstat/netstat_noimpl.go b/net/netstat/netstat_noimpl.go index e455c8ce931de..78bb018f213ad 100644 --- a/net/netstat/netstat_noimpl.go +++ b/net/netstat/netstat_noimpl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/netstat/netstat_test.go b/net/netstat/netstat_test.go index 38827df5ef65a..8407db778f001 100644 --- a/net/netstat/netstat_test.go +++ b/net/netstat/netstat_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstat diff --git a/net/netstat/netstat_windows.go b/net/netstat/netstat_windows.go index 24191a50eadab..4b3edbdf8134b 100644 --- a/net/netstat/netstat_windows.go +++ b/net/netstat/netstat_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstat diff --git a/net/netutil/default_interface_portable.go b/net/netutil/default_interface_portable.go index d75cefb7aec74..2a80553715aec 100644 --- a/net/netutil/default_interface_portable.go +++ b/net/netutil/default_interface_portable.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil diff --git a/net/netutil/default_interface_portable_test.go b/net/netutil/default_interface_portable_test.go index 03dce340505a5..b54733747524f 100644 --- a/net/netutil/default_interface_portable_test.go +++ b/net/netutil/default_interface_portable_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil diff --git a/net/netutil/ip_forward.go b/net/netutil/ip_forward.go index c64a9e4269ae0..bc0f1961dfcbc 100644 --- a/net/netutil/ip_forward.go +++ b/net/netutil/ip_forward.go @@ -1,11 +1,10 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil import ( "bytes" - "errors" "fmt" "net/netip" "os" @@ -146,64 +145,6 @@ func CheckIPForwarding(routes []netip.Prefix, state *netmon.State) (warn, err er return nil, nil } -// CheckReversePathFiltering reports whether reverse path filtering is either -// disabled or set to 'loose' mode for exit node functionality on any -// interface. -// -// The routes should only be advertised routes, and should not contain the -// node's Tailscale IPs. -// -// This function returns an error if it is unable to determine whether reverse -// path filtering is enabled, or a warning describing configuration issues if -// reverse path fitering is non-functional or partly functional. -func CheckReversePathFiltering(state *netmon.State) (warn []string, err error) { - if runtime.GOOS != "linux" { - return nil, nil - } - - if state == nil { - return nil, errors.New("no link state") - } - - // The kernel uses the maximum value for rp_filter between the 'all' - // setting and each per-interface config, so we need to fetch both. - allSetting, err := reversePathFilterValueLinux("all") - if err != nil { - return nil, fmt.Errorf("reading global rp_filter value: %w", err) - } - - const ( - filtOff = 0 - filtStrict = 1 - filtLoose = 2 - ) - - // Because the kernel use the max rp_filter value, each interface will use 'loose', so we - // can abort early. - if allSetting == filtLoose { - return nil, nil - } - - for _, iface := range state.Interface { - if iface.IsLoopback() { - continue - } - - iSetting, err := reversePathFilterValueLinux(iface.Name) - if err != nil { - return nil, fmt.Errorf("reading interface rp_filter value for %q: %w", iface.Name, err) - } - // Perform the same max() that the kernel does - if allSetting > iSetting { - iSetting = allSetting - } - if iSetting == filtStrict { - warn = append(warn, fmt.Sprintf("interface %q has strict reverse-path filtering enabled", iface.Name)) - } - } - return warn, nil -} - // ipForwardSysctlKey returns the sysctl key for the given protocol and iface. // When the dotFormat parameter is true the output is formatted as `net.ipv4.ip_forward`, // else it is `net/ipv4/ip_forward` @@ -235,25 +176,6 @@ func ipForwardSysctlKey(format sysctlFormat, p protocol, iface string) string { return fmt.Sprintf(k, iface) } -// rpFilterSysctlKey returns the sysctl key for the given iface. -// -// Format controls whether the output is formatted as -// `net.ipv4.conf.iface.rp_filter` or `net/ipv4/conf/iface/rp_filter`. -func rpFilterSysctlKey(format sysctlFormat, iface string) string { - // No iface means all interfaces - if iface == "" { - iface = "all" - } - - k := "net/ipv4/conf/%s/rp_filter" - if format == dotFormat { - // Swap the delimiters. - iface = strings.ReplaceAll(iface, ".", "/") - k = strings.ReplaceAll(k, "/", ".") - } - return fmt.Sprintf(k, iface) -} - type sysctlFormat int const ( @@ -305,32 +227,6 @@ func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) { return on, nil } -// reversePathFilterValueLinux reports the reverse path filter setting on Linux -// for the given interface. -// -// The iface param determines which interface to check against; the empty -// string means to check the global config. -// -// This function tries to look up the value directly from `/proc/sys`, and -// falls back to using the `sysctl` command on failure. -func reversePathFilterValueLinux(iface string) (int, error) { - k := rpFilterSysctlKey(slashFormat, iface) - bs, err := os.ReadFile(filepath.Join("/proc/sys", k)) - if err != nil { - // Fall back to the sysctl command - k := rpFilterSysctlKey(dotFormat, iface) - bs, err = exec.Command("sysctl", "-n", k).Output() - if err != nil { - return -1, fmt.Errorf("couldn't check %s (%v)", k, err) - } - } - v, err := strconv.Atoi(string(bytes.TrimSpace(bs))) - if err != nil { - return -1, fmt.Errorf("couldn't parse %s (%v)", k, err) - } - return v, nil -} - func ipForwardingEnabledSunOS(p protocol, iface string) (bool, error) { var proto string if p == ipv4 { diff --git a/net/netutil/netutil.go b/net/netutil/netutil.go index 5c42f51c64837..13882988594d1 100644 --- a/net/netutil/netutil.go +++ b/net/netutil/netutil.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netutil contains misc shared networking code & types. diff --git a/net/netutil/netutil_test.go b/net/netutil/netutil_test.go index 0523946e63c9b..2c40d8d9ee68e 100644 --- a/net/netutil/netutil_test.go +++ b/net/netutil/netutil_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil @@ -8,9 +8,6 @@ import ( "net" "runtime" "testing" - - "tailscale.com/net/netmon" - "tailscale.com/util/eventbus" ) type conn struct { @@ -68,21 +65,3 @@ func TestIPForwardingEnabledLinux(t *testing.T) { t.Errorf("got true; want false") } } - -func TestCheckReversePathFiltering(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skipf("skipping on %s", runtime.GOOS) - } - bus := eventbus.New() - defer bus.Close() - - netMon, err := netmon.New(bus, t.Logf) - if err != nil { - t.Fatal(err) - } - defer netMon.Close() - - warn, err := CheckReversePathFiltering(netMon.InterfaceState()) - t.Logf("err: %v", err) - t.Logf("warnings: %v", warn) -} diff --git a/net/netutil/routes.go b/net/netutil/routes.go index 7d67d3695e10d..c8212b9af66dd 100644 --- a/net/netutil/routes.go +++ b/net/netutil/routes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil diff --git a/net/netx/netx.go b/net/netx/netx.go index 014daa9a795cb..fba6567c4c312 100644 --- a/net/netx/netx.go +++ b/net/netx/netx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netx contains types to describe and abstract over how dialing and diff --git a/net/packet/capture.go b/net/packet/capture.go index dd0ca411f2051..630a4b1610c2b 100644 --- a/net/packet/capture.go +++ b/net/packet/capture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/checksum/checksum.go b/net/packet/checksum/checksum.go index 4b5b82174a22f..e6918e7ae1c9f 100644 --- a/net/packet/checksum/checksum.go +++ b/net/packet/checksum/checksum.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package checksum provides functions for updating checksums in parsed packets. diff --git a/net/packet/checksum/checksum_test.go b/net/packet/checksum/checksum_test.go index bf818743d3dbf..ab7c783b3e96a 100644 --- a/net/packet/checksum/checksum_test.go +++ b/net/packet/checksum/checksum_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package checksum diff --git a/net/packet/doc.go b/net/packet/doc.go index ce6c0c30716c6..4a62b8aa77727 100644 --- a/net/packet/doc.go +++ b/net/packet/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package packet contains packet parsing and marshaling utilities. diff --git a/net/packet/geneve.go b/net/packet/geneve.go index 71b365ae89414..bed54f641425f 100644 --- a/net/packet/geneve.go +++ b/net/packet/geneve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/geneve_test.go b/net/packet/geneve_test.go index be9784998adf2..bd673cd0d963a 100644 --- a/net/packet/geneve_test.go +++ b/net/packet/geneve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/header.go b/net/packet/header.go index fa66a8641c6c4..44b99e520d717 100644 --- a/net/packet/header.go +++ b/net/packet/header.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/icmp.go b/net/packet/icmp.go index 89a7aaa32bec4..8f9cd0e2bb4a1 100644 --- a/net/packet/icmp.go +++ b/net/packet/icmp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/icmp4.go b/net/packet/icmp4.go index 06780e0bb48ff..492a0e9dfee98 100644 --- a/net/packet/icmp4.go +++ b/net/packet/icmp4.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/icmp6.go b/net/packet/icmp6.go index f78db1f4a8c3c..a91db53c9e50c 100644 --- a/net/packet/icmp6.go +++ b/net/packet/icmp6.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/icmp6_test.go b/net/packet/icmp6_test.go index f34883ca41e7e..0348824b62296 100644 --- a/net/packet/icmp6_test.go +++ b/net/packet/icmp6_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/ip4.go b/net/packet/ip4.go index 967a8dba7f57b..1964acf1b7900 100644 --- a/net/packet/ip4.go +++ b/net/packet/ip4.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/ip6.go b/net/packet/ip6.go index d26b9a1619b31..eb92f1450f523 100644 --- a/net/packet/ip6.go +++ b/net/packet/ip6.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/packet.go b/net/packet/packet.go index 34b63aadd2c2e..b41e0dcd93301 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/packet_test.go b/net/packet/packet_test.go index 09c2c101d66d9..4dbf88009b20a 100644 --- a/net/packet/packet_test.go +++ b/net/packet/packet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/tsmp.go b/net/packet/tsmp.go index 9881299b7d13e..ad1db311a64c2 100644 --- a/net/packet/tsmp.go +++ b/net/packet/tsmp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TSMP is our ICMP-like "Tailscale Message Protocol" for signaling diff --git a/net/packet/tsmp_test.go b/net/packet/tsmp_test.go index d8f1d38d57180..01bb836d76971 100644 --- a/net/packet/tsmp_test.go +++ b/net/packet/tsmp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/udp4.go b/net/packet/udp4.go index 0d5bca73e8c89..a42222f785292 100644 --- a/net/packet/udp4.go +++ b/net/packet/udp4.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/udp6.go b/net/packet/udp6.go index 10fdcb99e525c..8d7f380884cbb 100644 --- a/net/packet/udp6.go +++ b/net/packet/udp6.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/ping/ping.go b/net/ping/ping.go index 8e16a692a8136..de79da51c5c48 100644 --- a/net/ping/ping.go +++ b/net/ping/ping.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ping allows sending ICMP echo requests to a host in order to diff --git a/net/ping/ping_test.go b/net/ping/ping_test.go index bbedbcad80e44..9fe12de7e9a54 100644 --- a/net/ping/ping_test.go +++ b/net/ping/ping_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ping diff --git a/net/portmapper/disabled_stubs.go b/net/portmapper/disabled_stubs.go index a1324c20be9e1..dea4ef0d3e630 100644 --- a/net/portmapper/disabled_stubs.go +++ b/net/portmapper/disabled_stubs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js diff --git a/net/portmapper/igd_test.go b/net/portmapper/igd_test.go index 77015f5bfb189..9426790639563 100644 --- a/net/portmapper/igd_test.go +++ b/net/portmapper/igd_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/legacy_upnp.go b/net/portmapper/legacy_upnp.go index 2ce92dc65d6b3..ed2c23a04a975 100644 --- a/net/portmapper/legacy_upnp.go +++ b/net/portmapper/legacy_upnp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js diff --git a/net/portmapper/pcp.go b/net/portmapper/pcp.go index d0752734e8752..0332295b8cfa0 100644 --- a/net/portmapper/pcp.go +++ b/net/portmapper/pcp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/pcp_test.go b/net/portmapper/pcp_test.go index 8f8eef3ef8399..ef2621f7dc401 100644 --- a/net/portmapper/pcp_test.go +++ b/net/portmapper/pcp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/pcpresultcode_string.go b/net/portmapper/pcpresultcode_string.go index 45eb70d39fa06..8ffd5beae0604 100644 --- a/net/portmapper/pcpresultcode_string.go +++ b/net/portmapper/pcpresultcode_string.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by "stringer -type=pcpResultCode -trimprefix=pcpCode"; DO NOT EDIT. diff --git a/net/portmapper/pmpresultcode_string.go b/net/portmapper/pmpresultcode_string.go index 18d911d944126..f32626328fdec 100644 --- a/net/portmapper/pmpresultcode_string.go +++ b/net/portmapper/pmpresultcode_string.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by "stringer -type=pmpResultCode -trimprefix=pmpCode"; DO NOT EDIT. diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index 16a981d1d8336..37d7730c51f0d 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portmapper is a UDP port mapping client. It currently allows for mapping over diff --git a/net/portmapper/portmapper_test.go b/net/portmapper/portmapper_test.go index a697a39089635..beb14cb8074eb 100644 --- a/net/portmapper/portmapper_test.go +++ b/net/portmapper/portmapper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/portmappertype/portmappertype.go b/net/portmapper/portmappertype/portmappertype.go index cc8358a4aed12..3b756e0ed04b2 100644 --- a/net/portmapper/portmappertype/portmappertype.go +++ b/net/portmapper/portmappertype/portmappertype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portmappertype defines the net/portmapper interface, which may or may not be diff --git a/net/portmapper/select_test.go b/net/portmapper/select_test.go index cc685bc253d3d..b7370c24139f1 100644 --- a/net/portmapper/select_test.go +++ b/net/portmapper/select_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/upnp.go b/net/portmapper/upnp.go index 34140e9473460..e3971a2ae0f97 100644 --- a/net/portmapper/upnp.go +++ b/net/portmapper/upnp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js @@ -574,7 +574,7 @@ func (c *Client) getUPnPPortMapping( c.mu.Lock() defer c.mu.Unlock() c.mapping = upnp - c.localPort = externalAddrPort.Port() + c.localPort = internal.Port() return upnp.external, true } diff --git a/net/portmapper/upnp_test.go b/net/portmapper/upnp_test.go index a954b2beac094..15b03517708e4 100644 --- a/net/portmapper/upnp_test.go +++ b/net/portmapper/upnp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/porttrack/porttrack.go b/net/porttrack/porttrack.go new file mode 100644 index 0000000000000..f71154f78e631 --- /dev/null +++ b/net/porttrack/porttrack.go @@ -0,0 +1,184 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package porttrack provides race-free ephemeral port assignment for +// subprocess tests. The parent test process creates a [Collector] that +// listens on a TCP port; the child process uses [Listen] which, when +// given a magic address, binds to localhost:0 and reports the actual +// port back to the collector. +// +// The magic address format is: +// +// testport-report-LABEL:PORT +// +// where localhost:PORT is the collector's TCP address and LABEL identifies +// which listener this is (e.g. "main", "plaintext"). +// +// When [Listen] is called with a non-magic address, it falls through to +// [net.Listen] with zero overhead beyond a single [strings.HasPrefix] +// check. +package porttrack + +import ( + "bufio" + "context" + "fmt" + "net" + "strconv" + "strings" + "sync" + + "tailscale.com/util/testenv" +) + +const magicPrefix = "testport-report-" + +// Collector is the parent/test side of the porttrack protocol. It +// listens for port reports from child processes that used [Listen] +// with a magic address obtained from [Collector.Addr]. +type Collector struct { + ln net.Listener + lnPort int + mu sync.Mutex + cond *sync.Cond + ports map[string]int + err error // non-nil if a context passed to Port was cancelled +} + +// NewCollector creates a new Collector. The collector's TCP listener is +// closed when t finishes. +func NewCollector(t testenv.TB) *Collector { + t.Helper() + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("porttrack.NewCollector: %v", err) + } + c := &Collector{ + ln: ln, + lnPort: ln.Addr().(*net.TCPAddr).Port, + ports: make(map[string]int), + } + c.cond = sync.NewCond(&c.mu) + go c.accept(t) + t.Cleanup(func() { ln.Close() }) + return c +} + +// accept runs in a goroutine, accepting connections and parsing port +// reports until the listener is closed. +func (c *Collector) accept(t testenv.TB) { + for { + conn, err := c.ln.Accept() + if err != nil { + return // listener closed + } + go c.handleConn(t, conn) + } +} + +func (c *Collector) handleConn(t testenv.TB, conn net.Conn) { + defer conn.Close() + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + line := scanner.Text() + label, portStr, ok := strings.Cut(line, "\t") + if !ok { + t.Errorf("porttrack: malformed report line: %q", line) + return + } + port, err := strconv.Atoi(portStr) + if err != nil { + t.Errorf("porttrack: bad port in report %q: %v", line, err) + return + } + c.mu.Lock() + c.ports[label] = port + c.cond.Broadcast() + c.mu.Unlock() + } +} + +// Addr returns a magic address string that, when passed to [Listen], +// causes the child to bind to localhost:0 and report its actual port +// back to this collector under the given label. +func (c *Collector) Addr(label string) string { + for _, c := range label { + switch { + case 'a' <= c && c <= 'z', 'A' <= c && c <= 'Z', '0' <= c && c <= '9', c == '-': + default: + panic(fmt.Sprintf("invalid label %q: only letters, digits, and hyphens are allowed", label)) + } + } + return fmt.Sprintf("%s%s:%d", magicPrefix, label, c.lnPort) +} + +// Port blocks until the child process has reported the port for the +// given label, then returns it. If ctx is cancelled before a port is +// reported, Port returns the context's cause as an error. +func (c *Collector) Port(ctx context.Context, label string) (int, error) { + stop := context.AfterFunc(ctx, func() { + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.err = context.Cause(ctx) + } + c.cond.Broadcast() + }) + defer stop() + + c.mu.Lock() + defer c.mu.Unlock() + for { + if p, ok := c.ports[label]; ok { + return p, nil + } + if c.err != nil { + return 0, c.err + } + c.cond.Wait() + } +} + +// Listen is the child/production side of the porttrack protocol. +// +// If address has the magic prefix (as returned by [Collector.Addr]), +// Listen binds to localhost:0 on the given network, then TCP-connects +// to the collector and writes "LABEL\tPORT\n" to report the actual +// port. The collector connection is closed before returning. +// +// If address does not have the magic prefix, Listen is simply +// [net.Listen](network, address). +func Listen(network, address string) (net.Listener, error) { + rest, ok := strings.CutPrefix(address, magicPrefix) + if !ok { + return net.Listen(network, address) + } + + // rest is LABEL:PORT. + label, collectorPort, ok := strings.Cut(rest, ":") + if !ok { + return nil, fmt.Errorf("porttrack: malformed magic address %q: missing :PORT", address) + } + + ln, err := net.Listen(network, "localhost:0") + if err != nil { + return nil, err + } + + port := ln.Addr().(*net.TCPAddr).Port + + collectorAddr := net.JoinHostPort("localhost", collectorPort) + conn, err := net.Dial("tcp", collectorAddr) + if err != nil { + ln.Close() + return nil, fmt.Errorf("porttrack: failed to connect to collector at %s: %v", collectorAddr, err) + } + _, err = fmt.Fprintf(conn, "%s\t%d\n", label, port) + conn.Close() + if err != nil { + ln.Close() + return nil, fmt.Errorf("porttrack: failed to report port to collector: %v", err) + } + + return ln, nil +} diff --git a/net/porttrack/porttrack_test.go b/net/porttrack/porttrack_test.go new file mode 100644 index 0000000000000..06412d87554fc --- /dev/null +++ b/net/porttrack/porttrack_test.go @@ -0,0 +1,95 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package porttrack + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "testing" +) + +func TestCollectorAndListen(t *testing.T) { + c := NewCollector(t) + + labels := []string{"main", "plaintext", "debug"} + ports := make([]int, len(labels)) + + for i, label := range labels { + ln, err := Listen("tcp", c.Addr(label)) + if err != nil { + t.Fatalf("Listen(%q): %v", label, err) + } + defer ln.Close() + p, err := c.Port(t.Context(), label) + if err != nil { + t.Fatalf("Port(%q): %v", label, err) + } + ports[i] = p + } + + // All ports should be distinct non-zero values. + seen := map[int]string{} + for i, label := range labels { + if ports[i] == 0 { + t.Errorf("Port(%q) = 0", label) + } + if prev, ok := seen[ports[i]]; ok { + t.Errorf("Port(%q) = Port(%q) = %d", label, prev, ports[i]) + } + seen[ports[i]] = label + } +} + +func TestListenPassthrough(t *testing.T) { + ln, err := Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("Listen passthrough: %v", err) + } + defer ln.Close() + if ln.Addr().(*net.TCPAddr).Port == 0 { + t.Fatal("expected non-zero port") + } +} + +func TestRoundTrip(t *testing.T) { + c := NewCollector(t) + + ln, err := Listen("tcp", c.Addr("http")) + if err != nil { + t.Fatalf("Listen: %v", err) + } + defer ln.Close() + + // Start a server on the listener. + go http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + })) + + port, err := c.Port(t.Context(), "http") + if err != nil { + t.Fatalf("Port: %v", err) + } + resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port)) + if err != nil { + t.Fatalf("http.Get: %v", err) + } + resp.Body.Close() + if resp.StatusCode != http.StatusNoContent { + t.Fatalf("status = %d, want %d", resp.StatusCode, http.StatusNoContent) + } +} + +func TestPortContextCancelled(t *testing.T) { + c := NewCollector(t) + // Nobody will ever report "never", so Port should block until ctx is done. + ctx, cancel := context.WithCancel(t.Context()) + cancel() + _, err := c.Port(ctx, "never") + if !errors.Is(err, context.Canceled) { + t.Fatalf("Port with cancelled context: got %v, want %v", err, context.Canceled) + } +} diff --git a/net/proxymux/mux.go b/net/proxymux/mux.go index ff5aaff3b975f..d9c57cd76ecf5 100644 --- a/net/proxymux/mux.go +++ b/net/proxymux/mux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package proxymux splits a net.Listener in two, routing SOCKS5 diff --git a/net/proxymux/mux_test.go b/net/proxymux/mux_test.go index 29166f9966bbc..6e84e57d8ef80 100644 --- a/net/proxymux/mux_test.go +++ b/net/proxymux/mux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package proxymux diff --git a/net/routetable/routetable.go b/net/routetable/routetable.go index 2884706f109a1..bfa62af7b3ce3 100644 --- a/net/routetable/routetable.go +++ b/net/routetable/routetable.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package routetable provides functions that operate on the system's route diff --git a/net/routetable/routetable_bsd.go b/net/routetable/routetable_bsd.go index 1de1a2734ce6c..f5306d8942a02 100644 --- a/net/routetable/routetable_bsd.go +++ b/net/routetable/routetable_bsd.go @@ -1,7 +1,7 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build darwin || freebsd +//go:build darwin || freebsd || openbsd package routetable diff --git a/net/routetable/routetable_bsd_test.go b/net/routetable/routetable_bsd_test.go index 29493d59bdc36..df515c5788681 100644 --- a/net/routetable/routetable_bsd_test.go +++ b/net/routetable/routetable_bsd_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || freebsd diff --git a/net/routetable/routetable_freebsd.go b/net/routetable/routetable_bsdconst.go similarity index 83% rename from net/routetable/routetable_freebsd.go rename to net/routetable/routetable_bsdconst.go index 8e57a330246ed..9de9aad73802f 100644 --- a/net/routetable/routetable_freebsd.go +++ b/net/routetable/routetable_bsdconst.go @@ -1,7 +1,7 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build freebsd +//go:build freebsd || openbsd package routetable @@ -21,6 +21,7 @@ var flags = map[int]string{ unix.RTF_BROADCAST: "broadcast", unix.RTF_GATEWAY: "gateway", unix.RTF_HOST: "host", + unix.RTF_LOCAL: "local", unix.RTF_MULTICAST: "multicast", unix.RTF_REJECT: "reject", unix.RTF_STATIC: "static", diff --git a/net/routetable/routetable_darwin.go b/net/routetable/routetable_darwin.go index 7f525ae32807a..5c143f0c1d7eb 100644 --- a/net/routetable/routetable_darwin.go +++ b/net/routetable/routetable_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/net/routetable/routetable_linux.go b/net/routetable/routetable_linux.go index 0b2cb305d7154..479aa8fd8f0af 100644 --- a/net/routetable/routetable_linux.go +++ b/net/routetable/routetable_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/routetable/routetable_linux_test.go b/net/routetable/routetable_linux_test.go index bbf7790e787ca..4d03b7f9d5466 100644 --- a/net/routetable/routetable_linux_test.go +++ b/net/routetable/routetable_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/routetable/routetable_other.go b/net/routetable/routetable_other.go index e547ab0ac769a..25d008ccc276f 100644 --- a/net/routetable/routetable_other.go +++ b/net/routetable/routetable_other.go @@ -1,7 +1,7 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build android || (!linux && !darwin && !freebsd) +//go:build android || (!linux && !darwin && !freebsd && !openbsd) package routetable diff --git a/net/sockopts/sockopts.go b/net/sockopts/sockopts.go index 0c0ee7692cf6a..aa10d977f6468 100644 --- a/net/sockopts/sockopts.go +++ b/net/sockopts/sockopts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sockopts contains logic for applying socket options. diff --git a/net/sockopts/sockopts_default.go b/net/sockopts/sockopts_default.go index 3cc8679b512c1..6b728d34c6a42 100644 --- a/net/sockopts/sockopts_default.go +++ b/net/sockopts/sockopts_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/net/sockopts/sockopts_linux.go b/net/sockopts/sockopts_linux.go index 5d778d380f5c9..216c589225d39 100644 --- a/net/sockopts/sockopts_linux.go +++ b/net/sockopts/sockopts_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/sockopts/sockopts_notwindows.go b/net/sockopts/sockopts_notwindows.go index f1bc7fd442ee1..880860a58036f 100644 --- a/net/sockopts/sockopts_notwindows.go +++ b/net/sockopts/sockopts_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/sockopts/sockopts_unix_test.go b/net/sockopts/sockopts_unix_test.go index ebb4354ac1385..d474326a14df8 100644 --- a/net/sockopts/sockopts_unix_test.go +++ b/net/sockopts/sockopts_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/net/sockopts/sockopts_windows.go b/net/sockopts/sockopts_windows.go index 1e6c3f69d3af5..9533fd2a4ca9f 100644 --- a/net/sockopts/sockopts_windows.go +++ b/net/sockopts/sockopts_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/net/socks5/socks5.go b/net/socks5/socks5.go index 2e277147bc50d..729fc8e882cf1 100644 --- a/net/socks5/socks5.go +++ b/net/socks5/socks5.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package socks5 is a SOCKS5 server implementation. diff --git a/net/socks5/socks5_test.go b/net/socks5/socks5_test.go index bc6fac79fdcf9..9fbc11f8c0dfb 100644 --- a/net/socks5/socks5_test.go +++ b/net/socks5/socks5_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package socks5 diff --git a/net/sockstats/sockstats.go b/net/sockstats/sockstats.go index 715c1ee06e9a9..14a58d19d800d 100644 --- a/net/sockstats/sockstats.go +++ b/net/sockstats/sockstats.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sockstats collects statistics about network sockets used by diff --git a/net/sockstats/sockstats_noop.go b/net/sockstats/sockstats_noop.go index 96723111ade7a..b586a04cbee29 100644 --- a/net/sockstats/sockstats_noop.go +++ b/net/sockstats/sockstats_noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !tailscale_go || !(darwin || ios || android || ts_enable_sockstats) diff --git a/net/sockstats/sockstats_tsgo.go b/net/sockstats/sockstats_tsgo.go index 4e9f4a9666308..46ac75c990d48 100644 --- a/net/sockstats/sockstats_tsgo.go +++ b/net/sockstats/sockstats_tsgo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go && (darwin || ios || android || ts_enable_sockstats) diff --git a/net/sockstats/sockstats_tsgo_darwin.go b/net/sockstats/sockstats_tsgo_darwin.go index 321d32e04e5f0..e79ff9ee3a02c 100644 --- a/net/sockstats/sockstats_tsgo_darwin.go +++ b/net/sockstats/sockstats_tsgo_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go && (darwin || ios) diff --git a/net/sockstats/sockstats_tsgo_test.go b/net/sockstats/sockstats_tsgo_test.go index c467c8a70ff79..b06ffa8946c44 100644 --- a/net/sockstats/sockstats_tsgo_test.go +++ b/net/sockstats/sockstats_tsgo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go && (darwin || ios || android || ts_enable_sockstats) diff --git a/net/speedtest/speedtest.go b/net/speedtest/speedtest.go index a462dbeece42b..8b887a8ef8b0b 100644 --- a/net/speedtest/speedtest.go +++ b/net/speedtest/speedtest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package speedtest contains both server and client code for diff --git a/net/speedtest/speedtest_client.go b/net/speedtest/speedtest_client.go index 299a12a8dfaec..099eb48549975 100644 --- a/net/speedtest/speedtest_client.go +++ b/net/speedtest/speedtest_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package speedtest diff --git a/net/speedtest/speedtest_server.go b/net/speedtest/speedtest_server.go index 72f85fa15b019..6b6f53b7da5a9 100644 --- a/net/speedtest/speedtest_server.go +++ b/net/speedtest/speedtest_server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package speedtest diff --git a/net/speedtest/speedtest_test.go b/net/speedtest/speedtest_test.go index bb8f2676af8c3..1fbd0915b219f 100644 --- a/net/speedtest/speedtest_test.go +++ b/net/speedtest/speedtest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package speedtest diff --git a/net/stun/stun.go b/net/stun/stun.go index eeac23cbbd45d..7d75e79b8e732 100644 --- a/net/stun/stun.go +++ b/net/stun/stun.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package STUN generates STUN request packets and parses response packets. diff --git a/net/stun/stun_fuzzer.go b/net/stun/stun_fuzzer.go index 6f0c9e3b0beae..b7e3198df873d 100644 --- a/net/stun/stun_fuzzer.go +++ b/net/stun/stun_fuzzer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build gofuzz diff --git a/net/stun/stun_test.go b/net/stun/stun_test.go index 05fc4d2ba727f..7f754324e7597 100644 --- a/net/stun/stun_test.go +++ b/net/stun/stun_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package stun_test diff --git a/net/stun/stuntest/stuntest.go b/net/stun/stuntest/stuntest.go index 09684160055fb..0d3988ce800a9 100644 --- a/net/stun/stuntest/stuntest.go +++ b/net/stun/stuntest/stuntest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package stuntest provides a STUN test server. diff --git a/net/stunserver/stunserver.go b/net/stunserver/stunserver.go index 7397675ca8dc3..97df8cb4d79e9 100644 --- a/net/stunserver/stunserver.go +++ b/net/stunserver/stunserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package stunserver implements a STUN server. The package publishes a number of stats diff --git a/net/stunserver/stunserver_test.go b/net/stunserver/stunserver_test.go index 24a7bb570b6bd..c96aea4d15973 100644 --- a/net/stunserver/stunserver_test.go +++ b/net/stunserver/stunserver_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package stunserver diff --git a/net/tcpinfo/tcpinfo.go b/net/tcpinfo/tcpinfo.go index a757add9f8f46..3e2d76a9529fa 100644 --- a/net/tcpinfo/tcpinfo.go +++ b/net/tcpinfo/tcpinfo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tcpinfo provides platform-agnostic accessors to information about a diff --git a/net/tcpinfo/tcpinfo_darwin.go b/net/tcpinfo/tcpinfo_darwin.go index 53fa22fbf5bed..3e53cd4ed0c8d 100644 --- a/net/tcpinfo/tcpinfo_darwin.go +++ b/net/tcpinfo/tcpinfo_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tcpinfo diff --git a/net/tcpinfo/tcpinfo_linux.go b/net/tcpinfo/tcpinfo_linux.go index 885d462c95e35..1ff0c1bc4f539 100644 --- a/net/tcpinfo/tcpinfo_linux.go +++ b/net/tcpinfo/tcpinfo_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tcpinfo diff --git a/net/tcpinfo/tcpinfo_other.go b/net/tcpinfo/tcpinfo_other.go index be45523aeb00d..c7d0f9177af2f 100644 --- a/net/tcpinfo/tcpinfo_other.go +++ b/net/tcpinfo/tcpinfo_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !darwin diff --git a/net/tcpinfo/tcpinfo_test.go b/net/tcpinfo/tcpinfo_test.go index bb3d224ec1beb..6baac934a00f0 100644 --- a/net/tcpinfo/tcpinfo_test.go +++ b/net/tcpinfo/tcpinfo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tcpinfo diff --git a/net/tlsdial/blockblame/blockblame.go b/net/tlsdial/blockblame/blockblame.go index 5b48dc009b980..f2d7db27c1a5e 100644 --- a/net/tlsdial/blockblame/blockblame.go +++ b/net/tlsdial/blockblame/blockblame.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package blockblame blames specific firewall manufacturers for blocking Tailscale, diff --git a/net/tlsdial/blockblame/blockblame_test.go b/net/tlsdial/blockblame/blockblame_test.go index 6d3592c60a3de..3d08bf811601c 100644 --- a/net/tlsdial/blockblame/blockblame_test.go +++ b/net/tlsdial/blockblame/blockblame_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package blockblame diff --git a/net/tlsdial/deps_test.go b/net/tlsdial/deps_test.go index 7a93899c2f126..3600af537cd85 100644 --- a/net/tlsdial/deps_test.go +++ b/net/tlsdial/deps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build for_go_mod_tidy_only diff --git a/net/tlsdial/tlsdial.go b/net/tlsdial/tlsdial.go index ee4771d8db613..ffc8c90a80f96 100644 --- a/net/tlsdial/tlsdial.go +++ b/net/tlsdial/tlsdial.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tlsdial generates tls.Config values and does x509 validation of diff --git a/net/tlsdial/tlsdial_test.go b/net/tlsdial/tlsdial_test.go index a288d765306e1..9ef0f76884c53 100644 --- a/net/tlsdial/tlsdial_test.go +++ b/net/tlsdial/tlsdial_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tlsdial diff --git a/net/tsaddr/tsaddr.go b/net/tsaddr/tsaddr.go index 5a8376c4b088b..7f01e54911f3c 100644 --- a/net/tsaddr/tsaddr.go +++ b/net/tsaddr/tsaddr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsaddr handles Tailscale-specific IPs and ranges. diff --git a/net/tsaddr/tsaddr_test.go b/net/tsaddr/tsaddr_test.go index 9ac1ce3036299..ac5a07fff94f5 100644 --- a/net/tsaddr/tsaddr_test.go +++ b/net/tsaddr/tsaddr_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsaddr diff --git a/net/tsdial/dnsmap.go b/net/tsdial/dnsmap.go index 37fedd14c899d..d7204463f66ed 100644 --- a/net/tsdial/dnsmap.go +++ b/net/tsdial/dnsmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsdial diff --git a/net/tsdial/dnsmap_test.go b/net/tsdial/dnsmap_test.go index 41a957f186f4a..b2a50fa0c4549 100644 --- a/net/tsdial/dnsmap_test.go +++ b/net/tsdial/dnsmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsdial diff --git a/net/tsdial/dohclient.go b/net/tsdial/dohclient.go index d830398cdfb9c..59b0da04d25f4 100644 --- a/net/tsdial/dohclient.go +++ b/net/tsdial/dohclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsdial diff --git a/net/tsdial/dohclient_test.go b/net/tsdial/dohclient_test.go index 23255769f4847..63e5dbd997826 100644 --- a/net/tsdial/dohclient_test.go +++ b/net/tsdial/dohclient_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsdial diff --git a/net/tsdial/peerapi_macios_ext.go b/net/tsdial/peerapi_macios_ext.go index 3ebead3db439f..fa40feef04524 100644 --- a/net/tsdial/peerapi_macios_ext.go +++ b/net/tsdial/peerapi_macios_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file's built on iOS and on two of three macOS build variants: diff --git a/net/tsdial/tsdial.go b/net/tsdial/tsdial.go index df2d80a619752..ebbafa52b01e9 100644 --- a/net/tsdial/tsdial.go +++ b/net/tsdial/tsdial.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsdial provides a Dialer type that can dial out of tailscaled. diff --git a/net/tshttpproxy/mksyscall.go b/net/tshttpproxy/mksyscall.go index f8fdae89b55f0..37824c84653de 100644 --- a/net/tshttpproxy/mksyscall.go +++ b/net/tshttpproxy/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tshttpproxy diff --git a/net/tshttpproxy/tshttpproxy.go b/net/tshttpproxy/tshttpproxy.go index 0456009ed9a81..1ea444c8f5e99 100644 --- a/net/tshttpproxy/tshttpproxy.go +++ b/net/tshttpproxy/tshttpproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tshttpproxy contains Tailscale additions to httpproxy not available diff --git a/net/tshttpproxy/tshttpproxy_linux.go b/net/tshttpproxy/tshttpproxy_linux.go index 7e086e4929bc7..30096e214a982 100644 --- a/net/tshttpproxy/tshttpproxy_linux.go +++ b/net/tshttpproxy/tshttpproxy_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/tshttpproxy/tshttpproxy_synology.go b/net/tshttpproxy/tshttpproxy_synology.go index e28844f7dbf67..a632753f7bc1b 100644 --- a/net/tshttpproxy/tshttpproxy_synology.go +++ b/net/tshttpproxy/tshttpproxy_synology.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/tshttpproxy/tshttpproxy_synology_test.go b/net/tshttpproxy/tshttpproxy_synology_test.go index b6e8b948c3ae9..a57ac1558d4f4 100644 --- a/net/tshttpproxy/tshttpproxy_synology_test.go +++ b/net/tshttpproxy/tshttpproxy_synology_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/tshttpproxy/tshttpproxy_test.go b/net/tshttpproxy/tshttpproxy_test.go index 97f8c1f8b049a..da847429d4bd4 100644 --- a/net/tshttpproxy/tshttpproxy_test.go +++ b/net/tshttpproxy/tshttpproxy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tshttpproxy diff --git a/net/tshttpproxy/tshttpproxy_windows.go b/net/tshttpproxy/tshttpproxy_windows.go index 7163c786307ac..1a80be3ff370f 100644 --- a/net/tshttpproxy/tshttpproxy_windows.go +++ b/net/tshttpproxy/tshttpproxy_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tshttpproxy diff --git a/net/tstun/fake.go b/net/tstun/fake.go index 3d86bb3df4ca9..f7925116e80bd 100644 --- a/net/tstun/fake.go +++ b/net/tstun/fake.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/ifstatus_noop.go b/net/tstun/ifstatus_noop.go index 8cf569f982010..420326c2fda38 100644 --- a/net/tstun/ifstatus_noop.go +++ b/net/tstun/ifstatus_noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/tstun/ifstatus_windows.go b/net/tstun/ifstatus_windows.go index fd9fc2112524c..64c898fd3aef2 100644 --- a/net/tstun/ifstatus_windows.go +++ b/net/tstun/ifstatus_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/mtu.go b/net/tstun/mtu.go index 004529c205f9e..6eceb6833b964 100644 --- a/net/tstun/mtu.go +++ b/net/tstun/mtu.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/mtu_test.go b/net/tstun/mtu_test.go index ec31e45ce73f5..6129e0c140a85 100644 --- a/net/tstun/mtu_test.go +++ b/net/tstun/mtu_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/netstack_disabled.go b/net/tstun/netstack_disabled.go index c1266b30559d4..6425668a36c87 100644 --- a/net/tstun/netstack_disabled.go +++ b/net/tstun/netstack_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_netstack diff --git a/net/tstun/netstack_enabled.go b/net/tstun/netstack_enabled.go index 8fc1a2e20e35a..440013c7e9510 100644 --- a/net/tstun/netstack_enabled.go +++ b/net/tstun/netstack_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netstack diff --git a/net/tstun/tstun_stub.go b/net/tstun/tstun_stub.go index d21eda6b07a57..27d530bc8b95c 100644 --- a/net/tstun/tstun_stub.go +++ b/net/tstun/tstun_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build aix || solaris || illumos diff --git a/net/tstun/tun.go b/net/tstun/tun.go index 19b0a53f5be6c..42b0d239c39d4 100644 --- a/net/tstun/tun.go +++ b/net/tstun/tun.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !wasm && !tamago && !aix && !solaris && !illumos diff --git a/net/tstun/tun_linux.go b/net/tstun/tun_linux.go index 05cf58c17df8a..fb4a8a415dac7 100644 --- a/net/tstun/tun_linux.go +++ b/net/tstun/tun_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun @@ -86,14 +86,32 @@ func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf, createErr error) logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", findOut) } case distro.OpenWrt: - out, err := exec.Command("opkg", "list-installed").CombinedOutput() - if err != nil { - logf("error querying OpenWrt installed packages: %s", out) - return - } - for _, pkg := range []string{"kmod-tun", "ca-bundle"} { - if !bytes.Contains(out, []byte(pkg+" - ")) { - logf("Missing required package %s; run: opkg install %s", pkg, pkg) + // OpenWRT switched to using apk as a package manager as of OpenWrt 25.12.0. + // Find out what is used on this system and use that, Maybe we can get rid + // of opkg in the future but for now keep checking. + + if path, err := exec.LookPath("apk"); err == nil && path != "" { + // Test with apk + out, err := exec.Command("apk", "info").CombinedOutput() + if err != nil { + logf("error querying OpenWrt installed packages with apk: %s", out) + return + } + for _, pkg := range []string{"kmod-tun", "ca-bundle"} { + if !bytes.Contains(out, []byte(pkg)) { + logf("Missing required package %s; run: apk add %s", pkg, pkg) + } + } + } else { // Check for package with opkg (legacy) + out, err := exec.Command("opkg", "list-installed").CombinedOutput() + if err != nil { + logf("error querying OpenWrt installed packages with opkg: %s", out) + return + } + for _, pkg := range []string{"kmod-tun", "ca-bundle"} { + if !bytes.Contains(out, []byte(pkg+" - ")) { + logf("Missing required package %s; run: opkg install %s", pkg, pkg) + } } } } diff --git a/net/tstun/tun_macos.go b/net/tstun/tun_macos.go index 3506f05b1e4c9..fb8eb9450fb7e 100644 --- a/net/tstun/tun_macos.go +++ b/net/tstun/tun_macos.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/net/tstun/tun_notwindows.go b/net/tstun/tun_notwindows.go index 087fcd4eec784..73f80fea12ce8 100644 --- a/net/tstun/tun_notwindows.go +++ b/net/tstun/tun_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/tstun/tun_windows.go b/net/tstun/tun_windows.go index 2b1d3054e5ecb..96721021b2022 100644 --- a/net/tstun/tun_windows.go +++ b/net/tstun/tun_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index fe1bc31b812b4..2f5d8c1d13254 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun @@ -23,6 +23,7 @@ import ( "github.com/tailscale/wireguard-go/tun" "go4.org/mem" "tailscale.com/disco" + "tailscale.com/envknob" "tailscale.com/feature/buildfeatures" "tailscale.com/net/packet" "tailscale.com/net/packet/checksum" @@ -171,6 +172,9 @@ type Wrapper struct { // PreFilterPacketInboundFromWireGuard is the inbound filter function that runs before the main filter // and therefore sees the packets that may be later dropped by it. PreFilterPacketInboundFromWireGuard FilterFunc + // PostFilterPacketInboundFromWireGuardAppConnector runs after the filter, but before PostFilterPacketInboundFromWireGuard. + // Non-app connector traffic is passed along. Invalid app connector traffic is dropped. + PostFilterPacketInboundFromWireGuardAppConnector FilterFunc // PostFilterPacketInboundFromWireGuard is the inbound filter function that runs after the main filter. PostFilterPacketInboundFromWireGuard GROFilterFunc // PreFilterPacketOutboundToWireGuardNetstackIntercept is a filter function that runs before the main filter @@ -183,6 +187,10 @@ type Wrapper struct { // packets which it handles internally. If both this and PreFilterFromTunToNetstack // filter functions are non-nil, this filter runs second. PreFilterPacketOutboundToWireGuardEngineIntercept FilterFunc + // PreFilterPacketOutboundToWireGuardAppConnectorIntercept runs after PreFilterPacketOutboundToWireGuardEngineIntercept + // for app connector specific traffic. Non-app connector traffic is passed along. Invalid app connector traffic is + // dropped. + PreFilterPacketOutboundToWireGuardAppConnectorIntercept FilterFunc // PostFilterPacketOutboundToWireGuard is the outbound filter function that runs after the main filter. PostFilterPacketOutboundToWireGuard FilterFunc @@ -872,6 +880,12 @@ func (t *Wrapper) filterPacketOutboundToWireGuard(p *packet.Parsed, pc *peerConf return res, gro } } + if t.PreFilterPacketOutboundToWireGuardAppConnectorIntercept != nil { + if res := t.PreFilterPacketOutboundToWireGuardAppConnectorIntercept(p, t); res.IsDrop() { + // Handled by userspaceEngine's configured hook for Connectors 2025 app connectors. + return res, gro + } + } // If the outbound packet is to a jailed peer, use our jailed peer // packet filter. @@ -1144,10 +1158,12 @@ func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook pa t.injectOutboundPong(p, pingReq) return filter.DropSilently, gro } else if discoKeyAdvert, ok := p.AsTSMPDiscoAdvertisement(); ok { - t.discoKeyAdvertisementPub.Publish(DiscoKeyAdvertisement{ - Src: discoKeyAdvert.Src, - Key: discoKeyAdvert.Key, - }) + if buildfeatures.HasCacheNetMap && envknob.Bool("TS_USE_CACHED_NETMAP") { + t.discoKeyAdvertisementPub.Publish(DiscoKeyAdvertisement{ + Src: discoKeyAdvert.Src, + Key: discoKeyAdvert.Key, + }) + } return filter.DropSilently, gro } else if data, ok := p.AsTSMPPong(); ok { if f := t.OnTSMPPongReceived; f != nil { @@ -1234,6 +1250,13 @@ func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook pa return filter.Drop, gro } + if t.PostFilterPacketInboundFromWireGuardAppConnector != nil { + if res := t.PostFilterPacketInboundFromWireGuardAppConnector(p, t); res.IsDrop() { + // Handled by userspaceEngine's configured hook for Connectors 2025 app connectors. + return res, gro + } + } + if t.PostFilterPacketInboundFromWireGuard != nil { var res filter.Response res, gro = t.PostFilterPacketInboundFromWireGuard(p, t, gro) diff --git a/net/tstun/wrap_linux.go b/net/tstun/wrap_linux.go index 7498f107b5fda..a4e76de5a9d20 100644 --- a/net/tstun/wrap_linux.go +++ b/net/tstun/wrap_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_gro diff --git a/net/tstun/wrap_noop.go b/net/tstun/wrap_noop.go index 8ad04bafe94c1..8f5b62d0cbcfe 100644 --- a/net/tstun/wrap_noop.go +++ b/net/tstun/wrap_noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || ts_omit_gro diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index 3bc2ff447422d..1744fc30266a9 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun @@ -41,6 +41,7 @@ import ( "tailscale.com/util/must" "tailscale.com/util/usermetric" "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/netstack/gro" "tailscale.com/wgengine/wgcfg" ) @@ -991,3 +992,67 @@ func TestTSMPDisco(t *testing.T) { } }) } + +func TestInterceptOrdering(t *testing.T) { + bus := eventbustest.NewBus(t) + chtun, tun := newChannelTUN(t.Logf, bus, true) + defer tun.Close() + + var seq uint8 + orderedFilterFn := func(expected uint8) FilterFunc { + return func(_ *packet.Parsed, _ *Wrapper) filter.Response { + seq++ + if expected != seq { + t.Errorf("got sequence %d; want %d", seq, expected) + } + return filter.Accept + } + } + + ordereredGROFilterFn := func(expected uint8) GROFilterFunc { + return func(_ *packet.Parsed, _ *Wrapper, _ *gro.GRO) (filter.Response, *gro.GRO) { + seq++ + if expected != seq { + t.Errorf("got sequence %d; want %d", seq, expected) + } + return filter.Accept, nil + } + } + + // As the number of inbound intercepts change, + // this value should change. + numInboundIntercepts := uint8(3) + + tun.PreFilterPacketInboundFromWireGuard = orderedFilterFn(1) + tun.PostFilterPacketInboundFromWireGuardAppConnector = orderedFilterFn(2) + tun.PostFilterPacketInboundFromWireGuard = ordereredGROFilterFn(3) + + // Write the packet. + go func() { <-chtun.Inbound }() // Simulate tun device receiving. + packet := [][]byte{udp4("5.6.7.8", "1.2.3.4", 89, 89)} + tun.Write(packet, 0) + + if seq != numInboundIntercepts { + t.Errorf("got number of intercepts run in Write(): %d; want: %d", seq, numInboundIntercepts) + } + + // As the number of inbound intercepts change, + // this value should change. + numOutboundIntercepts := uint8(4) + + seq = 0 + tun.PreFilterPacketOutboundToWireGuardNetstackIntercept = ordereredGROFilterFn(1) + tun.PreFilterPacketOutboundToWireGuardEngineIntercept = orderedFilterFn(2) + tun.PreFilterPacketOutboundToWireGuardAppConnectorIntercept = orderedFilterFn(3) + tun.PostFilterPacketOutboundToWireGuard = orderedFilterFn(4) + + // Read the packet. + var buf [MaxPacketSize]byte + sizes := make([]int, 1) + chtun.Outbound <- udp4("1.2.3.4", "5.6.7.8", 98, 98) // Simulate tun device sending. + tun.Read([][]byte{buf[:]}, sizes, 0) + + if seq != numOutboundIntercepts { + t.Errorf("got number of intercepts run in Read(): %d; want: %d", seq, numOutboundIntercepts) + } +} diff --git a/net/udprelay/endpoint/endpoint.go b/net/udprelay/endpoint/endpoint.go index 0d2a14e965a4a..7b8368b615e52 100644 --- a/net/udprelay/endpoint/endpoint.go +++ b/net/udprelay/endpoint/endpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package endpoint contains types relating to UDP relay server endpoints. It diff --git a/net/udprelay/endpoint/endpoint_test.go b/net/udprelay/endpoint/endpoint_test.go index f12a6e2f62240..eaef289de6725 100644 --- a/net/udprelay/endpoint/endpoint_test.go +++ b/net/udprelay/endpoint/endpoint_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package endpoint diff --git a/net/udprelay/metrics.go b/net/udprelay/metrics.go index b7c0710c2afc1..6e22acd03ce70 100644 --- a/net/udprelay/metrics.go +++ b/net/udprelay/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package udprelay @@ -22,6 +22,17 @@ var ( cMetricForwarded46Bytes = clientmetric.NewAggregateCounter("udprelay_forwarded_bytes_udp4_udp6") cMetricForwarded64Bytes = clientmetric.NewAggregateCounter("udprelay_forwarded_bytes_udp6_udp4") cMetricForwarded66Bytes = clientmetric.NewAggregateCounter("udprelay_forwarded_bytes_udp6_udp6") + + // cMetricEndpoints is initialized here with no other writes, making it safe for concurrent reads. + // + // [clientmetric.Gauge] does not let us embed existing counters, so + // [metrics.updateEndpoint] records data into client and user gauges independently. + // + // Transitions to and from [endpointClosed] are not recorded. + cMetricEndpoints = map[endpointState]*clientmetric.Metric{ + endpointConnecting: clientmetric.NewGauge("udprelay_endpoints_connecting"), + endpointOpen: clientmetric.NewGauge("udprelay_endpoints_open"), + } ) type transport string @@ -36,6 +47,10 @@ type forwardedLabel struct { transportOut transport `prom:"transport_out"` } +type endpointLabel struct { + state endpointState `prom:"state"` +} + type metrics struct { forwarded44Packets expvar.Int forwarded46Packets expvar.Int @@ -46,6 +61,11 @@ type metrics struct { forwarded46Bytes expvar.Int forwarded64Bytes expvar.Int forwarded66Bytes expvar.Int + + // endpoints are set in [registerMetrics] and safe for concurrent reads. + // + // Transitions to and from [endpointClosed] are not recorded + endpoints map[endpointState]*expvar.Int } // registerMetrics publishes user and client metric counters for peer relay server. @@ -65,6 +85,12 @@ func registerMetrics(reg *usermetric.Registry) *metrics { "counter", "Number of bytes forwarded via Peer Relay", ) + uMetricEndpoints = usermetric.NewMultiLabelMapWithRegistry[endpointLabel]( + reg, + "tailscaled_peer_relay_endpoints", + "gauge", + "Number of allocated Peer Relay endpoints", + ) forwarded44 = forwardedLabel{transportIn: transportUDP4, transportOut: transportUDP4} forwarded46 = forwardedLabel{transportIn: transportUDP4, transportOut: transportUDP6} forwarded64 = forwardedLabel{transportIn: transportUDP6, transportOut: transportUDP4} @@ -83,6 +109,13 @@ func registerMetrics(reg *usermetric.Registry) *metrics { uMetricForwardedBytes.Set(forwarded64, &m.forwarded64Bytes) uMetricForwardedBytes.Set(forwarded66, &m.forwarded66Bytes) + m.endpoints = map[endpointState]*expvar.Int{ + endpointConnecting: {}, + endpointOpen: {}, + } + uMetricEndpoints.Set(endpointLabel{endpointOpen}, m.endpoints[endpointOpen]) + uMetricEndpoints.Set(endpointLabel{endpointConnecting}, m.endpoints[endpointConnecting]) + // Publish client metrics. cMetricForwarded44Packets.Register(&m.forwarded44Packets) cMetricForwarded46Packets.Register(&m.forwarded46Packets) @@ -96,6 +129,26 @@ func registerMetrics(reg *usermetric.Registry) *metrics { return m } +type endpointUpdater interface { + updateEndpoint(before, after endpointState) +} + +// updateEndpoint updates the endpoints gauge according to states left and entered. +// It records client-metric gauges independently, see [cMetricEndpoints] doc. +func (m *metrics) updateEndpoint(before, after endpointState) { + if before == after { + return + } + if uMetricEndpointsBefore, ok := m.endpoints[before]; ok && before != endpointClosed { + uMetricEndpointsBefore.Add(-1) + cMetricEndpoints[before].Add(-1) + } + if uMetricEndpointsAfter, ok := m.endpoints[after]; ok && after != endpointClosed { + uMetricEndpointsAfter.Add(1) + cMetricEndpoints[after].Add(1) + } +} + // countForwarded records user and client metrics according to the // inbound and outbound address families. func (m *metrics) countForwarded(in4, out4 bool, bytes, packets int64) { @@ -114,8 +167,7 @@ func (m *metrics) countForwarded(in4, out4 bool, bytes, packets int64) { } } -// deregisterMetrics unregisters the underlying expvar counters -// from clientmetrics. +// deregisterMetrics clears clientmetrics counters and resets gauges to zero. func deregisterMetrics() { cMetricForwarded44Packets.UnregisterAll() cMetricForwarded46Packets.UnregisterAll() @@ -125,4 +177,7 @@ func deregisterMetrics() { cMetricForwarded46Bytes.UnregisterAll() cMetricForwarded64Bytes.UnregisterAll() cMetricForwarded66Bytes.UnregisterAll() + for _, v := range cMetricEndpoints { + v.Set(0) + } } diff --git a/net/udprelay/metrics_test.go b/net/udprelay/metrics_test.go index 5c6a751134e8b..da90550b88920 100644 --- a/net/udprelay/metrics_test.go +++ b/net/udprelay/metrics_test.go @@ -1,9 +1,10 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package udprelay import ( + "fmt" "slices" "testing" @@ -11,7 +12,7 @@ import ( "tailscale.com/util/usermetric" ) -func TestMetrics(t *testing.T) { +func TestMetricsLifecycle(t *testing.T) { c := qt.New(t) deregisterMetrics() r := &usermetric.Registry{} @@ -22,6 +23,7 @@ func TestMetrics(t *testing.T) { want := []string{ "tailscaled_peer_relay_forwarded_packets_total", "tailscaled_peer_relay_forwarded_bytes_total", + "tailscaled_peer_relay_endpoints", } slices.Sort(have) slices.Sort(want) @@ -51,4 +53,57 @@ func TestMetrics(t *testing.T) { c.Assert(m.forwarded66Packets.Value(), qt.Equals, int64(4)) c.Assert(cMetricForwarded66Bytes.Value(), qt.Equals, int64(4)) c.Assert(cMetricForwarded66Packets.Value(), qt.Equals, int64(4)) + + // Validate client metrics deregistration. + m.updateEndpoint(endpointClosed, endpointOpen) + deregisterMetrics() + c.Check(cMetricForwarded44Bytes.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded44Packets.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded46Bytes.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded46Packets.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded64Bytes.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded64Packets.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded66Bytes.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded66Packets.Value(), qt.Equals, int64(0)) + for k := range cMetricEndpoints { + c.Check(cMetricEndpoints[k].Value(), qt.Equals, int64(0)) + } +} + +func TestMetricsEndpointTransitions(t *testing.T) { + c := qt.New(t) + var states = []endpointState{ + endpointClosed, + endpointConnecting, + endpointOpen, + } + for _, a := range states { + for _, b := range states { + t.Run(fmt.Sprintf("%s-%s", a, b), func(t *testing.T) { + deregisterMetrics() + r := &usermetric.Registry{} + m := registerMetrics(r) + m.updateEndpoint(a, b) + var wantA, wantB int64 + switch { + case a == b: + wantA, wantB = 0, 0 + case a == endpointClosed: + wantA, wantB = 0, 1 + case b == endpointClosed: + wantA, wantB = -1, 0 + default: + wantA, wantB = -1, 1 + } + if a != endpointClosed { + c.Check(m.endpoints[a].Value(), qt.Equals, wantA) + c.Check(cMetricEndpoints[a].Value(), qt.Equals, wantA) + } + if b != endpointClosed { + c.Check(m.endpoints[b].Value(), qt.Equals, wantB) + c.Check(cMetricEndpoints[b].Value(), qt.Equals, wantB) + } + }) + } + } } diff --git a/net/udprelay/server.go b/net/udprelay/server.go index 5918863a5323f..03d8e3dc3050d 100644 --- a/net/udprelay/server.go +++ b/net/udprelay/server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package udprelay contains constructs for relaying Disco and WireGuard packets @@ -43,6 +43,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/types/nettype" "tailscale.com/types/views" + "tailscale.com/util/cloudinfo" "tailscale.com/util/eventbus" "tailscale.com/util/set" "tailscale.com/util/usermetric" @@ -81,6 +82,7 @@ type Server struct { netChecker *netcheck.Client metrics *metrics netMon *netmon.Monitor + cloudInfo *cloudinfo.CloudInfo // used to query cloud metadata services mu sync.Mutex // guards the following fields macSecrets views.Slice[[blake2s.Size]byte] // [0] is most recent, max 2 elements @@ -120,6 +122,7 @@ type serverEndpoint struct { allocatedAt mono.Time mu sync.Mutex // guards the following fields + closed bool // signals that no new data should be accepted inProgressGeneration [2]uint32 // or zero if a handshake has never started, or has just completed boundAddrPorts [2]netip.AddrPort // or zero value if a handshake has never completed for that relay leg lastSeen [2]mono.Time @@ -149,9 +152,15 @@ func blakeMACFromBindMsg(blakeKey [blake2s.Size]byte, src netip.AddrPort, msg di return out, nil } -func (e *serverEndpoint) handleDiscoControlMsg(from netip.AddrPort, senderIndex int, discoMsg disco.Message, serverDisco key.DiscoPublic, macSecrets views.Slice[[blake2s.Size]byte], now mono.Time) (write []byte, to netip.AddrPort) { +func (e *serverEndpoint) handleDiscoControlMsg(from netip.AddrPort, senderIndex int, discoMsg disco.Message, serverDisco key.DiscoPublic, macSecrets views.Slice[[blake2s.Size]byte], now mono.Time, m endpointUpdater) (write []byte, to netip.AddrPort) { e.mu.Lock() defer e.mu.Unlock() + lastState := e.stateLocked() + + if lastState == endpointClosed { + // endpoint was closed in [Server.endpointGC] + return nil, netip.AddrPort{} + } if senderIndex != 0 && senderIndex != 1 { return nil, netip.AddrPort{} @@ -228,6 +237,7 @@ func (e *serverEndpoint) handleDiscoControlMsg(from netip.AddrPort, senderIndex if bytes.Equal(mac[:], discoMsg.Challenge[:]) { // Handshake complete. Update the binding for this sender. e.boundAddrPorts[senderIndex] = from + m.updateEndpoint(lastState, e.stateLocked()) e.lastSeen[senderIndex] = now // record last seen as bound time e.inProgressGeneration[senderIndex] = 0 // reset to zero, which indicates there is no in-progress handshake return nil, netip.AddrPort{} @@ -241,7 +251,7 @@ func (e *serverEndpoint) handleDiscoControlMsg(from netip.AddrPort, senderIndex } } -func (e *serverEndpoint) handleSealedDiscoControlMsg(from netip.AddrPort, b []byte, serverDisco key.DiscoPublic, macSecrets views.Slice[[blake2s.Size]byte], now mono.Time) (write []byte, to netip.AddrPort) { +func (e *serverEndpoint) handleSealedDiscoControlMsg(from netip.AddrPort, b []byte, serverDisco key.DiscoPublic, macSecrets views.Slice[[blake2s.Size]byte], now mono.Time, m endpointUpdater) (write []byte, to netip.AddrPort) { senderRaw, isDiscoMsg := disco.Source(b) if !isDiscoMsg { // Not a Disco message @@ -272,7 +282,7 @@ func (e *serverEndpoint) handleSealedDiscoControlMsg(from netip.AddrPort, b []by return nil, netip.AddrPort{} } - return e.handleDiscoControlMsg(from, senderIndex, discoMsg, serverDisco, macSecrets, now) + return e.handleDiscoControlMsg(from, senderIndex, discoMsg, serverDisco, macSecrets, now, m) } func (e *serverEndpoint) handleDataPacket(from netip.AddrPort, b []byte, now mono.Time) (write []byte, to netip.AddrPort) { @@ -282,6 +292,10 @@ func (e *serverEndpoint) handleDataPacket(from netip.AddrPort, b []byte, now mon // not a control packet, but serverEndpoint isn't bound return nil, netip.AddrPort{} } + if e.stateLocked() == endpointClosed { + // endpoint was closed in [Server.endpointGC] + return nil, netip.AddrPort{} + } switch { case from == e.boundAddrPorts[0]: e.lastSeen[0] = now @@ -299,9 +313,21 @@ func (e *serverEndpoint) handleDataPacket(from netip.AddrPort, b []byte, now mon } } -func (e *serverEndpoint) isExpired(now mono.Time, bindLifetime, steadyStateLifetime time.Duration) bool { +// maybeExpire checks if the endpoint has expired according to the provided timeouts and sets its closed state accordingly. +// True is returned if the endpoint was expired and closed. +func (e *serverEndpoint) maybeExpire(now mono.Time, bindLifetime, steadyStateLifetime time.Duration, m endpointUpdater) bool { e.mu.Lock() defer e.mu.Unlock() + before := e.stateLocked() + if e.isExpiredLocked(now, bindLifetime, steadyStateLifetime) { + e.closed = true + m.updateEndpoint(before, e.stateLocked()) + return true + } + return false +} + +func (e *serverEndpoint) isExpiredLocked(now mono.Time, bindLifetime, steadyStateLifetime time.Duration) bool { if !e.isBoundLocked() { if now.Sub(e.allocatedAt) > bindLifetime { return true @@ -321,6 +347,31 @@ func (e *serverEndpoint) isBoundLocked() bool { e.boundAddrPorts[1].IsValid() } +// stateLocked returns current endpointState according to the +// peers handshake status. +func (e *serverEndpoint) stateLocked() endpointState { + switch { + case e == nil, e.closed: + return endpointClosed + case e.boundAddrPorts[0].IsValid() && e.boundAddrPorts[1].IsValid(): + return endpointOpen + default: + return endpointConnecting + } +} + +// endpointState canonicalizes endpoint state names, +// see [serverEndpoint.stateLocked]. +// +// Usermetrics can't handle Stringer, must be a string enum. +type endpointState string + +const ( + endpointClosed endpointState = "closed" // unallocated, not tracked in metrics + endpointConnecting endpointState = "connecting" // at least one peer has not completed handshake + endpointOpen endpointState = "open" // ready to forward +) + // NewServer constructs a [Server] listening on port. If port is zero, then // port selection is left up to the host networking stack. If // onlyStaticAddrPorts is true, then dynamic addr:port discovery will be @@ -336,6 +387,7 @@ func NewServer(logf logger.Logf, port uint16, onlyStaticAddrPorts bool, metrics onlyStaticAddrPorts: onlyStaticAddrPorts, serverEndpointByDisco: make(map[key.SortedPairOfDiscoPublic]*serverEndpoint), nextVNI: minVNI, + cloudInfo: cloudinfo.New(logf), } s.discoPublic = s.disco.Public() s.metrics = registerMetrics(metrics) @@ -402,11 +454,13 @@ func (s *Server) startPacketReaders() { func (s *Server) addrDiscoveryLoop() { defer s.wg.Done() - timer := time.NewTimer(0) // fire immediately defer timer.Stop() getAddrPorts := func() ([]netip.AddrPort, error) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + var addrPorts set.Set[netip.AddrPort] addrPorts.Make() @@ -425,6 +479,21 @@ func (s *Server) addrDiscoveryLoop() { } } + // Get cloud metadata service addresses. + // TODO(illotum) Same is done within magicsock, consider caching within cloudInfo + cloudIPs, err := s.cloudInfo.GetPublicIPs(ctx) + if err == nil { // Not handling the err, GetPublicIPs already printed to log. + for _, ip := range cloudIPs { + if ip.IsValid() { + if ip.Is4() { + addrPorts.Add(netip.AddrPortFrom(ip, s.uc4Port)) + } else { + addrPorts.Add(netip.AddrPortFrom(ip, s.uc6Port)) + } + } + } + } + dm := s.getDERPMap() if dm == nil { // We don't have a DERPMap which is required to dynamically @@ -434,9 +503,7 @@ func (s *Server) addrDiscoveryLoop() { } // get addrPorts as visible from DERP - netCheckerCtx, netCheckerCancel := context.WithTimeout(context.Background(), netcheck.ReportTimeout) - defer netCheckerCancel() - rep, err := s.netChecker.GetReport(netCheckerCtx, dm, &netcheck.GetReportOpts{ + rep, err := s.netChecker.GetReport(ctx, dm, &netcheck.GetReportOpts{ OnlySTUN: true, }) if err != nil { @@ -474,6 +541,8 @@ func (s *Server) addrDiscoveryLoop() { // Mirror magicsock behavior for duration between STUN. We consider // 30s a min bound for NAT timeout. timer.Reset(tstime.RandomDurationBetween(20*time.Second, 26*time.Second)) + // TODO(illotum) Pass in context bound to the [s.closeCh] lifetime, + // and do not block on getAddrPorts IO. addrPorts, err := getAddrPorts() if err != nil { s.logf("error discovering IP:port candidates: %v", err) @@ -582,8 +651,9 @@ func trySetSOMark(logf logger.Logf, netMon *netmon.Monitor, network, address str // single packet syscall operations. func (s *Server) bindSockets(desiredPort uint16) error { // maxSocketsPerAF is a conservative starting point, but is somewhat - // arbitrary. - maxSocketsPerAF := min(16, runtime.NumCPU()) + // arbitrary. Use GOMAXPROCS rather than NumCPU as it is container-aware + // and respects CPU limits/quotas set via cgroups. + maxSocketsPerAF := min(16, runtime.GOMAXPROCS(0)) listenConfig := &net.ListenConfig{ Control: func(network, address string, c syscall.RawConn) error { trySetReusePort(network, address, c) @@ -683,33 +753,33 @@ func (s *Server) Close() error { clear(s.serverEndpointByDisco) s.closed = true s.bus.Close() + deregisterMetrics() }) return nil } +func (s *Server) endpointGC(bindLifetime, steadyStateLifetime time.Duration) { + now := mono.Now() + // TODO: consider performance implications of scanning all endpoints and + // holding s.mu for the duration. Keep it simple (and slow) for now. + s.mu.Lock() + defer s.mu.Unlock() + for k, v := range s.serverEndpointByDisco { + if v.maybeExpire(now, bindLifetime, steadyStateLifetime, s.metrics) { + delete(s.serverEndpointByDisco, k) + s.serverEndpointByVNI.Delete(v.vni) + } + } +} + func (s *Server) endpointGCLoop() { defer s.wg.Done() ticker := time.NewTicker(s.bindLifetime) defer ticker.Stop() - - gc := func() { - now := mono.Now() - // TODO: consider performance implications of scanning all endpoints and - // holding s.mu for the duration. Keep it simple (and slow) for now. - s.mu.Lock() - defer s.mu.Unlock() - for k, v := range s.serverEndpointByDisco { - if v.isExpired(now, s.bindLifetime, s.steadyStateLifetime) { - delete(s.serverEndpointByDisco, k) - s.serverEndpointByVNI.Delete(v.vni) - } - } - } - for { select { case <-ticker.C: - gc() + s.endpointGC(s.bindLifetime, s.steadyStateLifetime) case <-s.closeCh: return } @@ -753,7 +823,7 @@ func (s *Server) handlePacket(from netip.AddrPort, b []byte) (write []byte, to n } msg := b[packet.GeneveFixedHeaderLength:] secrets := s.getMACSecrets(now) - write, to = e.(*serverEndpoint).handleSealedDiscoControlMsg(from, msg, s.discoPublic, secrets, now) + write, to = e.(*serverEndpoint).handleSealedDiscoControlMsg(from, msg, s.discoPublic, secrets, now, s.metrics) isDataPacket = false return } @@ -995,6 +1065,7 @@ func (s *Server) AllocateEndpoint(discoA, discoB key.DiscoPublic) (endpoint.Serv s.serverEndpointByVNI.Store(e.vni, e) s.logf("allocated endpoint vni=%d lamportID=%d disco[0]=%v disco[1]=%v", e.vni, e.lamportID, pair.Get()[0].ShortString(), pair.Get()[1].ShortString()) + s.metrics.updateEndpoint(endpointClosed, endpointConnecting) return endpoint.ServerEndpoint{ ServerDisco: s.discoPublic, ClientDisco: pair.Get(), diff --git a/net/udprelay/server_linux.go b/net/udprelay/server_linux.go index d4cf2a2b16ee9..3a734f9c75505 100644 --- a/net/udprelay/server_linux.go +++ b/net/udprelay/server_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/udprelay/server_notlinux.go b/net/udprelay/server_notlinux.go index f21020631f76e..027ffb7658aa2 100644 --- a/net/udprelay/server_notlinux.go +++ b/net/udprelay/server_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/net/udprelay/server_test.go b/net/udprelay/server_test.go index 59917e1c6ef52..66de0d88a7d0d 100644 --- a/net/udprelay/server_test.go +++ b/net/udprelay/server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package udprelay @@ -8,6 +8,7 @@ import ( "crypto/rand" "net" "net/netip" + "sync" "testing" "time" @@ -21,6 +22,7 @@ import ( "tailscale.com/tstime/mono" "tailscale.com/types/key" "tailscale.com/types/views" + "tailscale.com/util/mak" "tailscale.com/util/usermetric" ) @@ -471,3 +473,75 @@ func TestServer_maybeRotateMACSecretLocked(t *testing.T) { qt.Assert(t, macSecret, qt.Not(qt.Equals), s.macSecrets.At(1)) qt.Assert(t, s.macSecrets.At(0), qt.Not(qt.Equals), s.macSecrets.At(1)) } + +func TestServer_endpointGC(t *testing.T) { + for _, tc := range []struct { + name string + addrs [2]netip.AddrPort + lastSeen [2]mono.Time + allocatedAt mono.Time + wantRemoved bool + }{ + { + name: "unbound_endpoint_expired", + allocatedAt: mono.Now().Add(-2 * defaultBindLifetime), + wantRemoved: true, + }, + { + name: "unbound_endpoint_kept", + allocatedAt: mono.Now(), + wantRemoved: false, + }, + { + name: "bound_endpoint_expired_a", + addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")}, + lastSeen: [2]mono.Time{mono.Now().Add(-2 * defaultSteadyStateLifetime), mono.Now()}, + wantRemoved: true, + }, + { + name: "bound_endpoint_expired_b", + addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")}, + lastSeen: [2]mono.Time{mono.Now(), mono.Now().Add(-2 * defaultSteadyStateLifetime)}, + wantRemoved: true, + }, + { + name: "bound_endpoint_kept", + addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")}, + lastSeen: [2]mono.Time{mono.Now(), mono.Now()}, + wantRemoved: false, + }, + } { + t.Run(tc.name, func(t *testing.T) { + disco1 := key.NewDisco() + disco2 := key.NewDisco() + pair := key.NewSortedPairOfDiscoPublic(disco1.Public(), disco2.Public()) + ep := &serverEndpoint{ + discoPubKeys: pair, + vni: 1, + lastSeen: tc.lastSeen, + boundAddrPorts: tc.addrs, + allocatedAt: tc.allocatedAt, + } + s := &Server{serverEndpointByVNI: sync.Map{}, metrics: &metrics{}} + mak.Set(&s.serverEndpointByDisco, pair, ep) + s.serverEndpointByVNI.Store(ep.vni, ep) + s.endpointGC(defaultBindLifetime, defaultSteadyStateLifetime) + removed := len(s.serverEndpointByDisco) > 0 + if tc.wantRemoved { + if removed { + t.Errorf("expected endpoint to be removed from Server") + } + if !ep.closed { + t.Errorf("expected endpoint to be closed") + } + } else { + if !removed { + t.Errorf("expected endpoint to remain in Server") + } + if ep.closed { + t.Errorf("expected endpoint to remain open") + } + } + }) + } +} diff --git a/net/udprelay/status/status.go b/net/udprelay/status/status.go index 9ed9a0d2a8def..d64792ab6032e 100644 --- a/net/udprelay/status/status.go +++ b/net/udprelay/status/status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package status contains types relating to the status of peer relay sessions diff --git a/net/wsconn/wsconn.go b/net/wsconn/wsconn.go index 9e44da59ca1d7..fed734cf5ffd8 100644 --- a/net/wsconn/wsconn.go +++ b/net/wsconn/wsconn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wsconn contains an adapter type that turns diff --git a/omit/aws_def.go b/omit/aws_def.go index 8ae539736b28c..7f48881c10bcf 100644 --- a/omit/aws_def.go +++ b/omit/aws_def.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_aws diff --git a/omit/aws_omit.go b/omit/aws_omit.go index 5b6957d5b639b..f077041158f0d 100644 --- a/omit/aws_omit.go +++ b/omit/aws_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_aws diff --git a/omit/omit.go b/omit/omit.go index 018cfba94545c..e59d4fc3c61f7 100644 --- a/omit/omit.go +++ b/omit/omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package omit provides consts to access Tailscale ts_omit_FOO build tags. diff --git a/packages/deb/deb.go b/packages/deb/deb.go index cab0fea075e74..63f30fc9d7d4f 100644 --- a/packages/deb/deb.go +++ b/packages/deb/deb.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package deb extracts metadata from Debian packages. diff --git a/packages/deb/deb_test.go b/packages/deb/deb_test.go index 1a25f67ad4875..fb8a6454c3ab7 100644 --- a/packages/deb/deb_test.go +++ b/packages/deb/deb_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deb diff --git a/paths/migrate.go b/paths/migrate.go index 3a23ecca34fdc..22f947611f4cd 100644 --- a/paths/migrate.go +++ b/paths/migrate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package paths diff --git a/paths/paths.go b/paths/paths.go index 6c9c3fa6c9dea..398d8b23d8988 100644 --- a/paths/paths.go +++ b/paths/paths.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package paths returns platform and user-specific default paths to diff --git a/paths/paths_unix.go b/paths/paths_unix.go index d317921d59cd9..b1556b233104f 100644 --- a/paths/paths_unix.go +++ b/paths/paths_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !wasm && !plan9 && !tamago diff --git a/paths/paths_windows.go b/paths/paths_windows.go index 4705400655212..850a1c97b52a0 100644 --- a/paths/paths_windows.go +++ b/paths/paths_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package paths diff --git a/pkgdoc_test.go b/pkgdoc_test.go index 0f4a455288950..60b2d4856d6c7 100644 --- a/pkgdoc_test.go +++ b/pkgdoc_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscaleroot @@ -71,6 +71,10 @@ func TestPackageDocs(t *testing.T) { t.Logf("multiple files with package doc in %s: %q", dir, ff) } if len(ff) == 0 { + if strings.HasPrefix(dir, "gokrazy/") { + // Ignore gokrazy appliances. Their *.go file is only for deps. + continue + } t.Errorf("no package doc in %s", dir) } } diff --git a/portlist/clean.go b/portlist/clean.go index 7e137de948e99..f6c3f4a6b3587 100644 --- a/portlist/clean.go +++ b/portlist/clean.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/clean_test.go b/portlist/clean_test.go index 5a1e34405eed0..e7a5f6a0ca4ac 100644 --- a/portlist/clean_test.go +++ b/portlist/clean_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/netstat.go b/portlist/netstat.go index 5fdef675d0e2a..de625afb52170 100644 --- a/portlist/netstat.go +++ b/portlist/netstat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/portlist/netstat_test.go b/portlist/netstat_test.go index 023b75b794426..7048e90b2ffea 100644 --- a/portlist/netstat_test.go +++ b/portlist/netstat_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/portlist/poller.go b/portlist/poller.go index 423bad3be33ba..a8e611054eb1b 100644 --- a/portlist/poller.go +++ b/portlist/poller.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains the code related to the Poller type and its methods. diff --git a/portlist/portlist.go b/portlist/portlist.go index 9f7af40d08dc1..9430e2562268b 100644 --- a/portlist/portlist.go +++ b/portlist/portlist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file is just the types. The bulk of the code is in poller.go. diff --git a/portlist/portlist_linux.go b/portlist/portlist_linux.go index 94f843746c29d..159c4beb3a74a 100644 --- a/portlist/portlist_linux.go +++ b/portlist/portlist_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/portlist_linux_test.go b/portlist/portlist_linux_test.go index 24635fae26577..4b541f8e7dd70 100644 --- a/portlist/portlist_linux_test.go +++ b/portlist/portlist_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/portlist_macos.go b/portlist/portlist_macos.go index e67b2c9b8c064..d210fdd946de1 100644 --- a/portlist/portlist_macos.go +++ b/portlist/portlist_macos.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/portlist/portlist_plan9.go b/portlist/portlist_plan9.go index 77f8619f97ffa..62ed61fb3ddea 100644 --- a/portlist/portlist_plan9.go +++ b/portlist/portlist_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/portlist_test.go b/portlist/portlist_test.go index 8503b0fefdf50..922cb7a1ef562 100644 --- a/portlist/portlist_test.go +++ b/portlist/portlist_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist @@ -11,7 +11,7 @@ import ( "tailscale.com/tstest" ) -func maybeSkip(t *testing.T) { +func maybeSkip(t testing.TB) { if runtime.GOOS == "linux" { tstest.SkipOnKernelVersions(t, "https://github.com/tailscale/tailscale/issues/16966", @@ -214,6 +214,7 @@ func BenchmarkGetListIncremental(b *testing.B) { } func benchmarkGetList(b *testing.B, incremental bool) { + maybeSkip(b) b.ReportAllocs() var p Poller p.init() diff --git a/portlist/portlist_windows.go b/portlist/portlist_windows.go index f449973599247..bd603dbfd8f91 100644 --- a/portlist/portlist_windows.go +++ b/portlist/portlist_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/posture/doc.go b/posture/doc.go index d061065235b99..14fd21998647e 100644 --- a/posture/doc.go +++ b/posture/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package posture contains functions to query the local system diff --git a/posture/hwaddr.go b/posture/hwaddr.go index dd0b6d8be77ce..2075331f16727 100644 --- a/posture/hwaddr.go +++ b/posture/hwaddr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package posture diff --git a/posture/serialnumber_macos.go b/posture/serialnumber_macos.go index 18c929107a768..fed0d4111fb83 100644 --- a/posture/serialnumber_macos.go +++ b/posture/serialnumber_macos.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo && darwin && !ios diff --git a/posture/serialnumber_macos_test.go b/posture/serialnumber_macos_test.go index 9d9b9f578da55..5f1aec5cd790b 100644 --- a/posture/serialnumber_macos_test.go +++ b/posture/serialnumber_macos_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo && darwin && !ios diff --git a/posture/serialnumber_notmacos.go b/posture/serialnumber_notmacos.go index 132fa08f6a56e..e076b8f3dcfdf 100644 --- a/posture/serialnumber_notmacos.go +++ b/posture/serialnumber_notmacos.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Build on Windows, Linux and *BSD diff --git a/posture/serialnumber_notmacos_test.go b/posture/serialnumber_notmacos_test.go index da5aada8509e3..1009ea6b4a208 100644 --- a/posture/serialnumber_notmacos_test.go +++ b/posture/serialnumber_notmacos_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Build on Windows, Linux and *BSD diff --git a/posture/serialnumber_stub.go b/posture/serialnumber_stub.go index 854a0014bd1bf..e040aacfb30e2 100644 --- a/posture/serialnumber_stub.go +++ b/posture/serialnumber_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // js: not implemented diff --git a/posture/serialnumber_syspolicy.go b/posture/serialnumber_syspolicy.go index 64a154a2cae0b..448fdb677abef 100644 --- a/posture/serialnumber_syspolicy.go +++ b/posture/serialnumber_syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build android || ios diff --git a/posture/serialnumber_test.go b/posture/serialnumber_test.go index 6db3651e21cd7..20e726d9ff530 100644 --- a/posture/serialnumber_test.go +++ b/posture/serialnumber_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package posture diff --git a/prober/derp.go b/prober/derp.go index 22843b53a4049..73ea02cf5ad4f 100644 --- a/prober/derp.go +++ b/prober/derp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/derp_test.go b/prober/derp_test.go index 08a65d6978f13..364d57481ae20 100644 --- a/prober/derp_test.go +++ b/prober/derp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/dns.go b/prober/dns.go index 77e22ea3f89ba..cfef252716ae9 100644 --- a/prober/dns.go +++ b/prober/dns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/dns_example_test.go b/prober/dns_example_test.go index 089816919489a..625ecec0c1411 100644 --- a/prober/dns_example_test.go +++ b/prober/dns_example_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober_test diff --git a/prober/dns_test.go b/prober/dns_test.go index 1b6c31b554877..4eaea199ae289 100644 --- a/prober/dns_test.go +++ b/prober/dns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/histogram.go b/prober/histogram.go index c544a5f79bb17..5c52894f9eb02 100644 --- a/prober/histogram.go +++ b/prober/histogram.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/histogram_test.go b/prober/histogram_test.go index dbb5eda6741a5..2c7deea354a89 100644 --- a/prober/histogram_test.go +++ b/prober/histogram_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/http.go b/prober/http.go index e4b0b26fd3e7d..144ed3fb55195 100644 --- a/prober/http.go +++ b/prober/http.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/prober.go b/prober/prober.go index 6b904dd97d231..3a43401a14ac3 100644 --- a/prober/prober.go +++ b/prober/prober.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package prober implements a simple blackbox prober. Each probe runs @@ -161,6 +161,7 @@ func newProbe(p *Prober, name string, interval time.Duration, lg prometheus.Labe mEndTime: prometheus.NewDesc("end_secs", "Latest probe end time (seconds since epoch)", nil, lg), mLatency: prometheus.NewDesc("latency_millis", "Latest probe latency (ms)", nil, lg), mResult: prometheus.NewDesc("result", "Latest probe result (1 = success, 0 = failure)", nil, lg), + mInFlight: prometheus.NewDesc("in_flight", "Number of probes currently running", nil, lg), mAttempts: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "attempts_total", Help: "Total number of probing attempts", ConstLabels: lg, }, []string{"status"}), @@ -261,10 +262,12 @@ type Probe struct { mEndTime *prometheus.Desc mLatency *prometheus.Desc mResult *prometheus.Desc + mInFlight *prometheus.Desc mAttempts *prometheus.CounterVec mSeconds *prometheus.CounterVec mu sync.Mutex + inFlight int // number of currently running probes start time.Time // last time doProbe started end time.Time // last time doProbe returned latency time.Duration // last successful probe latency @@ -392,11 +395,13 @@ func (p *Probe) run() (pi ProbeInfo, err error) { func (p *Probe) recordStart() { p.mu.Lock() p.start = p.prober.now() + p.inFlight++ p.mu.Unlock() } func (p *Probe) recordEndLocked(err error) { end := p.prober.now() + p.inFlight-- p.end = end p.succeeded = err == nil p.lastErr = err @@ -649,6 +654,7 @@ func (p *Probe) Describe(ch chan<- *prometheus.Desc) { ch <- p.mStartTime ch <- p.mEndTime ch <- p.mResult + ch <- p.mInFlight ch <- p.mLatency p.mAttempts.Describe(ch) p.mSeconds.Describe(ch) @@ -664,6 +670,7 @@ func (p *Probe) Collect(ch chan<- prometheus.Metric) { p.mu.Lock() defer p.mu.Unlock() ch <- prometheus.MustNewConstMetric(p.mInterval, prometheus.GaugeValue, p.interval.Seconds()) + ch <- prometheus.MustNewConstMetric(p.mInFlight, prometheus.GaugeValue, float64(p.inFlight)) if !p.start.IsZero() { ch <- prometheus.MustNewConstMetric(p.mStartTime, prometheus.GaugeValue, float64(p.start.Unix())) } diff --git a/prober/prober_test.go b/prober/prober_test.go index 1e045fa8971b0..14b75d5b5c2b1 100644 --- a/prober/prober_test.go +++ b/prober/prober_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober @@ -213,6 +213,14 @@ func TestProberConcurrency(t *testing.T) { if got, want := ran.Load(), int64(3); got != want { return fmt.Errorf("expected %d probes to run concurrently, got %d", want, got) } + wantMetrics := ` + # HELP prober_in_flight Number of probes currently running + # TYPE prober_in_flight gauge + prober_in_flight{class="",name="foo"} 3 + ` + if err := testutil.GatherAndCompare(p.metrics, strings.NewReader(wantMetrics), "prober_in_flight"); err != nil { + return fmt.Errorf("unexpected metrics: %w", err) + } return nil }); err != nil { t.Fatal(err) @@ -308,9 +316,12 @@ probe_end_secs{class="",label="value",name="testprobe"} %d # HELP probe_result Latest probe result (1 = success, 0 = failure) # TYPE probe_result gauge probe_result{class="",label="value",name="testprobe"} 0 +# HELP probe_in_flight Number of probes currently running +# TYPE probe_in_flight gauge +probe_in_flight{class="",label="value",name="testprobe"} 0 `, probeInterval.Seconds(), epoch.Unix(), epoch.Add(aFewMillis).Unix()) return testutil.GatherAndCompare(p.metrics, strings.NewReader(want), - "probe_interval_secs", "probe_start_secs", "probe_end_secs", "probe_result") + "probe_interval_secs", "probe_start_secs", "probe_end_secs", "probe_result", "probe_in_flight") }) if err != nil { t.Fatal(err) @@ -338,9 +349,13 @@ probe_latency_millis{class="",label="value",name="testprobe"} %d # HELP probe_result Latest probe result (1 = success, 0 = failure) # TYPE probe_result gauge probe_result{class="",label="value",name="testprobe"} 1 +# HELP probe_in_flight Number of probes currently running +# TYPE probe_in_flight gauge +probe_in_flight{class="",label="value",name="testprobe"} 0 `, probeInterval.Seconds(), start.Unix(), end.Unix(), aFewMillis.Milliseconds()) return testutil.GatherAndCompare(p.metrics, strings.NewReader(want), - "probe_interval_secs", "probe_start_secs", "probe_end_secs", "probe_latency_millis", "probe_result") + "probe_interval_secs", "probe_start_secs", "probe_end_secs", + "probe_latency_millis", "probe_result", "probe_in_flight") }) if err != nil { t.Fatal(err) @@ -778,9 +793,14 @@ func TestExcludeInRunAll(t *testing.T) { }, } - p.Run("includedProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) - p.Run("excludedProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) - p.Run("excludedOtherProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) + includedProbe := p.Run("includedProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) + excludedProbe := p.Run("excludedProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) + excludedOtherProbe := p.Run("excludedOtherProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) + + // Wait for all probes to complete their initial run + <-includedProbe.stopped + <-excludedProbe.stopped + <-excludedOtherProbe.stopped mux := http.NewServeMux() server := httptest.NewServer(mux) diff --git a/prober/status.go b/prober/status.go index 20fbeec58a77e..a06d3d55c4e6c 100644 --- a/prober/status.go +++ b/prober/status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/tcp.go b/prober/tcp.go index 22d05461652a4..f932be44553ab 100644 --- a/prober/tcp.go +++ b/prober/tcp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/tls.go b/prober/tls.go index 3ce5354357d71..1247f9502e8f6 100644 --- a/prober/tls.go +++ b/prober/tls.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/tls_test.go b/prober/tls_test.go index 86fba91b98836..a32693762f291 100644 --- a/prober/tls_test.go +++ b/prober/tls_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/tun_darwin.go b/prober/tun_darwin.go index 0ef22e41e4076..45c5415acd8d0 100644 --- a/prober/tun_darwin.go +++ b/prober/tun_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/prober/tun_default.go b/prober/tun_default.go index 93a5b07fd442a..2094e19933c06 100644 --- a/prober/tun_default.go +++ b/prober/tun_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !darwin diff --git a/prober/tun_linux.go b/prober/tun_linux.go index 52a31efbbf66a..7a28a4b3f829e 100644 --- a/prober/tun_linux.go +++ b/prober/tun_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/proxymap/proxymap.go b/proxymap/proxymap.go index 20dc96c848307..2407371513d89 100644 --- a/proxymap/proxymap.go +++ b/proxymap/proxymap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package proxymap contains a mapping table for ephemeral localhost ports used diff --git a/pull-toolchain.sh b/pull-toolchain.sh index eb8febf6bb32d..c80c913bb17b2 100755 --- a/pull-toolchain.sh +++ b/pull-toolchain.sh @@ -1,20 +1,34 @@ #!/bin/sh # Retrieve the latest Go toolchain. +# Set TS_GO_NEXT=1 to update go.toolchain.next.rev instead. # set -eu cd "$(dirname "$0")" -read -r go_branch go.toolchain.rev + echo "$upstream" >"$go_toolchain_rev_file" fi -./tool/go version 2>/dev/null | awk '{print $3}' | sed 's/^go//' > go.toolchain.version - -./update-flake.sh +# Only update go.toolchain.version and go.toolchain.rev.sri for the main toolchain, +# skipping it if TS_GO_NEXT=1. Those two files are only used by Nix, and as of 2026-01-26 +# don't yet support TS_GO_NEXT=1 with flake.nix or in our corp CI. +if [ "${TS_GO_NEXT:-}" != "1" ]; then + ./tool/go version 2>/dev/null | awk '{print $3}' | sed 's/^go//' > go.toolchain.version + ./tool/go mod edit -go "$(cat go.toolchain.version)" + ./update-flake.sh +fi -if [ -n "$(git diff-index --name-only HEAD -- go.toolchain.rev go.toolchain.rev.sri go.toolchain.version)" ]; then +if [ -n "$(git diff-index --name-only HEAD -- "$go_toolchain_rev_file" go.toolchain.rev.sri go.toolchain.version)" ]; then echo "pull-toolchain.sh: changes imported. Use git commit to make them permanent." >&2 fi diff --git a/release/dist/cli/cli.go b/release/dist/cli/cli.go index f4480cbdbdfa4..ca4977f5d2cbe 100644 --- a/release/dist/cli/cli.go +++ b/release/dist/cli/cli.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cli provides the skeleton of a CLI for building release packages. diff --git a/release/dist/dist.go b/release/dist/dist.go index 6fb0102993cbd..094d0a0e04c46 100644 --- a/release/dist/dist.go +++ b/release/dist/dist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dist is a release artifact builder library. diff --git a/release/dist/memoize.go b/release/dist/memoize.go index 0927ac0a81540..bdf0f68ff9fd6 100644 --- a/release/dist/memoize.go +++ b/release/dist/memoize.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dist diff --git a/release/dist/qnap/pkgs.go b/release/dist/qnap/pkgs.go index 5062011f06ea6..1d69b3eaf3500 100644 --- a/release/dist/qnap/pkgs.go +++ b/release/dist/qnap/pkgs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package qnap contains dist Targets for building QNAP Tailscale packages. diff --git a/release/dist/qnap/targets.go b/release/dist/qnap/targets.go index 0a02139548b17..3eef3cbbe693d 100644 --- a/release/dist/qnap/targets.go +++ b/release/dist/qnap/targets.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package qnap diff --git a/release/dist/synology/pkgs.go b/release/dist/synology/pkgs.go index ab89dbee3e19f..c2fe6528e4dfe 100644 --- a/release/dist/synology/pkgs.go +++ b/release/dist/synology/pkgs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package synology contains dist Targets for building Synology Tailscale packages. diff --git a/release/dist/synology/targets.go b/release/dist/synology/targets.go index bc7b20afca5d3..2f08510557d8d 100644 --- a/release/dist/synology/targets.go +++ b/release/dist/synology/targets.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package synology diff --git a/release/dist/unixpkgs/pkgs.go b/release/dist/unixpkgs/pkgs.go index bad6ce572e675..6e140d580b904 100644 --- a/release/dist/unixpkgs/pkgs.go +++ b/release/dist/unixpkgs/pkgs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package unixpkgs contains dist Targets for building unix Tailscale packages. @@ -140,6 +140,12 @@ func (t *tgzTarget) Build(b *dist.Build) ([]string, error) { if err := addFile(filepath.Join(tailscaledDir, "tailscaled.defaults"), filepath.Join(dir, "tailscaled.defaults"), 0644); err != nil { return nil, err } + if err := addFile(filepath.Join(tailscaledDir, "tailscale-online.target"), filepath.Join(dir, "tailscale-online.target"), 0644); err != nil { + return nil, err + } + if err := addFile(filepath.Join(tailscaledDir, "tailscale-wait-online.service"), filepath.Join(dir, "tailscale-wait-online.service"), 0644); err != nil { + return nil, err + } } if err := tw.Close(); err != nil { return nil, err @@ -223,6 +229,16 @@ func (t *debTarget) Build(b *dist.Build) ([]string, error) { Source: filepath.Join(tailscaledDir, "tailscaled.service"), Destination: "/lib/systemd/system/tailscaled.service", }, + &files.Content{ + Type: files.TypeFile, + Source: filepath.Join(tailscaledDir, "tailscale-online.target"), + Destination: "/lib/systemd/system/tailscale-online.target", + }, + &files.Content{ + Type: files.TypeFile, + Source: filepath.Join(tailscaledDir, "tailscale-wait-online.service"), + Destination: "/lib/systemd/system/tailscale-wait-online.service", + }, &files.Content{ Type: files.TypeConfigNoReplace, Source: filepath.Join(tailscaledDir, "tailscaled.defaults"), @@ -360,6 +376,16 @@ func (t *rpmTarget) Build(b *dist.Build) ([]string, error) { Source: filepath.Join(tailscaledDir, "tailscaled.service"), Destination: "/lib/systemd/system/tailscaled.service", }, + &files.Content{ + Type: files.TypeFile, + Source: filepath.Join(tailscaledDir, "tailscale-online.target"), + Destination: "/lib/systemd/system/tailscale-online.target", + }, + &files.Content{ + Type: files.TypeFile, + Source: filepath.Join(tailscaledDir, "tailscale-wait-online.service"), + Destination: "/lib/systemd/system/tailscale-wait-online.service", + }, &files.Content{ Type: files.TypeConfigNoReplace, Source: filepath.Join(tailscaledDir, "tailscaled.defaults"), diff --git a/release/dist/unixpkgs/targets.go b/release/dist/unixpkgs/targets.go index 42bab6d3b2685..b5f96fc38b3f0 100644 --- a/release/dist/unixpkgs/targets.go +++ b/release/dist/unixpkgs/targets.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package unixpkgs diff --git a/release/release.go b/release/release.go index a8d0e6b62e8d7..314bb0d8e7473 100644 --- a/release/release.go +++ b/release/release.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package release provides functionality for building client releases. diff --git a/safesocket/basic_test.go b/safesocket/basic_test.go index 292a3438a0e75..9cef300497422 100644 --- a/safesocket/basic_test.go +++ b/safesocket/basic_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/pipe_windows.go b/safesocket/pipe_windows.go index 2968542f2ccf4..0ffee762f8840 100644 --- a/safesocket/pipe_windows.go +++ b/safesocket/pipe_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/pipe_windows_test.go b/safesocket/pipe_windows_test.go index 8d9cbd19b5e43..5d4e68cc251c9 100644 --- a/safesocket/pipe_windows_test.go +++ b/safesocket/pipe_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/safesocket.go b/safesocket/safesocket.go index 287cdca599f77..6be8ae5b8fac3 100644 --- a/safesocket/safesocket.go +++ b/safesocket/safesocket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package safesocket creates either a Unix socket, if possible, or diff --git a/safesocket/safesocket_darwin.go b/safesocket/safesocket_darwin.go index e2b3ea4581059..8cbabff63364e 100644 --- a/safesocket/safesocket_darwin.go +++ b/safesocket/safesocket_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/safesocket_darwin_test.go b/safesocket/safesocket_darwin_test.go index e52959ad58dcf..d828a80f5dfe2 100644 --- a/safesocket/safesocket_darwin_test.go +++ b/safesocket/safesocket_darwin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/safesocket_js.go b/safesocket/safesocket_js.go index 38e615da43535..746fea51115c4 100644 --- a/safesocket/safesocket_js.go +++ b/safesocket/safesocket_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/safesocket_plan9.go b/safesocket/safesocket_plan9.go index c8a5e3b05bbef..921e758748d5c 100644 --- a/safesocket/safesocket_plan9.go +++ b/safesocket/safesocket_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build plan9 diff --git a/safesocket/safesocket_ps.go b/safesocket/safesocket_ps.go index d3f409df58d15..6130ca5b0d574 100644 --- a/safesocket/safesocket_ps.go +++ b/safesocket/safesocket_ps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ((linux && !android) || windows || (darwin && !ios) || freebsd) && !ts_omit_cliconndiag diff --git a/safesocket/safesocket_test.go b/safesocket/safesocket_test.go index 3f36a1cf6ca1f..be2dd193d8e32 100644 --- a/safesocket/safesocket_test.go +++ b/safesocket/safesocket_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/unixsocket.go b/safesocket/unixsocket.go index ec8635bbbf0d7..6fe3883c32c9d 100644 --- a/safesocket/unixsocket.go +++ b/safesocket/unixsocket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !js && !plan9 diff --git a/safeweb/http.go b/safeweb/http.go index d085fcb8819d8..f76591cbd0e16 100644 --- a/safeweb/http.go +++ b/safeweb/http.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package safeweb provides a wrapper around an http.Server that applies diff --git a/safeweb/http_test.go b/safeweb/http_test.go index 852ce326ba374..cbac7210a4807 100644 --- a/safeweb/http_test.go +++ b/safeweb/http_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safeweb diff --git a/scripts/installer.sh b/scripts/installer.sh index 89d54a4311d01..2c15ea6571678 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # # This script detects the current operating system, and installs @@ -341,6 +341,11 @@ main() { echo "https://github.com/tailscale-dev/deck-tailscale" exit 1 ;; + kde-linux) + echo "The maintainers of KDE Linux provide documentation on multiple ways to install Tailscale. These instructions are not officially supported by Tailscale:" + echo "https://kde.org/linux/docs/more-software/#tailscale" + exit 1 + ;; # TODO: wsl? # TODO: synology? qnap? @@ -603,7 +608,7 @@ main() { $SUDO dnf config-manager --add-repo "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" elif [ "$DNF_VERSION" = "5" ]; then # Already installed config-manager, above. - $SUDO dnf config-manager addrepo --from-repofile="https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" + $SUDO dnf config-manager addrepo --overwrite --from-repofile="https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" else echo "unexpected: unknown dnf version $DNF_VERSION" exit 1 diff --git a/sessionrecording/connect.go b/sessionrecording/connect.go index 9d20b41f9b31a..6135688ca0153 100644 --- a/sessionrecording/connect.go +++ b/sessionrecording/connect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sessionrecording contains session recording utils shared amongst diff --git a/sessionrecording/connect_test.go b/sessionrecording/connect_test.go index e834828f5a6cc..64bcb1c3185d3 100644 --- a/sessionrecording/connect_test.go +++ b/sessionrecording/connect_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sessionrecording diff --git a/sessionrecording/event.go b/sessionrecording/event.go index 8f8172cc4b303..0597048a2113f 100644 --- a/sessionrecording/event.go +++ b/sessionrecording/event.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sessionrecording diff --git a/sessionrecording/header.go b/sessionrecording/header.go index 2208522168dec..95b70962cefd8 100644 --- a/sessionrecording/header.go +++ b/sessionrecording/header.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sessionrecording diff --git a/shell.nix b/shell.nix index ccec5faf538e0..7ddf62c52df5c 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-WeMTOkERj4hvdg4yPaZ1gRgKnhRIBXX55kUVbX/k/xM= +# nix-direnv cache busting line: sha256-rhuWEEN+CtumVxOw6Dy/IRxWIrZ2x6RJb6ULYwXCQc4= diff --git a/ssh/tailssh/accept_env.go b/ssh/tailssh/accept_env.go index 6461a79a3408b..6354d41d76a6b 100644 --- a/ssh/tailssh/accept_env.go +++ b/ssh/tailssh/accept_env.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailssh diff --git a/ssh/tailssh/accept_env_test.go b/ssh/tailssh/accept_env_test.go index b54c980978ece..25787db302357 100644 --- a/ssh/tailssh/accept_env_test.go +++ b/ssh/tailssh/accept_env_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailssh diff --git a/ssh/tailssh/auditd_linux.go b/ssh/tailssh/auditd_linux.go index e9f551d9e7991..bddb901d5cebe 100644 --- a/ssh/tailssh/auditd_linux.go +++ b/ssh/tailssh/auditd_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/ssh/tailssh/auditd_linux_test.go b/ssh/tailssh/auditd_linux_test.go index 93f5442918a98..c3c2302fe9e66 100644 --- a/ssh/tailssh/auditd_linux_test.go +++ b/ssh/tailssh/auditd_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/ssh/tailssh/incubator.go b/ssh/tailssh/incubator.go index f75646771057a..b414ce3fbf42a 100644 --- a/ssh/tailssh/incubator.go +++ b/ssh/tailssh/incubator.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains the code for the incubator process. Tailscaled diff --git a/ssh/tailssh/incubator_linux.go b/ssh/tailssh/incubator_linux.go index 4dfb9f27cc097..cff46160758f3 100644 --- a/ssh/tailssh/incubator_linux.go +++ b/ssh/tailssh/incubator_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/ssh/tailssh/incubator_plan9.go b/ssh/tailssh/incubator_plan9.go index 61b6a54ebdc94..69112635f5c11 100644 --- a/ssh/tailssh/incubator_plan9.go +++ b/ssh/tailssh/incubator_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains the plan9-specific version of the incubator. Tailscaled diff --git a/ssh/tailssh/privs_test.go b/ssh/tailssh/privs_test.go index 32b219a7798ca..f0ec66c64e581 100644 --- a/ssh/tailssh/privs_test.go +++ b/ssh/tailssh/privs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || darwin || freebsd || openbsd || netbsd || dragonfly diff --git a/ssh/tailssh/tailssh.go b/ssh/tailssh/tailssh.go index 91e1779bfd543..cb56f701b5e68 100644 --- a/ssh/tailssh/tailssh.go +++ b/ssh/tailssh/tailssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9 @@ -192,9 +192,9 @@ func (srv *server) OnPolicyChange() { srv.mu.Lock() defer srv.mu.Unlock() for c := range srv.activeConns { - if c.info == nil { - // c.info is nil when the connection hasn't been authenticated yet. - // In that case, the connection will be terminated when it is. + if !c.authCompleted.Load() { + // The connection hasn't completed authentication yet. + // In that case, the connection will be terminated when it does. continue } go c.checkStillValid() @@ -236,14 +236,26 @@ type conn struct { // Banners cannot be sent after auth completes. spac gossh.ServerPreAuthConn + // The following fields are set during clientAuth and are used for policy + // evaluation and session management. They are immutable after clientAuth + // completes. They must not be read from other goroutines until + // authCompleted is set to true. + action0 *tailcfg.SSHAction // set by clientAuth finalAction *tailcfg.SSHAction // set by clientAuth - info *sshConnInfo // set by setInfo + info *sshConnInfo // set by setInfo (during clientAuth) localUser *userMeta // set by clientAuth userGroupIDs []string // set by clientAuth acceptEnv []string + // authCompleted is set to true after clientAuth has finished writing + // all authentication state fields (info, localUser, action0, + // finalAction, userGroupIDs, acceptEnv). It provides a memory + // barrier so that concurrent readers (e.g. OnPolicyChange) see + // fully-initialized values. + authCompleted atomic.Bool + // mu protects the following fields. // // srv.mu should be acquired prior to mu. @@ -369,6 +381,7 @@ func (c *conn) clientAuth(cm gossh.ConnMetadata) (perms *gossh.Permissions, retE } } c.finalAction = action + c.authCompleted.Store(true) return &gossh.Permissions{}, nil case action.Reject: metricTerminalReject.Add(1) diff --git a/ssh/tailssh/tailssh_integration_test.go b/ssh/tailssh/tailssh_integration_test.go index 9ab26e169665b..1135bebbc2a5b 100644 --- a/ssh/tailssh/tailssh_integration_test.go +++ b/ssh/tailssh/tailssh_integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build integrationtest diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go index 3b6d3c52c391c..6d9d859a22d91 100644 --- a/ssh/tailssh/tailssh_test.go +++ b/ssh/tailssh/tailssh_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || darwin @@ -31,6 +31,7 @@ import ( "sync" "sync/atomic" "testing" + "testing/synctest" "time" gossh "golang.org/x/crypto/ssh" @@ -495,6 +496,9 @@ func TestSSHRecordingCancelsSessionsOnUploadFailure(t *testing.T) { if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { t.Skipf("skipping on %q; only runs on linux and darwin", runtime.GOOS) } + if runtime.GOOS == "darwin" && cibuild.On() { + t.Skipf("this fails on CI on macOS; see https://github.com/tailscale/tailscale/issues/7707") + } var handler http.HandlerFunc recordingServer := mockRecordingServer(t, func(w http.ResponseWriter, r *http.Request) { @@ -1108,6 +1112,7 @@ func TestSSH(t *testing.T) { } sc.action0 = &tailcfg.SSHAction{Accept: true} sc.finalAction = sc.action0 + sc.authCompleted.Store(true) sc.Handler = func(s ssh.Session) { sc.newSSHSession(s).run() @@ -1317,6 +1322,79 @@ func TestStdOsUserUserAssumptions(t *testing.T) { } } +func TestOnPolicyChangeSkipsPreAuthConns(t *testing.T) { + tests := []struct { + name string + sshRule *tailcfg.SSHRule + wantCancel bool + }{ + { + name: "accept-after-auth", + sshRule: newSSHRule(&tailcfg.SSHAction{Accept: true}), + wantCancel: false, + }, + { + name: "reject-after-auth", + sshRule: newSSHRule(&tailcfg.SSHAction{Reject: true}), + wantCancel: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + srv := &server{ + logf: tstest.WhileTestRunningLogger(t), + lb: &localState{ + sshEnabled: true, + matchingRule: tt.sshRule, + }, + } + c := &conn{ + srv: srv, + info: &sshConnInfo{ + sshUser: "alice", + src: netip.MustParseAddrPort("1.2.3.4:30343"), + dst: netip.MustParseAddrPort("100.100.100.102:22"), + }, + localUser: &userMeta{User: user.User{Username: currentUser}}, + } + srv.activeConns = map[*conn]bool{c: true} + ctx, cancel := context.WithCancelCause(context.Background()) + ss := &sshSession{ctx: ctx, cancelCtx: cancel} + c.sessions = []*sshSession{ss} + + // Before authCompleted is set, OnPolicyChange should skip + // the conn entirely — no goroutine spawned. + srv.OnPolicyChange() + synctest.Wait() + select { + case <-ctx.Done(): + t.Fatal("session canceled before auth completed") + default: + } + + // Mark auth as completed. Now OnPolicyChange should + // evaluate the policy and act accordingly. + c.authCompleted.Store(true) + + srv.OnPolicyChange() + synctest.Wait() + select { + case <-ctx.Done(): + if !tt.wantCancel { + t.Fatal("valid session should not have been canceled") + } + default: + if tt.wantCancel { + t.Fatal("invalid session should have been canceled") + } + } + }) + }) + } +} + func mockRecordingServer(t *testing.T, handleRecord http.HandlerFunc) *httptest.Server { t.Helper() mux := http.NewServeMux() diff --git a/ssh/tailssh/user.go b/ssh/tailssh/user.go index ac92c762a875e..7da6bb4eb387f 100644 --- a/ssh/tailssh/user.go +++ b/ssh/tailssh/user.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9 diff --git a/syncs/locked.go b/syncs/locked.go index d2e9edef7a9dd..5c94e6336fb7a 100644 --- a/syncs/locked.go +++ b/syncs/locked.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/locked_test.go b/syncs/locked_test.go index 90b36e8321d82..94481f9cb2205 100644 --- a/syncs/locked_test.go +++ b/syncs/locked_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.13 && !go1.19 diff --git a/syncs/mutex.go b/syncs/mutex.go index 8034e17121717..cb60c3432b5cc 100644 --- a/syncs/mutex.go +++ b/syncs/mutex.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_mutex_debug diff --git a/syncs/mutex_debug.go b/syncs/mutex_debug.go index 55a9b1231092f..7af1e9abfe12d 100644 --- a/syncs/mutex_debug.go +++ b/syncs/mutex_debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_mutex_debug diff --git a/syncs/pool.go b/syncs/pool.go index 46ffd2e521783..9a13dd526f55d 100644 --- a/syncs/pool.go +++ b/syncs/pool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/pool_test.go b/syncs/pool_test.go index 798b18cbabfd8..34ca9973ff334 100644 --- a/syncs/pool_test.go +++ b/syncs/pool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardedint.go b/syncs/shardedint.go index 28c4168d54c79..c0fda341fb564 100644 --- a/syncs/shardedint.go +++ b/syncs/shardedint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardedint_test.go b/syncs/shardedint_test.go index 815a739d13842..8c3f7ef7bd915 100644 --- a/syncs/shardedint_test.go +++ b/syncs/shardedint_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs_test diff --git a/syncs/shardedmap.go b/syncs/shardedmap.go index 12edf5bfce475..6f53522360464 100644 --- a/syncs/shardedmap.go +++ b/syncs/shardedmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardedmap_test.go b/syncs/shardedmap_test.go index 993ffdff875c2..0491bf3dd1fbf 100644 --- a/syncs/shardedmap_test.go +++ b/syncs/shardedmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardvalue.go b/syncs/shardvalue.go index b1474477c7082..fcb5d3c73207e 100644 --- a/syncs/shardvalue.go +++ b/syncs/shardvalue.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardvalue_go.go b/syncs/shardvalue_go.go index 9b9d252a796d4..8531993319d1e 100644 --- a/syncs/shardvalue_go.go +++ b/syncs/shardvalue_go.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !tailscale_go diff --git a/syncs/shardvalue_tailscale.go b/syncs/shardvalue_tailscale.go index 8ef778ff3e669..6b03d7d0dd58e 100644 --- a/syncs/shardvalue_tailscale.go +++ b/syncs/shardvalue_tailscale.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO(raggi): update build tag after toolchain update diff --git a/syncs/shardvalue_test.go b/syncs/shardvalue_test.go index 8f6ac6414dee7..1dd0a542e60c2 100644 --- a/syncs/shardvalue_test.go +++ b/syncs/shardvalue_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/syncs.go b/syncs/syncs.go index 3b37bca085c89..d447b2e7bc4e0 100644 --- a/syncs/syncs.go +++ b/syncs/syncs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package syncs contains additional sync types and functionality. diff --git a/syncs/syncs_test.go b/syncs/syncs_test.go index a546b8d0a2343..81fcccbf63aca 100644 --- a/syncs/syncs_test.go +++ b/syncs/syncs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/tailcfg/c2ntypes.go b/tailcfg/c2ntypes.go index d78baef1c29a4..d3f5755e81ba1 100644 --- a/tailcfg/c2ntypes.go +++ b/tailcfg/c2ntypes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // c2n (control-to-node) API types. diff --git a/tailcfg/derpmap.go b/tailcfg/derpmap.go index e05559f3ed7f1..c18b04ea11342 100644 --- a/tailcfg/derpmap.go +++ b/tailcfg/derpmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg diff --git a/tailcfg/proto_port_range.go b/tailcfg/proto_port_range.go index 03505dbd131e7..63012e93b2b8e 100644 --- a/tailcfg/proto_port_range.go +++ b/tailcfg/proto_port_range.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg diff --git a/tailcfg/proto_port_range_test.go b/tailcfg/proto_port_range_test.go index 59ccc9be4a1a8..c0c5ff5d5cb76 100644 --- a/tailcfg/proto_port_range_test.go +++ b/tailcfg/proto_port_range_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 8468aa09efb3e..b49791be6fb39 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tailcfg contains types used by the Tailscale protocol with between @@ -178,7 +178,9 @@ type CapabilityVersion int // - 129: 2025-10-04: Fixed sleep/wake deadlock in magicsock when using peer relay (PR #17449) // - 130: 2025-10-06: client can send key.HardwareAttestationPublic and key.HardwareAttestationKeySignature in MapRequest // - 131: 2025-11-25: client respects [NodeAttrDefaultAutoUpdate] -const CurrentCapabilityVersion CapabilityVersion = 131 +// - 132: 2026-02-13: client respects [NodeAttrDisableHostsFileUpdates] +// - 133: 2026-02-17: client understands [NodeAttrForceRegisterMagicDNSIPv4Only]; MagicDNS IPv6 registered w/ OS by default +const CurrentCapabilityVersion CapabilityVersion = 133 // ID is an integer ID for a user, node, or login allocated by the // control plane. @@ -887,6 +889,7 @@ type Hostinfo struct { UserspaceRouter opt.Bool `json:",omitzero"` // if the client's subnet router is running in userspace (netstack) mode AppConnector opt.Bool `json:",omitzero"` // if the client is running the app-connector service ServicesHash string `json:",omitzero"` // opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n + PeerRelay bool `json:",omitzero"` // if the client is willing to relay traffic for other peers ExitNodeID StableNodeID `json:",omitzero"` // the client’s selected exit node, empty when unselected. // Location represents geographical location data about a @@ -2707,6 +2710,13 @@ const ( // server to answer AAAA queries about its peers. See tailscale/tailscale#1152. NodeAttrMagicDNSPeerAAAA NodeCapability = "magicdns-aaaa" + // NodeAttrDNSSubdomainResolve, when set on Self or a Peer node, indicates + // that the subdomains of that node's MagicDNS name should resolve to the + // same IP addresses as the node itself. + // For example, if node "myserver.tailnet.ts.net" has this capability, + // then "anything.myserver.tailnet.ts.net" will resolve to myserver's IPs. + NodeAttrDNSSubdomainResolve NodeCapability = "dns-subdomain-resolve" + // NodeAttrTrafficSteering configures the node to use the traffic // steering subsystem for via routes. See tailscale/corp#29966. NodeAttrTrafficSteering NodeCapability = "traffic-steering" @@ -2732,6 +2742,19 @@ const ( // // The value of the key in [NodeCapMap] is a JSON boolean. NodeAttrDefaultAutoUpdate NodeCapability = "default-auto-update" + + // NodeAttrDisableHostsFileUpdates indicates that the node's DNS manager should + // not create hosts file entries when it normally would, such as when we're not + // the primary resolver on Windows or when the host is domain-joined and its + // primary domain takes precedence over MagicDNS. As of 2026-02-12, it is only + // used on Windows. + NodeAttrDisableHostsFileUpdates NodeCapability = "disable-hosts-file-updates" + + // NodeAttrForceRegisterMagicDNSIPv4Only forces the client to only register + // its MagicDNS IPv4 address with systemd/etc, and not both its IPv4 and IPv6 addresses. + // See https://github.com/tailscale/tailscale/issues/15404. + // TODO(bradfitz): remove this a few releases after 2026-02-16. + NodeAttrForceRegisterMagicDNSIPv4Only NodeCapability = "force-register-magicdns-ipv4-only" ) // SetDNSRequest is a request to add a DNS record. diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index 751b7c288f274..a60f301d763c7 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. @@ -186,6 +186,7 @@ var _HostinfoCloneNeedsRegeneration = Hostinfo(struct { UserspaceRouter opt.Bool AppConnector opt.Bool ServicesHash string + PeerRelay bool ExitNodeID StableNodeID Location *Location TPM *TPMInfo diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 6691263eb997a..f649e43ab57b8 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg_test @@ -67,6 +67,7 @@ func TestHostinfoEqual(t *testing.T) { "UserspaceRouter", "AppConnector", "ServicesHash", + "PeerRelay", "ExitNodeID", "Location", "TPM", @@ -244,6 +245,16 @@ func TestHostinfoEqual(t *testing.T) { &Hostinfo{AppConnector: opt.Bool("false")}, false, }, + { + &Hostinfo{PeerRelay: true}, + &Hostinfo{PeerRelay: true}, + true, + }, + { + &Hostinfo{PeerRelay: true}, + &Hostinfo{PeerRelay: false}, + false, + }, { &Hostinfo{ServicesHash: "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"}, &Hostinfo{ServicesHash: "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"}, diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go index dbd29a87a354e..7960000fd3d6a 100644 --- a/tailcfg/tailcfg_view.go +++ b/tailcfg/tailcfg_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. @@ -606,6 +606,9 @@ func (v HostinfoView) AppConnector() opt.Bool { return v.Đļ.AppConnector } // opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n func (v HostinfoView) ServicesHash() string { return v.Đļ.ServicesHash } +// if the client is willing to relay traffic for other peers +func (v HostinfoView) PeerRelay() bool { return v.Đļ.PeerRelay } + // the client’s selected exit node, empty when unselected. func (v HostinfoView) ExitNodeID() StableNodeID { return v.Đļ.ExitNodeID } @@ -664,6 +667,7 @@ var _HostinfoViewNeedsRegeneration = Hostinfo(struct { UserspaceRouter opt.Bool AppConnector opt.Bool ServicesHash string + PeerRelay bool ExitNodeID StableNodeID Location *Location TPM *TPMInfo diff --git a/tailcfg/tka.go b/tailcfg/tka.go index 97fdcc0db687a..29c17b7567198 100644 --- a/tailcfg/tka.go +++ b/tailcfg/tka.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg diff --git a/tka/aum.go b/tka/aum.go index b8c4b6c9e14d4..44d289906566c 100644 --- a/tka/aum.go +++ b/tka/aum.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/aum_test.go b/tka/aum_test.go index 833a026544f54..4f32e91a1964f 100644 --- a/tka/aum_test.go +++ b/tka/aum_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/builder.go b/tka/builder.go index ab2364d856ee2..1e7b130151876 100644 --- a/tka/builder.go +++ b/tka/builder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/builder_test.go b/tka/builder_test.go index 3fd32f64eac12..edca1e95a516e 100644 --- a/tka/builder_test.go +++ b/tka/builder_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/chaintest_test.go b/tka/chaintest_test.go index a3122b5d19da8..c370bf60a2e4c 100644 --- a/tka/chaintest_test.go +++ b/tka/chaintest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/deeplink.go b/tka/deeplink.go index 5570a19d7371b..34f80be034bb0 100644 --- a/tka/deeplink.go +++ b/tka/deeplink.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/deeplink_test.go b/tka/deeplink_test.go index 03523202fed8b..6d85b158589ac 100644 --- a/tka/deeplink_test.go +++ b/tka/deeplink_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/disabled_stub.go b/tka/disabled_stub.go index 4c4afa3706d98..d14473e5ec1ac 100644 --- a/tka/disabled_stub.go +++ b/tka/disabled_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_tailnetlock diff --git a/tka/key.go b/tka/key.go index dca1b4416560b..bc946156eb9be 100644 --- a/tka/key.go +++ b/tka/key.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/key_test.go b/tka/key_test.go index 327de1a0e2851..799accc857e1c 100644 --- a/tka/key_test.go +++ b/tka/key_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/scenario_test.go b/tka/scenario_test.go index a0361a130dcc6..cf4ee2d5b2582 100644 --- a/tka/scenario_test.go +++ b/tka/scenario_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/sig.go b/tka/sig.go index 46d598ad97b47..9d107c98ff64c 100644 --- a/tka/sig.go +++ b/tka/sig.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/sig_test.go b/tka/sig_test.go index c5c03ef2e0055..efec62b7d791f 100644 --- a/tka/sig_test.go +++ b/tka/sig_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/state.go b/tka/state.go index 95a319bd9bd7d..06fdc65048b59 100644 --- a/tka/state.go +++ b/tka/state.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/state_test.go b/tka/state_test.go index 32b6563145ee7..337e3c3ceff85 100644 --- a/tka/state_test.go +++ b/tka/state_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/sync.go b/tka/sync.go index 2dbfb7ac435b2..27e1c0e633329 100644 --- a/tka/sync.go +++ b/tka/sync.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/sync_test.go b/tka/sync_test.go index ea14a37e57e9b..158f73c46cb01 100644 --- a/tka/sync_test.go +++ b/tka/sync_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/tailchonk.go b/tka/tailchonk.go index 13bdf6aac86d4..256faaea2b8b9 100644 --- a/tka/tailchonk.go +++ b/tka/tailchonk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/tailchonk_test.go b/tka/tailchonk_test.go index eeb6edfff3018..d40e4b09da769 100644 --- a/tka/tailchonk_test.go +++ b/tka/tailchonk_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/tka.go b/tka/tka.go index ed029c82e0592..e3862c29d3264 100644 --- a/tka/tka.go +++ b/tka/tka.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/tka_clone.go b/tka/tka_clone.go index 323a824fe5a63..9c7a6eeb3350d 100644 --- a/tka/tka_clone.go +++ b/tka/tka_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/tka/tka_test.go b/tka/tka_test.go index cc9ea57ee2f6a..f2ce73d357343 100644 --- a/tka/tka_test.go +++ b/tka/tka_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/verify.go b/tka/verify.go index ed0ecea669817..1ef4fbbb19308 100644 --- a/tka/verify.go +++ b/tka/verify.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/verify_disabled.go b/tka/verify_disabled.go index ba72f93e27d8f..a4b3136d2ffea 100644 --- a/tka/verify_disabled.go +++ b/tka/verify_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_tailnetlock diff --git a/tool/gocross/autoflags.go b/tool/gocross/autoflags.go index b28d3bc5dd26e..405cad8b3b68e 100644 --- a/tool/gocross/autoflags.go +++ b/tool/gocross/autoflags.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/autoflags_test.go b/tool/gocross/autoflags_test.go index a0f3edfd2bb68..7363e452ed635 100644 --- a/tool/gocross/autoflags_test.go +++ b/tool/gocross/autoflags_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/env.go b/tool/gocross/env.go index 9d8a4f1b390b4..6b22f9365d255 100644 --- a/tool/gocross/env.go +++ b/tool/gocross/env.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/env_test.go b/tool/gocross/env_test.go index 001487bb8e1a6..39af579eb15f1 100644 --- a/tool/gocross/env_test.go +++ b/tool/gocross/env_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/exec_other.go b/tool/gocross/exec_other.go index 4dd74f84d7d2b..20e52aa8f9496 100644 --- a/tool/gocross/exec_other.go +++ b/tool/gocross/exec_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !unix diff --git a/tool/gocross/exec_unix.go b/tool/gocross/exec_unix.go index 79cbf764ad2f6..2d9fd72ba046b 100644 --- a/tool/gocross/exec_unix.go +++ b/tool/gocross/exec_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/tool/gocross/gocross-wrapper.ps1 b/tool/gocross/gocross-wrapper.ps1 index fe0b46996204d..23bd6eb2771dd 100644 --- a/tool/gocross/gocross-wrapper.ps1 +++ b/tool/gocross/gocross-wrapper.ps1 @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause #Requires -Version 7.4 @@ -114,7 +114,12 @@ $bootstrapScriptBlock = { New-Item -Force -Path $toolchain -ItemType Directory | Out-Null Start-ChildScope -ScriptBlock { Set-Location -LiteralPath $toolchain - tar --strip-components=1 -xf "$toolchain.tar.gz" + + # Using an absolute path to the tar that ships with Windows + # to avoid conflicts with others (eg msys2). + $system32 = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::System) + $tar = Join-Path $system32 'tar.exe' -Resolve + & $tar --strip-components=1 -xf "$toolchain.tar.gz" if ($LASTEXITCODE -ne 0) { throw "tar failed with exit code $LASTEXITCODE" } @@ -185,7 +190,8 @@ $bootstrapScriptBlock = { $goBuildEnv['GOROOT'] = $null $procExe = Join-Path $toolchain 'bin' 'go.exe' -Resolve - $proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $goBuildEnv -ArgumentList 'build', '-o', $gocrossPath, "-ldflags=-X=tailscale.com/version.gitCommitStamp=$wantVer", 'tailscale.com/tool/gocross' -NoNewWindow -Wait -PassThru + $proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $goBuildEnv -ArgumentList 'build', '-o', $gocrossPath, "-ldflags=-X=tailscale.com/version.gitCommitStamp=$wantVer", 'tailscale.com/tool/gocross' -NoNewWindow -PassThru + $proc.WaitForExit() if ($proc.ExitCode -ne 0) { throw 'error building gocross' } @@ -217,10 +223,12 @@ if ($Env:TS_USE_GOCROSS -ne '1') { } $procExe = Join-Path $toolchain 'bin' 'go.exe' -Resolve - $proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -Wait -PassThru + $proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -PassThru + $proc.WaitForExit() exit $proc.ExitCode } $procExe = Join-Path $repoRoot 'gocross.exe' -Resolve -$proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -Wait -PassThru +$proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -PassThru +$proc.WaitForExit() exit $proc.ExitCode diff --git a/tool/gocross/gocross-wrapper.sh b/tool/gocross/gocross-wrapper.sh index d93b137aab6f5..05a35ba424cc2 100755 --- a/tool/gocross/gocross-wrapper.sh +++ b/tool/gocross/gocross-wrapper.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # # gocross-wrapper.sh is a wrapper that can be aliased to 'go', which # transparently runs the version of github.com/tailscale/go as specified repo's -# go.toolchain.rev file. +# go.toolchain.rev file (or go.toolchain.next.rev if TS_GO_NEXT=1). # # It also conditionally (if TS_USE_GOCROSS=1) builds gocross and uses it as a go # wrapper to inject certain go flags. @@ -21,6 +21,12 @@ if [[ "${OSTYPE:-}" == "cygwin" || "${OSTYPE:-}" == "msys" ]]; then exit fi +if [[ "${TS_GO_NEXT:-}" == "1" ]]; then + go_toolchain_rev_file="go.toolchain.next.rev" +else + go_toolchain_rev_file="go.toolchain.rev" +fi + # Locate a bootstrap toolchain and (re)build gocross if necessary. We run all of # this in a subshell because posix shell semantics make it very easy to # accidentally mutate the input environment that will get passed to gocross at @@ -45,7 +51,7 @@ cd "$repo_root" # https://github.com/tailscale/go release artifact to download. toolchain="" -read -r REV 0 && (matches-1)%m != n-1 { continue // not in this shard @@ -112,6 +133,62 @@ Pkg: } } +// computeAffected returns the set of package paths whose test binaries would +// differ with vs without the given build tag. It finds packages that directly +// mention the tag, then propagates transitively via reverse dependencies. +func computeAffected(pkgs []*packages.Package, tag string) map[string]bool { + // Build a map from package ID to package for quick lookup. + byID := make(map[string]*packages.Package, len(pkgs)) + for _, pkg := range pkgs { + byID[pkg.ID] = pkg + } + + // First pass: find directly affected package IDs. + directlyAffected := make(map[string]bool) + for _, pkg := range pkgs { + if hasBuildTag(pkg, tag) { + directlyAffected[pkg.ID] = true + } + } + + // Build reverse dependency graph: importedID → []importingID. + reverseDeps := make(map[string][]string) + for _, pkg := range pkgs { + for _, imp := range pkg.Imports { + reverseDeps[imp.ID] = append(reverseDeps[imp.ID], pkg.ID) + } + } + + // BFS from directly affected packages through reverse deps. + affectedIDs := make(map[string]bool) + queue := make([]string, 0, len(directlyAffected)) + for id := range directlyAffected { + affectedIDs[id] = true + queue = append(queue, id) + } + for len(queue) > 0 { + id := queue[0] + queue = queue[1:] + for _, rdep := range reverseDeps[id] { + if !affectedIDs[rdep] { + affectedIDs[rdep] = true + queue = append(queue, rdep) + } + } + } + + // Map affected IDs back to PkgPaths. For-test variants like + // "foo [foo.test]" share the same PkgPath as "foo", so the + // result naturally deduplicates. + affected := make(map[string]bool) + for id := range affectedIDs { + if pkg, ok := byID[id]; ok { + affected[pkg.PkgPath] = true + } + } + return affected +} + func isThirdParty(pkg string) bool { return strings.HasPrefix(pkg, "tailscale.com/tempfork/") } @@ -194,7 +271,7 @@ func getFileTags(filename string) (tagSet, error) { mu.Lock() defer mu.Unlock() fileTags[filename] = ts - return tags, nil + return ts, nil } func fileMentionsTag(filename, tag string) (bool, error) { diff --git a/tsconsensus/authorization.go b/tsconsensus/authorization.go index bd8e2f39a014b..017c9e80721b9 100644 --- a/tsconsensus/authorization.go +++ b/tsconsensus/authorization.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus @@ -17,6 +17,10 @@ import ( "tailscale.com/util/set" ) +// defaultStatusCacheTimeout is the duration after which cached status will be +// disregarded. See tailscaleStatusGetter.cacheTimeout. +const defaultStatusCacheTimeout = time.Second + type statusGetter interface { getStatus(context.Context) (*ipnstate.Status, error) } @@ -24,6 +28,10 @@ type statusGetter interface { type tailscaleStatusGetter struct { ts *tsnet.Server + // cacheTimeout is used to determine when the cached status should be + // disregarded and a new status fetched. Zero means ignore the cache. + cacheTimeout time.Duration + mu sync.Mutex // protects the following lastStatus *ipnstate.Status lastStatusTime time.Time @@ -40,7 +48,7 @@ func (sg *tailscaleStatusGetter) fetchStatus(ctx context.Context) (*ipnstate.Sta func (sg *tailscaleStatusGetter) getStatus(ctx context.Context) (*ipnstate.Status, error) { sg.mu.Lock() defer sg.mu.Unlock() - if sg.lastStatus != nil && time.Since(sg.lastStatusTime) < 1*time.Second { + if sg.lastStatus != nil && time.Since(sg.lastStatusTime) < sg.cacheTimeout { return sg.lastStatus, nil } status, err := sg.fetchStatus(ctx) @@ -61,14 +69,23 @@ type authorization struct { } func newAuthorization(ts *tsnet.Server, tag string) *authorization { + return newAuthorizationWithCacheTimeout(ts, tag, defaultStatusCacheTimeout) +} + +func newAuthorizationWithCacheTimeout(ts *tsnet.Server, tag string, cacheTimeout time.Duration) *authorization { return &authorization{ sg: &tailscaleStatusGetter{ - ts: ts, + ts: ts, + cacheTimeout: cacheTimeout, }, tag: tag, } } +func newAuthorizationForTest(ts *tsnet.Server, tag string) *authorization { + return newAuthorizationWithCacheTimeout(ts, tag, 0) +} + func (a *authorization) Refresh(ctx context.Context) error { tStatus, err := a.sg.getStatus(ctx) if err != nil { diff --git a/tsconsensus/authorization_test.go b/tsconsensus/authorization_test.go index e0023f4ff24d2..0f7a4e5958595 100644 --- a/tsconsensus/authorization_test.go +++ b/tsconsensus/authorization_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus diff --git a/tsconsensus/bolt_store.go b/tsconsensus/bolt_store.go index ca347cfc049b2..e8dbb5a227505 100644 --- a/tsconsensus/bolt_store.go +++ b/tsconsensus/bolt_store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !loong64 diff --git a/tsconsensus/bolt_store_no_bolt.go b/tsconsensus/bolt_store_no_bolt.go index 33b3bd6c7a29f..f799cc5938d10 100644 --- a/tsconsensus/bolt_store_no_bolt.go +++ b/tsconsensus/bolt_store_no_bolt.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build loong64 diff --git a/tsconsensus/http.go b/tsconsensus/http.go index d2a44015f8f68..a7e3af35d94f6 100644 --- a/tsconsensus/http.go +++ b/tsconsensus/http.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus diff --git a/tsconsensus/monitor.go b/tsconsensus/monitor.go index c84e83454f3f7..cc5ac812c49d9 100644 --- a/tsconsensus/monitor.go +++ b/tsconsensus/monitor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus diff --git a/tsconsensus/tsconsensus.go b/tsconsensus/tsconsensus.go index 1f7dc1b7b6a5e..27cbf964e7207 100644 --- a/tsconsensus/tsconsensus.go +++ b/tsconsensus/tsconsensus.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsconsensus implements a consensus algorithm for a group of tsnet.Servers diff --git a/tsconsensus/tsconsensus_test.go b/tsconsensus/tsconsensus_test.go index 796c8f51b76a9..8897db119c467 100644 --- a/tsconsensus/tsconsensus_test.go +++ b/tsconsensus/tsconsensus_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus @@ -642,7 +642,7 @@ func TestOnlyTaggedPeersCanBeDialed(t *testing.T) { // make a StreamLayer for ps[0] ts := ps[0].ts - auth := newAuthorization(ts, clusterTag) + auth := newAuthorizationForTest(ts, clusterTag) port := 19841 lns := make([]net.Listener, 3) @@ -692,10 +692,12 @@ func TestOnlyTaggedPeersCanBeDialed(t *testing.T) { conn.Close() _, err = sl.Dial(a2, 2*time.Second) + if err == nil { + t.Fatal("expected dial error to untagged node, got none") + } if err.Error() != "dial: peer is not allowed" { t.Fatalf("expected dial: peer is not allowed, got: %v", err) } - } func TestOnlyTaggedPeersCanJoin(t *testing.T) { diff --git a/tsconst/health.go b/tsconst/health.go index 5db9b1fc286ec..93c6550efaba4 100644 --- a/tsconst/health.go +++ b/tsconst/health.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconst diff --git a/tsconst/linuxfw.go b/tsconst/linuxfw.go index ce571e40239ed..3a7a4cf2e0b5e 100644 --- a/tsconst/linuxfw.go +++ b/tsconst/linuxfw.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconst diff --git a/tsconst/tsconst.go b/tsconst/tsconst.go index d17aa356d25fe..85f05e54905f9 100644 --- a/tsconst/tsconst.go +++ b/tsconst/tsconst.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsconst exports some constants used elsewhere in the diff --git a/tsconst/webclient.go b/tsconst/webclient.go index d4b3c8db51b2a..705931159d24f 100644 --- a/tsconst/webclient.go +++ b/tsconst/webclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconst diff --git a/tsd/tsd.go b/tsd/tsd.go index 8dc0c14278864..9d79334d68e2b 100644 --- a/tsd/tsd.go +++ b/tsd/tsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsd (short for "Tailscale Daemon") contains a System type that @@ -32,6 +32,7 @@ import ( "tailscale.com/net/tstun" "tailscale.com/proxymap" "tailscale.com/types/netmap" + "tailscale.com/types/views" "tailscale.com/util/eventbus" "tailscale.com/util/syspolicy/policyclient" "tailscale.com/util/usermetric" @@ -111,6 +112,8 @@ type LocalBackend = any type NetstackImpl interface { Start(LocalBackend) error UpdateNetstackIPs(*netmap.NetworkMap) + UpdateIPServiceMappings(netmap.IPServiceMappings) + UpdateActiveVIPServices(views.Slice[string]) } // Set is a convenience method to set a subsystem value. diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index 5b08200c97f6d..cb6b6996b7a87 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) + đŸ’Ŗ crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 W đŸ’Ŗ github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ @@ -88,8 +89,13 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) W đŸ’Ŗ github.com/dblohm7/wingoes/pe from tailscale.com/util/osdiag+ github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ @@ -98,7 +104,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+ L đŸ’Ŗ github.com/godbus/dbus/v5 from tailscale.com/net/dns github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ + github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/transport/tcp DI github.com/google/uuid from github.com/prometheus-community/pro-bing github.com/hdevalence/ed25519consensus from tailscale.com/tka github.com/huin/goupnp from github.com/huin/goupnp/dcps/internetgateway2+ @@ -161,7 +167,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) đŸ’Ŗ gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+ gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state đŸ’Ŗ gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+ - đŸ’Ŗ gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack + đŸ’Ŗ gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack+ gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack đŸ’Ŗ gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+ @@ -223,7 +229,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) tailscale.com/feature/syspolicy from tailscale.com/logpolicy tailscale.com/feature/useproxy from tailscale.com/feature/condregister/useproxy tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/internal/client/tailscale from tailscale.com/tsnet+ tailscale.com/ipn from tailscale.com/client/local+ @@ -231,6 +237,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) đŸ’Ŗ tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnstate from tailscale.com/client/local+ tailscale.com/ipn/localapi from tailscale.com/tsnet tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+ @@ -435,7 +442,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -460,7 +467,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509+ @@ -469,12 +476,13 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ crypto/fips140 from crypto/tls/internal/fips140tls+ - crypto/hkdf from crypto/internal/hpke+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -489,7 +497,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls+ + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -503,20 +511,21 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ - LD crypto/mlkem from golang.org/x/crypto/ssh + crypto/mlkem from golang.org/x/crypto/ssh+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls+ crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from github.com/prometheus-community/pro-bing+ @@ -560,9 +569,8 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -575,14 +583,17 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) internal/runtime/atomic from internal/runtime/exithook+ LA internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - LA internal/runtime/syscall from runtime+ + LA internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -625,7 +636,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) os/user from github.com/godbus/dbus/v5+ path from debug/dwarf+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from database/sql/driver+ regexp from github.com/huin/goupnp/httpu+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/tsnet/example/tshello/tshello.go b/tsnet/example/tshello/tshello.go index 0cadcdd837d99..d45d209dc7abc 100644 --- a/tsnet/example/tshello/tshello.go +++ b/tsnet/example/tshello/tshello.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tshello server demonstrates how to use Tailscale as a library. diff --git a/tsnet/example/tsnet-funnel/tsnet-funnel.go b/tsnet/example/tsnet-funnel/tsnet-funnel.go index 1dac57a1ebf86..27c3e1e5cdf2e 100644 --- a/tsnet/example/tsnet-funnel/tsnet-funnel.go +++ b/tsnet/example/tsnet-funnel/tsnet-funnel.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tsnet-funnel server demonstrates how to use tsnet with Funnel. diff --git a/tsnet/example/tsnet-http-client/tsnet-http-client.go b/tsnet/example/tsnet-http-client/tsnet-http-client.go index 9666fe9992745..e61c512a0b085 100644 --- a/tsnet/example/tsnet-http-client/tsnet-http-client.go +++ b/tsnet/example/tsnet-http-client/tsnet-http-client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tshello server demonstrates how to use Tailscale as a library. diff --git a/tsnet/example/web-client/web-client.go b/tsnet/example/web-client/web-client.go index 541efbaedf3d3..e64eb47e6e14f 100644 --- a/tsnet/example/web-client/web-client.go +++ b/tsnet/example/web-client/web-client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The web-client command demonstrates serving the Tailscale web client over tsnet. diff --git a/tsnet/example_tshello_test.go b/tsnet/example_tshello_test.go index d534bcfd1f1d4..62b6737fd5245 100644 --- a/tsnet/example_tshello_test.go +++ b/tsnet/example_tshello_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsnet_test diff --git a/tsnet/packet_filter_test.go b/tsnet/packet_filter_test.go index 455400eaa0c8a..ca776436e7085 100644 --- a/tsnet/packet_filter_test.go +++ b/tsnet/packet_filter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsnet diff --git a/tstest/allocs.go b/tstest/allocs.go index f15a00508d87f..6c2a1a22bec6a 100644 --- a/tstest/allocs.go +++ b/tstest/allocs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/archtest/archtest_test.go b/tstest/archtest/archtest_test.go index 1aeca5c109073..1523baf7b2044 100644 --- a/tstest/archtest/archtest_test.go +++ b/tstest/archtest/archtest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package archtest diff --git a/tstest/archtest/qemu_test.go b/tstest/archtest/qemu_test.go index 68ec38851069e..400f8bc4f9ea0 100644 --- a/tstest/archtest/qemu_test.go +++ b/tstest/archtest/qemu_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && amd64 && !race diff --git a/tstest/chonktest/chonktest.go b/tstest/chonktest/chonktest.go index 404f1ec47f16c..b0b32e6151c82 100644 --- a/tstest/chonktest/chonktest.go +++ b/tstest/chonktest/chonktest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package chonktest contains a shared set of tests for the Chonk diff --git a/tstest/chonktest/tailchonk_test.go b/tstest/chonktest/tailchonk_test.go index d9343e9160ea9..99b57f54f5900 100644 --- a/tstest/chonktest/tailchonk_test.go +++ b/tstest/chonktest/tailchonk_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package chonktest diff --git a/tstest/clock.go b/tstest/clock.go index ee7523430ff54..5742c6e5aeda1 100644 --- a/tstest/clock.go +++ b/tstest/clock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest @@ -17,8 +17,11 @@ import ( type ClockOpts struct { // Start is the starting time for the Clock. When FollowRealTime is false, // Start is also the value that will be returned by the first call - // to Clock.Now. + // to Clock.Now. If you are passing a value here, set an explicit + // timezone, otherwise the test may be non-deterministic when TZ environment + // variable is set to different values. The default time is in UTC. Start time.Time + // Step is the amount of time the Clock will advance whenever Clock.Now is // called. If set to zero, the Clock will only advance when Clock.Advance is // called and/or if FollowRealTime is true. @@ -119,7 +122,7 @@ func (c *Clock) init() { } if c.start.IsZero() { if c.realTime.IsZero() { - c.start = time.Now() + c.start = time.Now().UTC() } else { c.start = c.realTime } diff --git a/tstest/clock_test.go b/tstest/clock_test.go index 2ebaf752a1963..cdfc2319ac115 100644 --- a/tstest/clock_test.go +++ b/tstest/clock_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/deptest/deptest.go b/tstest/deptest/deptest.go index c0b6d8b8cffb5..3117af2fffa01 100644 --- a/tstest/deptest/deptest.go +++ b/tstest/deptest/deptest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The deptest package contains a shared implementation of negative diff --git a/tstest/deptest/deptest_test.go b/tstest/deptest/deptest_test.go index ebafa56849efb..1b83d46d3cc31 100644 --- a/tstest/deptest/deptest_test.go +++ b/tstest/deptest/deptest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deptest diff --git a/tstest/integration/capmap_test.go b/tstest/integration/capmap_test.go index 0ee05be2f57d7..aea4a210b44e1 100644 --- a/tstest/integration/capmap_test.go +++ b/tstest/integration/capmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package integration diff --git a/tstest/integration/gen_deps.go b/tstest/integration/gen_deps.go index 23bb95ee56a9f..7e668266bbb78 100644 --- a/tstest/integration/gen_deps.go +++ b/tstest/integration/gen_deps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore @@ -35,7 +35,7 @@ func generate(goos string) { log.Fatal(err) } var out bytes.Buffer - out.WriteString(`// Copyright (c) Tailscale Inc & AUTHORS + out.WriteString(`// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/integration.go b/tstest/integration/integration.go index a62173ae3e353..a98df81808097 100644 --- a/tstest/integration/integration.go +++ b/tstest/integration/integration.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package integration contains Tailscale integration tests. diff --git a/tstest/integration/integration_test.go b/tstest/integration/integration_test.go index fc891ad722b28..779cba6290cfe 100644 --- a/tstest/integration/integration_test.go +++ b/tstest/integration/integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package integration diff --git a/tstest/integration/nat/nat_test.go b/tstest/integration/nat/nat_test.go index 15f1269858947..2322e243a8ee9 100644 --- a/tstest/integration/nat/nat_test.go +++ b/tstest/integration/nat/nat_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package nat @@ -81,7 +81,7 @@ func newNatTest(tb testing.TB) *natTest { } } - nt.kernel, err = findKernelPath(filepath.Join(modRoot, "gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod")) + nt.kernel, err = findKernelPath(filepath.Join(modRoot, "go.mod")) if err != nil { tb.Skipf("skipping test; kernel not found: %v", err) } @@ -415,7 +415,7 @@ func (nt *natTest) runTest(addNode ...addNodeFunc) pingRoute { return "" } - pingRes, err := ping(ctx, clients[0], sts[1].Self.TailscaleIPs[0]) + pingRes, err := ping(ctx, t, clients[0], sts[1].Self.TailscaleIPs[0]) if err != nil { t.Fatalf("ping failure: %v", err) } @@ -450,35 +450,38 @@ const ( routeNil pingRoute = "nil" // *ipnstate.PingResult is nil ) -func ping(ctx context.Context, c *vnet.NodeAgentClient, target netip.Addr) (*ipnstate.PingResult, error) { - n := 0 - var res *ipnstate.PingResult - anyPong := false - for n < 10 { - n++ - pr, err := c.PingWithOpts(ctx, target, tailcfg.PingDisco, tailscale.PingOpts{}) +func ping(ctx context.Context, t testing.TB, c *vnet.NodeAgentClient, target netip.Addr) (*ipnstate.PingResult, error) { + var lastRes *ipnstate.PingResult + for n := range 10 { + t.Logf("ping attempt %d to %v ...", n+1, target) + pingCtx, cancel := context.WithTimeout(ctx, 2*time.Second) + pr, err := c.PingWithOpts(pingCtx, target, tailcfg.PingDisco, tailscale.PingOpts{}) + cancel() if err != nil { - if anyPong { - return res, nil + t.Logf("ping attempt %d error: %v", n+1, err) + if ctx.Err() != nil { + break } - return nil, err + continue } if pr.Err != "" { return nil, errors.New(pr.Err) } + t.Logf("ping attempt %d: derp=%d endpoint=%v latency=%v", n+1, pr.DERPRegionID, pr.Endpoint, pr.LatencySeconds) if pr.DERPRegionID == 0 { return pr, nil } - res = pr + lastRes = pr select { case <-ctx.Done(): + return lastRes, nil case <-time.After(time.Second): } } - if res == nil { - return nil, errors.New("no ping response") + if lastRes != nil { + return lastRes, nil } - return res, nil + return nil, fmt.Errorf("no ping response (ctx: %v)", ctx.Err()) } func up(ctx context.Context, c *vnet.NodeAgentClient) error { diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index 9f92839d8cde7..112f04767c89d 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/tstest/integration/tailscaled_deps_test_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go index 9f92839d8cde7..112f04767c89d 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/tstest/integration/tailscaled_deps_test_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go index 9f92839d8cde7..112f04767c89d 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/tstest/integration/tailscaled_deps_test_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go index 9f92839d8cde7..112f04767c89d 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/tstest/integration/tailscaled_deps_test_openbsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index 82f8097c8bc36..cabac744a5c6c 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/vms/derive_bindhost_test.go b/tstest/integration/vms/derive_bindhost_test.go index 728f60c01e465..079308055da3a 100644 --- a/tstest/integration/vms/derive_bindhost_test.go +++ b/tstest/integration/vms/derive_bindhost_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vms diff --git a/tstest/integration/vms/distros.go b/tstest/integration/vms/distros.go index ca2bf53ba66a7..94f11c77aac5d 100644 --- a/tstest/integration/vms/distros.go +++ b/tstest/integration/vms/distros.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vms diff --git a/tstest/integration/vms/distros_test.go b/tstest/integration/vms/distros_test.go index 462aa2a6bc825..8cc15aa7297fc 100644 --- a/tstest/integration/vms/distros_test.go +++ b/tstest/integration/vms/distros_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vms diff --git a/tstest/integration/vms/dns_tester.go b/tstest/integration/vms/dns_tester.go index 50b39bb5f1fa1..8a0ca5afaf366 100644 --- a/tstest/integration/vms/dns_tester.go +++ b/tstest/integration/vms/dns_tester.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/tstest/integration/vms/doc.go b/tstest/integration/vms/doc.go index 6093b53ac8ed5..0c9eced92d686 100644 --- a/tstest/integration/vms/doc.go +++ b/tstest/integration/vms/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package vms does VM-based integration/functional tests by using diff --git a/tstest/integration/vms/harness_test.go b/tstest/integration/vms/harness_test.go index 256227d6c64cc..ccff6e81e0f33 100644 --- a/tstest/integration/vms/harness_test.go +++ b/tstest/integration/vms/harness_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/nixos_test.go b/tstest/integration/vms/nixos_test.go index 02b040fedfaff..7d7a104363761 100644 --- a/tstest/integration/vms/nixos_test.go +++ b/tstest/integration/vms/nixos_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/top_level_test.go b/tstest/integration/vms/top_level_test.go index 5db237b6e33b7..849abfd2469ed 100644 --- a/tstest/integration/vms/top_level_test.go +++ b/tstest/integration/vms/top_level_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/udp_tester.go b/tstest/integration/vms/udp_tester.go index be44aa9636103..46bc1261f1765 100644 --- a/tstest/integration/vms/udp_tester.go +++ b/tstest/integration/vms/udp_tester.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/tstest/integration/vms/vm_setup_test.go b/tstest/integration/vms/vm_setup_test.go index 0c6901014bb74..690c89dcf487b 100644 --- a/tstest/integration/vms/vm_setup_test.go +++ b/tstest/integration/vms/vm_setup_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/vms_steps_test.go b/tstest/integration/vms/vms_steps_test.go index 94e4114f01e78..940c92ddac63c 100644 --- a/tstest/integration/vms/vms_steps_test.go +++ b/tstest/integration/vms/vms_steps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/vms_test.go b/tstest/integration/vms/vms_test.go index c3a3775de9407..5ebb12b71032b 100644 --- a/tstest/integration/vms/vms_test.go +++ b/tstest/integration/vms/vms_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/iosdeps/iosdeps.go b/tstest/iosdeps/iosdeps.go index f414f53dfd0b6..f6290af676e97 100644 --- a/tstest/iosdeps/iosdeps.go +++ b/tstest/iosdeps/iosdeps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package iosdeps is a just a list of the packages we import on iOS, to let us diff --git a/tstest/iosdeps/iosdeps_test.go b/tstest/iosdeps/iosdeps_test.go index b533724eb4b3d..870088e38db9a 100644 --- a/tstest/iosdeps/iosdeps_test.go +++ b/tstest/iosdeps/iosdeps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package iosdeps diff --git a/tstest/jsdeps/jsdeps.go b/tstest/jsdeps/jsdeps.go index 1d188152f73b1..964ca51e10fb6 100644 --- a/tstest/jsdeps/jsdeps.go +++ b/tstest/jsdeps/jsdeps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsdeps is a just a list of the packages we import in the diff --git a/tstest/jsdeps/jsdeps_test.go b/tstest/jsdeps/jsdeps_test.go index 27570fc2676b0..ba6dad6badf3d 100644 --- a/tstest/jsdeps/jsdeps_test.go +++ b/tstest/jsdeps/jsdeps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsdeps diff --git a/tstest/kernel_linux.go b/tstest/kernel_linux.go index 664fe9bdd7b9f..ab7c0d529fc13 100644 --- a/tstest/kernel_linux.go +++ b/tstest/kernel_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/tstest/kernel_other.go b/tstest/kernel_other.go index bf69be6df4b27..3dfc3c239576f 100644 --- a/tstest/kernel_other.go +++ b/tstest/kernel_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/tstest/log.go b/tstest/log.go index d081c819d8ce2..73e973d238d66 100644 --- a/tstest/log.go +++ b/tstest/log.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/log_test.go b/tstest/log_test.go index 51a5743c2c7f2..34aab000d4ad3 100644 --- a/tstest/log_test.go +++ b/tstest/log_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/mts/mts.go b/tstest/mts/mts.go index c10d69d8daca4..c91e0ce996f44 100644 --- a/tstest/mts/mts.go +++ b/tstest/mts/mts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || darwin diff --git a/tstest/natlab/firewall.go b/tstest/natlab/firewall.go index c427d6692a29c..e9192cfbd8ab7 100644 --- a/tstest/natlab/firewall.go +++ b/tstest/natlab/firewall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package natlab diff --git a/tstest/natlab/nat.go b/tstest/natlab/nat.go index d756c5bf11833..67e84f44e7b7a 100644 --- a/tstest/natlab/nat.go +++ b/tstest/natlab/nat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package natlab diff --git a/tstest/natlab/natlab.go b/tstest/natlab/natlab.go index ffa02eee46e06..add812d8fe6e3 100644 --- a/tstest/natlab/natlab.go +++ b/tstest/natlab/natlab.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package natlab lets us simulate different types of networks all diff --git a/tstest/natlab/natlab_test.go b/tstest/natlab/natlab_test.go index 84388373236be..d604907017a84 100644 --- a/tstest/natlab/natlab_test.go +++ b/tstest/natlab/natlab_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package natlab diff --git a/tstest/natlab/vnet/conf.go b/tstest/natlab/vnet/conf.go index 07b181540838c..3f83e35c09ba3 100644 --- a/tstest/natlab/vnet/conf.go +++ b/tstest/natlab/vnet/conf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/conf_test.go b/tstest/natlab/vnet/conf_test.go index 6566ac8cf4610..5716a503e4007 100644 --- a/tstest/natlab/vnet/conf_test.go +++ b/tstest/natlab/vnet/conf_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/easyaf.go b/tstest/natlab/vnet/easyaf.go index 0901bbdffdd7d..1edc9b3cd16b2 100644 --- a/tstest/natlab/vnet/easyaf.go +++ b/tstest/natlab/vnet/easyaf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/nat.go b/tstest/natlab/vnet/nat.go index ad6f29b3adb58..172e19767b179 100644 --- a/tstest/natlab/vnet/nat.go +++ b/tstest/natlab/vnet/nat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/pcap.go b/tstest/natlab/vnet/pcap.go index 41a443e30b6c5..3a766b3759a52 100644 --- a/tstest/natlab/vnet/pcap.go +++ b/tstest/natlab/vnet/pcap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/vip.go b/tstest/natlab/vnet/vip.go index 190c9e75f1a62..9d7aa56a3d2a0 100644 --- a/tstest/natlab/vnet/vip.go +++ b/tstest/natlab/vnet/vip.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/vnet.go b/tstest/natlab/vnet/vnet.go index 49d47f02937ae..357fe213c8c28 100644 --- a/tstest/natlab/vnet/vnet.go +++ b/tstest/natlab/vnet/vnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package vnet simulates a virtual Internet containing a set of networks with various diff --git a/tstest/natlab/vnet/vnet_test.go b/tstest/natlab/vnet/vnet_test.go index 5ffa2b1049c88..93f208c29ca0a 100644 --- a/tstest/natlab/vnet/vnet_test.go +++ b/tstest/natlab/vnet/vnet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/nettest/nettest.go b/tstest/nettest/nettest.go index c78677dd45c59..cfb0a921904eb 100644 --- a/tstest/nettest/nettest.go +++ b/tstest/nettest/nettest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package nettest contains additional test helpers related to network state @@ -91,8 +91,8 @@ func NewUnstartedHTTPServer(nw netx.Network, handler http.Handler) *httptest.Ser c.Transport = &http.Transport{} } tr := c.Transport.(*http.Transport) - if tr.Dial != nil || tr.DialContext != nil { - panic("unexpected non-nil Dial or DialContext in httptest.Server.Client.Transport") + if tr.Dial != nil { + panic("unexpected non-nil Dial in httptest.Server.Client.Transport") } tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return nw.Dial(ctx, network, addr) diff --git a/tstest/reflect.go b/tstest/reflect.go index 125391349a941..22903e7e9fca2 100644 --- a/tstest/reflect.go +++ b/tstest/reflect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/resource.go b/tstest/resource.go index f50bb3330e846..867925b7ddeb1 100644 --- a/tstest/resource.go +++ b/tstest/resource.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/resource_test.go b/tstest/resource_test.go index 7199ac5d11cbf..ecef91cf60b08 100644 --- a/tstest/resource_test.go +++ b/tstest/resource_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/tailmac/Swift/Common/Config.swift b/tstest/tailmac/Swift/Common/Config.swift index 18b68ae9b9d14..53d7680205a00 100644 --- a/tstest/tailmac/Swift/Common/Config.swift +++ b/tstest/tailmac/Swift/Common/Config.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/Common/Notifications.swift b/tstest/tailmac/Swift/Common/Notifications.swift index de2216e227eb7..b91741a463c8e 100644 --- a/tstest/tailmac/Swift/Common/Notifications.swift +++ b/tstest/tailmac/Swift/Common/Notifications.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/Common/TailMacConfigHelper.swift b/tstest/tailmac/Swift/Common/TailMacConfigHelper.swift index c0961c883fdbb..fc7f2d89dc0e2 100644 --- a/tstest/tailmac/Swift/Common/TailMacConfigHelper.swift +++ b/tstest/tailmac/Swift/Common/TailMacConfigHelper.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/Host/AppDelegate.swift b/tstest/tailmac/Swift/Host/AppDelegate.swift index 63c0192da236e..378a524d13c37 100644 --- a/tstest/tailmac/Swift/Host/AppDelegate.swift +++ b/tstest/tailmac/Swift/Host/AppDelegate.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Cocoa diff --git a/tstest/tailmac/Swift/Host/HostCli.swift b/tstest/tailmac/Swift/Host/HostCli.swift index c31478cc39d45..9c9ae6fa0476e 100644 --- a/tstest/tailmac/Swift/Host/HostCli.swift +++ b/tstest/tailmac/Swift/Host/HostCli.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Cocoa diff --git a/tstest/tailmac/Swift/Host/VMController.swift b/tstest/tailmac/Swift/Host/VMController.swift index fe4a3828b18fe..a19d7222e1e9e 100644 --- a/tstest/tailmac/Swift/Host/VMController.swift +++ b/tstest/tailmac/Swift/Host/VMController.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Cocoa diff --git a/tstest/tailmac/Swift/TailMac/RestoreImage.swift b/tstest/tailmac/Swift/TailMac/RestoreImage.swift index c2b8b3dd6a878..8346cbe26c408 100644 --- a/tstest/tailmac/Swift/TailMac/RestoreImage.swift +++ b/tstest/tailmac/Swift/TailMac/RestoreImage.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/TailMac/TailMac.swift b/tstest/tailmac/Swift/TailMac/TailMac.swift index 84aa5e498a008..3859b9b0b0aeb 100644 --- a/tstest/tailmac/Swift/TailMac/TailMac.swift +++ b/tstest/tailmac/Swift/TailMac/TailMac.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/TailMac/VMInstaller.swift b/tstest/tailmac/Swift/TailMac/VMInstaller.swift index 568b6efc4bfe0..7e90079b596c2 100644 --- a/tstest/tailmac/Swift/TailMac/VMInstaller.swift +++ b/tstest/tailmac/Swift/TailMac/VMInstaller.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/test-wishlist.md b/tstest/test-wishlist.md index eb4601b929650..39b4da6c05453 100644 --- a/tstest/test-wishlist.md +++ b/tstest/test-wishlist.md @@ -18,3 +18,6 @@ reference to an issue or PR about the feature. When the option is disabled, we should still permit it for internal interfaces, such as Hyper-V/WSL2 on Windows. +- Inbound and outbound broadcasts when an exit node is used, both with and without + the "Allow local network access" option enabled. When the option is disabled, + we should still permit traffic on internal interfaces, such as Hyper-V/WSL2 on Windows. \ No newline at end of file diff --git a/tstest/tkatest/tkatest.go b/tstest/tkatest/tkatest.go index fb157a1a19315..2726b4deca249 100644 --- a/tstest/tkatest/tkatest.go +++ b/tstest/tkatest/tkatest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // tkatest has functions for creating a mock control server that responds diff --git a/tstest/tlstest/tlstest.go b/tstest/tlstest/tlstest.go index 76ec0e7e2dfad..3ab08c61fb211 100644 --- a/tstest/tlstest/tlstest.go +++ b/tstest/tlstest/tlstest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tlstest contains code to help test Tailscale's TLS support without diff --git a/tstest/tlstest/tlstest_test.go b/tstest/tlstest/tlstest_test.go index 8497b872ec7c5..7f3583c8af15a 100644 --- a/tstest/tlstest/tlstest_test.go +++ b/tstest/tlstest/tlstest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tlstest diff --git a/tstest/tools/tools.go b/tstest/tools/tools.go index 4d810483b78b5..439acc053250a 100644 --- a/tstest/tools/tools.go +++ b/tstest/tools/tools.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tools diff --git a/tstest/tstest.go b/tstest/tstest.go index d0828f508a46c..4e00fbaa38ae8 100644 --- a/tstest/tstest.go +++ b/tstest/tstest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tstest provides utilities for use in unit tests. diff --git a/tstest/tstest_test.go b/tstest/tstest_test.go index ce59bde538b9a..0c281d2352f7b 100644 --- a/tstest/tstest_test.go +++ b/tstest/tstest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/typewalk/typewalk.go b/tstest/typewalk/typewalk.go index b22505351b1a2..f989b4c180394 100644 --- a/tstest/typewalk/typewalk.go +++ b/tstest/typewalk/typewalk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package typewalk provides utilities to walk Go types using reflection. diff --git a/tstime/jitter.go b/tstime/jitter.go index c5095c15d87ae..987680f3c0ba7 100644 --- a/tstime/jitter.go +++ b/tstime/jitter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstime diff --git a/tstime/jitter_test.go b/tstime/jitter_test.go index 579287bda0bc2..149ed3fa5d6d8 100644 --- a/tstime/jitter_test.go +++ b/tstime/jitter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstime diff --git a/tstime/mono/mono.go b/tstime/mono/mono.go index 260e02b0fb0f3..8975c2480c748 100644 --- a/tstime/mono/mono.go +++ b/tstime/mono/mono.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mono provides fast monotonic time. diff --git a/tstime/mono/mono_test.go b/tstime/mono/mono_test.go index 67a8614baf2ef..dfa6fe1f078a3 100644 --- a/tstime/mono/mono_test.go +++ b/tstime/mono/mono_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package mono diff --git a/tstime/rate/rate.go b/tstime/rate/rate.go index f0473862a2890..3f2f5c9be55f5 100644 --- a/tstime/rate/rate.go +++ b/tstime/rate/rate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This is a modified, simplified version of code from golang.org/x/time/rate. diff --git a/tstime/rate/rate_test.go b/tstime/rate/rate_test.go index dc3f9e84bb851..3486371be565a 100644 --- a/tstime/rate/rate_test.go +++ b/tstime/rate/rate_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This is a modified, simplified version of code from golang.org/x/time/rate. diff --git a/tstime/rate/value.go b/tstime/rate/value.go index 610f06bbd7991..8a627ff36119e 100644 --- a/tstime/rate/value.go +++ b/tstime/rate/value.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rate diff --git a/tstime/rate/value_test.go b/tstime/rate/value_test.go index a26442650cf94..e6d60798407f1 100644 --- a/tstime/rate/value_test.go +++ b/tstime/rate/value_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rate diff --git a/tstime/tstime.go b/tstime/tstime.go index 6e5b7f9f47146..8c52a4652beca 100644 --- a/tstime/tstime.go +++ b/tstime/tstime.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tstime defines Tailscale-specific time utilities. diff --git a/tstime/tstime_test.go b/tstime/tstime_test.go index 556ad4e8bb1d0..80d4e318e66bc 100644 --- a/tstime/tstime_test.go +++ b/tstime/tstime_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstime diff --git a/tsweb/debug.go b/tsweb/debug.go index 4c0fabaff4aea..e4ac7a55909dd 100644 --- a/tsweb/debug.go +++ b/tsweb/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/debug_test.go b/tsweb/debug_test.go index 2a68ab6fb27b9..b46a3a3f37c32 100644 --- a/tsweb/debug_test.go +++ b/tsweb/debug_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/log.go b/tsweb/log.go index 51f95e95f5d07..1cb5f28eff29f 100644 --- a/tsweb/log.go +++ b/tsweb/log.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/pprof_default.go b/tsweb/pprof_default.go index 7d22a61619855..a4ac86cdba161 100644 --- a/tsweb/pprof_default.go +++ b/tsweb/pprof_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !wasm diff --git a/tsweb/pprof_js.go b/tsweb/pprof_js.go index 1212b37e86f5a..5635fbb2ca7e9 100644 --- a/tsweb/pprof_js.go +++ b/tsweb/pprof_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js && wasm diff --git a/tsweb/promvarz/promvarz.go b/tsweb/promvarz/promvarz.go index 1d978c7677328..4fdf394d0891b 100644 --- a/tsweb/promvarz/promvarz.go +++ b/tsweb/promvarz/promvarz.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package promvarz combines Prometheus metrics exported by our expvar converter diff --git a/tsweb/promvarz/promvarz_test.go b/tsweb/promvarz/promvarz_test.go index cffbbec2273c8..123330d6e5831 100644 --- a/tsweb/promvarz/promvarz_test.go +++ b/tsweb/promvarz/promvarz_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package promvarz diff --git a/tsweb/request_id.go b/tsweb/request_id.go index 46e52385240ca..351ed1710802b 100644 --- a/tsweb/request_id.go +++ b/tsweb/request_id.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index f6196174b38b2..f464e7af2141e 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsweb contains code used in various Tailscale webservers. diff --git a/tsweb/tsweb_test.go b/tsweb/tsweb_test.go index d4c9721e97215..af8e52420bd50 100644 --- a/tsweb/tsweb_test.go +++ b/tsweb/tsweb_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/varz/varz.go b/tsweb/varz/varz.go index b1c66b859e8cf..a2286c7603be3 100644 --- a/tsweb/varz/varz.go +++ b/tsweb/varz/varz.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package varz contains code to export metrics in Prometheus format. @@ -245,11 +245,21 @@ func writePromExpVar(w io.Writer, prefix string, kv expvar.KeyValue) { if label != "" && typ != "" { fmt.Fprintf(w, "# TYPE %s %s\n", name, typ) v.Do(func(kv expvar.KeyValue) { - fmt.Fprintf(w, "%s{%s=%q} %v\n", name, label, kv.Key, kv.Value) + switch kv.Value.(type) { + case *expvar.Int, *expvar.Float: + fmt.Fprintf(w, "%s{%s=%q} %v\n", name, label, kv.Key, kv.Value) + default: + fmt.Fprintf(w, "# skipping %q expvar map key %q with unknown value type %T\n", name, kv.Key, kv.Value) + } }) } else { v.Do(func(kv expvar.KeyValue) { - fmt.Fprintf(w, "%s_%s %v\n", name, kv.Key, kv.Value) + switch kv.Value.(type) { + case *expvar.Int, *expvar.Float: + fmt.Fprintf(w, "%s_%s %v\n", name, kv.Key, kv.Value) + default: + fmt.Fprintf(w, "# skipping %q expvar map key %q with unknown value type %T\n", name, kv.Key, kv.Value) + } }) } } diff --git a/tsweb/varz/varz_test.go b/tsweb/varz/varz_test.go index 5bbacbe356940..d041edb4b93d4 100644 --- a/tsweb/varz/varz_test.go +++ b/tsweb/varz/varz_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package varz @@ -113,7 +113,6 @@ func TestVarzHandler(t *testing.T) { &metrics.Set{ Map: *(func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("foo", 1) m.Add("bar", 2) return m @@ -127,7 +126,6 @@ func TestVarzHandler(t *testing.T) { &metrics.Set{ Map: *(func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("foo", 1) m.Add("bar", 2) return m @@ -140,7 +138,6 @@ func TestVarzHandler(t *testing.T) { "api_status_code", func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("2xx", 100) m.Add("5xx", 2) return m @@ -172,7 +169,6 @@ func TestVarzHandler(t *testing.T) { Label: "label", Map: *(func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("foo", 1) m.Add("bar", 2) return m @@ -180,6 +176,40 @@ func TestVarzHandler(t *testing.T) { }, "# TYPE m counter\nm{label=\"bar\"} 2\nm{label=\"foo\"} 1\n", }, + { + "metrics_label_map_float", + "float_map", + func() *expvar.Map { + m := new(expvar.Map) + f := new(expvar.Float) + f.Set(1.5) + m.Set("a", f) + return m + }(), + "float_map_a 1.5\n", + }, + { + "metrics_label_map_int", + "int_map", + func() *expvar.Map { + m := new(expvar.Map) + f := new(expvar.Int) + f.Set(55) + m.Set("a", f) + return m + }(), + "int_map_a 55\n", + }, + { + "metrics_label_map_string", + "string_map", + func() *expvar.Map { + m := new(expvar.Map) + m.Set("a", expvar.NewString("foo")) + return m + }(), + "# skipping \"string_map\" expvar map key \"a\" with unknown value type *expvar.String\n", + }, { "metrics_label_map_untyped", "control_save_config", @@ -219,7 +249,6 @@ func TestVarzHandler(t *testing.T) { "counter_labelmap_keyname_m", func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("foo", 1) m.Add("bar", 2) return m @@ -298,6 +327,12 @@ foo_foo_b 1 api_status_code 42 `) + "\n", }, + { + "string_expvar_is_not_exported", + "foo_string", + new(expvar.String), + "# skipping expvar \"foo_string\" (Go type *expvar.String) with undeclared Prometheus type\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/types/appctype/appconnector.go b/types/appctype/appconnector.go index 567ab755f0598..0af5db4c38672 100644 --- a/types/appctype/appconnector.go +++ b/types/appctype/appconnector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package appcfg contains an experimental configuration structure for @@ -8,6 +8,7 @@ package appctype import ( "net/netip" + "go4.org/netipx" "tailscale.com/tailcfg" ) @@ -93,3 +94,17 @@ type RouteUpdate struct { Advertise []netip.Prefix Unadvertise []netip.Prefix } + +type Conn25Attr struct { + // Name is the name of this collection of domains. + Name string `json:"name,omitempty"` + // Domains enumerates the domains serviced by the specified app connectors. + // Domains can be of the form: example.com, or *.example.com. + Domains []string `json:"domains,omitempty"` + // Connectors enumerates the app connectors which service these domains. + // These can either be "*" to match any advertising connector, or a + // tag of the form tag:. + Connectors []string `json:"connectors,omitempty"` + MagicIPPool []netipx.IPRange `json:"magicIPPool,omitempty"` + TransitIPPool []netipx.IPRange `json:"transitIPPool,omitempty"` +} diff --git a/types/appctype/appconnector_test.go b/types/appctype/appconnector_test.go index 390d1776a3280..f411faec5bef0 100644 --- a/types/appctype/appconnector_test.go +++ b/types/appctype/appconnector_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appctype diff --git a/types/bools/bools.go b/types/bools/bools.go index e64068746ed9e..d271b8c28e19a 100644 --- a/types/bools/bools.go +++ b/types/bools/bools.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package bools contains the [Int], [Compare], and [IfElse] functions. diff --git a/types/bools/bools_test.go b/types/bools/bools_test.go index 67faf3bcc92d8..70fcd0fbcb1a2 100644 --- a/types/bools/bools_test.go +++ b/types/bools/bools_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package bools diff --git a/types/dnstype/dnstype.go b/types/dnstype/dnstype.go index a3ba1b0a981e2..1cd38d38385ba 100644 --- a/types/dnstype/dnstype.go +++ b/types/dnstype/dnstype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dnstype defines types for working with DNS. diff --git a/types/dnstype/dnstype_clone.go b/types/dnstype/dnstype_clone.go index 3985704aa0638..e690ebaec3865 100644 --- a/types/dnstype/dnstype_clone.go +++ b/types/dnstype/dnstype_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/types/dnstype/dnstype_test.go b/types/dnstype/dnstype_test.go index ada5f687def9f..cf20f4f7f6618 100644 --- a/types/dnstype/dnstype_test.go +++ b/types/dnstype/dnstype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnstype diff --git a/types/dnstype/dnstype_view.go b/types/dnstype/dnstype_view.go index a983864d0ce42..c91feb6b8aa38 100644 --- a/types/dnstype/dnstype_view.go +++ b/types/dnstype/dnstype_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/types/empty/message.go b/types/empty/message.go index dc8eb4cc2dc37..bee653038be33 100644 --- a/types/empty/message.go +++ b/types/empty/message.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package empty defines an empty struct type. diff --git a/types/flagtype/flagtype.go b/types/flagtype/flagtype.go index be160dee82a21..1e45b04f453ed 100644 --- a/types/flagtype/flagtype.go +++ b/types/flagtype/flagtype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package flagtype defines flag.Value types. diff --git a/types/geo/doc.go b/types/geo/doc.go index 749c6308093f6..61c78f78cb653 100644 --- a/types/geo/doc.go +++ b/types/geo/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package geo provides functionality to represent and process geographical diff --git a/types/geo/point.go b/types/geo/point.go index d7160ac593338..d039ea1fad7e2 100644 --- a/types/geo/point.go +++ b/types/geo/point.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo @@ -125,6 +125,9 @@ func (p Point) SphericalAngleTo(q Point) (Radians, error) { sLat, sLng := float64(qLat.Radians()), float64(qLng.Radians()) cosA := math.Sin(rLat)*math.Sin(sLat) + math.Cos(rLat)*math.Cos(sLat)*math.Cos(rLng-sLng) + // Subtle floating point imprecision can lead to cosA being outside + // the domain of arccosine [-1, 1]. Clamp the input to avoid NaN return. + cosA = min(max(-1.0, cosA), 1.0) return Radians(math.Acos(cosA)), nil } diff --git a/types/geo/point_test.go b/types/geo/point_test.go index 308c1a1834377..32a73180add34 100644 --- a/types/geo/point_test.go +++ b/types/geo/point_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo_test @@ -448,65 +448,79 @@ func TestPointMarshalUint64(t *testing.T) { }) } +const earthRadius = 6371.000 // volumetric mean radius (km) +const kmToRad = 1 / earthRadius + +// Test corpus for exercising PointSphericalAngleTo. +var corpusPointSphericalAngleTo = []struct { + name string + x geo.Point + y geo.Point + want geo.Radians + wantErr string +}{ + { + name: "same-point-null-island", + x: geo.MakePoint(0, 0), + y: geo.MakePoint(0, 0), + want: 0.0 * geo.Radian, + }, + { + name: "same-point-north-pole", + x: geo.MakePoint(+90, 0), + y: geo.MakePoint(+90, +90), + want: 0.0 * geo.Radian, + }, + { + name: "same-point-south-pole", + x: geo.MakePoint(-90, 0), + y: geo.MakePoint(-90, -90), + want: 0.0 * geo.Radian, + }, + { + name: "north-pole-to-south-pole", + x: geo.MakePoint(+90, 0), + y: geo.MakePoint(-90, -90), + want: math.Pi * geo.Radian, + }, + { + name: "toronto-to-montreal", + x: geo.MakePoint(+43.6532, -79.3832), + y: geo.MakePoint(+45.5019, -73.5674), + want: 504.26 * kmToRad * geo.Radian, + }, + { + name: "sydney-to-san-francisco", + x: geo.MakePoint(-33.8727, +151.2057), + y: geo.MakePoint(+37.7749, -122.4194), + want: 11948.18 * kmToRad * geo.Radian, + }, + { + name: "new-york-to-paris", + x: geo.MakePoint(+40.7128, -74.0060), + y: geo.MakePoint(+48.8575, +2.3514), + want: 5837.15 * kmToRad * geo.Radian, + }, + { + name: "seattle-to-tokyo", + x: geo.MakePoint(+47.6061, -122.3328), + y: geo.MakePoint(+35.6764, +139.6500), + want: 7700.00 * kmToRad * geo.Radian, + }, + { + // Subtle floating point imprecision can propagate and lead to + // trigonometric functions receiving inputs outside their + // domain, thus returning NaN. + // Test one such case. + name: "floating-point-precision-test", + x: geo.MakePoint(-6.0, 0.0), + y: geo.MakePoint(-6.0, 0.0), + want: 0.0 * geo.Radian, + }, +} + func TestPointSphericalAngleTo(t *testing.T) { - const earthRadius = 6371.000 // volumetric mean radius (km) - const kmToRad = 1 / earthRadius - for _, tt := range []struct { - name string - x geo.Point - y geo.Point - want geo.Radians - wantErr string - }{ - { - name: "same-point-null-island", - x: geo.MakePoint(0, 0), - y: geo.MakePoint(0, 0), - want: 0.0 * geo.Radian, - }, - { - name: "same-point-north-pole", - x: geo.MakePoint(+90, 0), - y: geo.MakePoint(+90, +90), - want: 0.0 * geo.Radian, - }, - { - name: "same-point-south-pole", - x: geo.MakePoint(-90, 0), - y: geo.MakePoint(-90, -90), - want: 0.0 * geo.Radian, - }, - { - name: "north-pole-to-south-pole", - x: geo.MakePoint(+90, 0), - y: geo.MakePoint(-90, -90), - want: math.Pi * geo.Radian, - }, - { - name: "toronto-to-montreal", - x: geo.MakePoint(+43.6532, -79.3832), - y: geo.MakePoint(+45.5019, -73.5674), - want: 504.26 * kmToRad * geo.Radian, - }, - { - name: "sydney-to-san-francisco", - x: geo.MakePoint(-33.8727, +151.2057), - y: geo.MakePoint(+37.7749, -122.4194), - want: 11948.18 * kmToRad * geo.Radian, - }, - { - name: "new-york-to-paris", - x: geo.MakePoint(+40.7128, -74.0060), - y: geo.MakePoint(+48.8575, +2.3514), - want: 5837.15 * kmToRad * geo.Radian, - }, - { - name: "seattle-to-tokyo", - x: geo.MakePoint(+47.6061, -122.3328), - y: geo.MakePoint(+35.6764, +139.6500), - want: 7700.00 * kmToRad * geo.Radian, - }, - } { + for _, tt := range corpusPointSphericalAngleTo { t.Run(tt.name, func(t *testing.T) { got, err := tt.x.SphericalAngleTo(tt.y) if tt.wantErr == "" && err != nil { @@ -536,6 +550,23 @@ func TestPointSphericalAngleTo(t *testing.T) { } } +func FuzzPointSphericalAngleTo(f *testing.F) { + for _, tt := range corpusPointSphericalAngleTo { + xLat, xLng, _ := tt.x.LatLngFloat64() + yLat, yLng, _ := tt.y.LatLngFloat64() + f.Add(xLat, xLng, yLat, yLng) + } + + f.Fuzz(func(t *testing.T, xLat float64, xLng float64, yLat float64, yLng float64) { + x := geo.MakePoint(geo.Degrees(xLat), geo.Degrees(xLng)) + y := geo.MakePoint(geo.Degrees(yLat), geo.Degrees(yLng)) + got, _ := x.SphericalAngleTo(y) + if math.IsNaN(float64(got)) { + t.Errorf("got NaN result with xLat=%.15f xLng=%.15f yLat=%.15f yLng=%.15f", xLat, xLng, yLat, yLng) + } + }) +} + func approx[T ~float64](x, y T) bool { return math.Abs(float64(x)-float64(y)) <= 1e-5 } diff --git a/types/geo/quantize.go b/types/geo/quantize.go index 18ec11f9f119c..f07562424f96e 100644 --- a/types/geo/quantize.go +++ b/types/geo/quantize.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo diff --git a/types/geo/quantize_test.go b/types/geo/quantize_test.go index bc1f62c9be32f..59d5587e565dc 100644 --- a/types/geo/quantize_test.go +++ b/types/geo/quantize_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo_test diff --git a/types/geo/units.go b/types/geo/units.go index 76a4c02f79f34..74df9624d6bd9 100644 --- a/types/geo/units.go +++ b/types/geo/units.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo diff --git a/types/geo/units_test.go b/types/geo/units_test.go index b6f724ce0d9b3..cfbb7ae6a6685 100644 --- a/types/geo/units_test.go +++ b/types/geo/units_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo_test diff --git a/types/iox/io.go b/types/iox/io.go index a5ca1be43f737..f78328a10a969 100644 --- a/types/iox/io.go +++ b/types/iox/io.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package iox provides types to implement [io] functionality. diff --git a/types/iox/io_test.go b/types/iox/io_test.go index 9fba39605d28d..7a902841b75fe 100644 --- a/types/iox/io_test.go +++ b/types/iox/io_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package iox diff --git a/types/ipproto/ipproto.go b/types/ipproto/ipproto.go index b5333eb56ace0..a08985b3aba26 100644 --- a/types/ipproto/ipproto.go +++ b/types/ipproto/ipproto.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipproto contains IP Protocol constants. diff --git a/types/ipproto/ipproto_test.go b/types/ipproto/ipproto_test.go index 102b79cffae5b..8bfeb13fa4246 100644 --- a/types/ipproto/ipproto_test.go +++ b/types/ipproto/ipproto_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipproto diff --git a/types/jsonx/json.go b/types/jsonx/json.go index 3f01ea358df30..36516f495380b 100644 --- a/types/jsonx/json.go +++ b/types/jsonx/json.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsonx contains helper types and functionality to use with diff --git a/types/jsonx/json_test.go b/types/jsonx/json_test.go index 0f2a646c40d6d..5c302d9746c3e 100644 --- a/types/jsonx/json_test.go +++ b/types/jsonx/json_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsonx diff --git a/types/key/chal.go b/types/key/chal.go index 742ac5479e4a1..50827d28ed0fe 100644 --- a/types/key/chal.go +++ b/types/key/chal.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/control.go b/types/key/control.go index 96021249ba047..384be160265fa 100644 --- a/types/key/control.go +++ b/types/key/control.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/control_test.go b/types/key/control_test.go index a98a586f3ba5a..928be4283bc3a 100644 --- a/types/key/control_test.go +++ b/types/key/control_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/derp.go b/types/key/derp.go index 1466b85bc5288..a85611d241765 100644 --- a/types/key/derp.go +++ b/types/key/derp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/derp_test.go b/types/key/derp_test.go index b91cbbf8c4e01..ab98671e5bd29 100644 --- a/types/key/derp_test.go +++ b/types/key/derp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/disco.go b/types/key/disco.go index 52b40c766fbbf..f46347c919ebb 100644 --- a/types/key/disco.go +++ b/types/key/disco.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/disco_test.go b/types/key/disco_test.go index 131fe350f508a..fb22fa82f5400 100644 --- a/types/key/disco_test.go +++ b/types/key/disco_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/doc.go b/types/key/doc.go index b2aad72d612bb..cbee21e5f5732 100644 --- a/types/key/doc.go +++ b/types/key/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package key contains types for different types of public and private keys diff --git a/types/key/hardware_attestation.go b/types/key/hardware_attestation.go index 9d4a21ee42706..5ca7e936b3d72 100644 --- a/types/key/hardware_attestation.go +++ b/types/key/hardware_attestation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/machine.go b/types/key/machine.go index a05f3cc1f5735..9ad73bec1a434 100644 --- a/types/key/machine.go +++ b/types/key/machine.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/machine_test.go b/types/key/machine_test.go index 157df9e4356b1..3db92ed406bd6 100644 --- a/types/key/machine_test.go +++ b/types/key/machine_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/nl.go b/types/key/nl.go index 50caed98c2d0b..fc11d5b20ff64 100644 --- a/types/key/nl.go +++ b/types/key/nl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/nl_test.go b/types/key/nl_test.go index 75b7765a19ea1..84fa920569f08 100644 --- a/types/key/nl_test.go +++ b/types/key/nl_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/node.go b/types/key/node.go index 11ee1fa3cfd41..1402aad361870 100644 --- a/types/key/node.go +++ b/types/key/node.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/node_test.go b/types/key/node_test.go index 80a2dadf90f5f..77eef2b28d2f5 100644 --- a/types/key/node_test.go +++ b/types/key/node_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/util.go b/types/key/util.go index 50fac827556aa..c336d38792a25 100644 --- a/types/key/util.go +++ b/types/key/util.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/util_test.go b/types/key/util_test.go index 4d6f8242280ad..3323e0e574684 100644 --- a/types/key/util_test.go +++ b/types/key/util_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/lazy/deferred.go b/types/lazy/deferred.go index 973082914c48c..582090ab93112 100644 --- a/types/lazy/deferred.go +++ b/types/lazy/deferred.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/deferred_test.go b/types/lazy/deferred_test.go index 98cacbfce7088..61cc8f8ac6c27 100644 --- a/types/lazy/deferred_test.go +++ b/types/lazy/deferred_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/lazy.go b/types/lazy/lazy.go index f537758fa6415..915ae2002c135 100644 --- a/types/lazy/lazy.go +++ b/types/lazy/lazy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lazy provides types for lazily initialized values. diff --git a/types/lazy/map.go b/types/lazy/map.go index 75a1dd739d3bc..4718c5b873c4b 100644 --- a/types/lazy/map.go +++ b/types/lazy/map.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/map_test.go b/types/lazy/map_test.go index ec1152b0b802c..5f09da5aea1f1 100644 --- a/types/lazy/map_test.go +++ b/types/lazy/map_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/sync_test.go b/types/lazy/sync_test.go index 4d1278253955b..b517594d0a8e3 100644 --- a/types/lazy/sync_test.go +++ b/types/lazy/sync_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/unsync.go b/types/lazy/unsync.go index 0f89ce4f6935a..75d7be23f1e04 100644 --- a/types/lazy/unsync.go +++ b/types/lazy/unsync.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/unsync_test.go b/types/lazy/unsync_test.go index f0d2494d12b6e..c3fcf27acad65 100644 --- a/types/lazy/unsync_test.go +++ b/types/lazy/unsync_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/logger/logger.go b/types/logger/logger.go index 6c4edf6336005..71086e87dbd83 100644 --- a/types/logger/logger.go +++ b/types/logger/logger.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package logger defines a type for writing to logs. It's just a diff --git a/types/logger/logger_test.go b/types/logger/logger_test.go index 52c1d3900e1c5..f55a9484d344e 100644 --- a/types/logger/logger_test.go +++ b/types/logger/logger_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logger diff --git a/types/logger/rusage.go b/types/logger/rusage.go index 3943636d6e255..c1bbbaa5378f7 100644 --- a/types/logger/rusage.go +++ b/types/logger/rusage.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logger diff --git a/types/logger/rusage_stub.go b/types/logger/rusage_stub.go index f646f1e1eee7f..e94478ef7c59b 100644 --- a/types/logger/rusage_stub.go +++ b/types/logger/rusage_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows || wasm || plan9 || tamago diff --git a/types/logger/rusage_syscall.go b/types/logger/rusage_syscall.go index 2871b66c6bb24..25b026994afe8 100644 --- a/types/logger/rusage_syscall.go +++ b/types/logger/rusage_syscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !wasm && !plan9 && !tamago diff --git a/types/logger/tokenbucket.go b/types/logger/tokenbucket.go index 83d4059c2af00..fdee56237757e 100644 --- a/types/logger/tokenbucket.go +++ b/types/logger/tokenbucket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logger diff --git a/types/logid/id.go b/types/logid/id.go index fd46a7bef735c..94e363879d324 100644 --- a/types/logid/id.go +++ b/types/logid/id.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package logid contains ID types for interacting with the log service. diff --git a/types/logid/id_test.go b/types/logid/id_test.go index c93d1f1c1adc0..86a736bd877e6 100644 --- a/types/logid/id_test.go +++ b/types/logid/id_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logid diff --git a/types/mapx/ordered.go b/types/mapx/ordered.go index 1991f039d7726..caaa4d098f8fb 100644 --- a/types/mapx/ordered.go +++ b/types/mapx/ordered.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mapx contains extra map types and functions. diff --git a/types/mapx/ordered_test.go b/types/mapx/ordered_test.go index 7dcb7e40558c3..9bf0be6410e80 100644 --- a/types/mapx/ordered_test.go +++ b/types/mapx/ordered_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package mapx diff --git a/types/netlogfunc/netlogfunc.go b/types/netlogfunc/netlogfunc.go index 6185fcb715c65..db856f0cf49f1 100644 --- a/types/netlogfunc/netlogfunc.go +++ b/types/netlogfunc/netlogfunc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netlogfunc defines types for network logging. diff --git a/types/netlogtype/netlogtype.go b/types/netlogtype/netlogtype.go index cc38684a30dbf..24fb32ab0bb5f 100644 --- a/types/netlogtype/netlogtype.go +++ b/types/netlogtype/netlogtype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netlogtype defines types for network logging. diff --git a/types/netlogtype/netlogtype_test.go b/types/netlogtype/netlogtype_test.go index 00f89b228aa96..8271f0ae04144 100644 --- a/types/netlogtype/netlogtype_test.go +++ b/types/netlogtype/netlogtype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index 18abd1c195024..ac95254daee1d 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netmap contains the netmap.NetworkMap type. @@ -27,6 +27,8 @@ import ( // The fields should all be considered read-only. They might // alias parts of previous NetworkMap values. type NetworkMap struct { + Cached bool // whether this NetworkMap was loaded from disk cache (as opposed to live from network) + SelfNode tailcfg.NodeView AllCaps set.Set[tailcfg.NodeCapability] // set version of SelfNode.Capabilities + SelfNode.CapMap NodeKey key.NodePublic diff --git a/types/netmap/netmap_test.go b/types/netmap/netmap_test.go index ee4fecdb4ff4e..e68b243b37f42 100644 --- a/types/netmap/netmap_test.go +++ b/types/netmap/netmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmap diff --git a/types/netmap/nodemut.go b/types/netmap/nodemut.go index 4f93be21c6d68..5c9000d56ef38 100644 --- a/types/netmap/nodemut.go +++ b/types/netmap/nodemut.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmap diff --git a/types/netmap/nodemut_test.go b/types/netmap/nodemut_test.go index 374f8623ad564..f7302d48df097 100644 --- a/types/netmap/nodemut_test.go +++ b/types/netmap/nodemut_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmap diff --git a/types/nettype/nettype.go b/types/nettype/nettype.go index 5d3d303c38a0d..e44daa0c709f3 100644 --- a/types/nettype/nettype.go +++ b/types/nettype/nettype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package nettype defines an interface that doesn't exist in the Go net package. diff --git a/types/opt/bool.go b/types/opt/bool.go index fbc39e1dc3754..cecbf5eac1c68 100644 --- a/types/opt/bool.go +++ b/types/opt/bool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package opt defines optional types. diff --git a/types/opt/bool_test.go b/types/opt/bool_test.go index e61d66dbe9e96..de4da3788d1e7 100644 --- a/types/opt/bool_test.go +++ b/types/opt/bool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package opt diff --git a/types/opt/value.go b/types/opt/value.go index c71c53e511aca..1ccdd75a47c72 100644 --- a/types/opt/value.go +++ b/types/opt/value.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package opt diff --git a/types/opt/value_test.go b/types/opt/value_test.go index 890f9a5795cb3..0b73182996ad2 100644 --- a/types/opt/value_test.go +++ b/types/opt/value_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package opt diff --git a/types/persist/persist.go b/types/persist/persist.go index 80bac9b5e2741..2a8c2fb824d36 100644 --- a/types/persist/persist.go +++ b/types/persist/persist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package persist contains the Persist type. diff --git a/types/persist/persist_clone.go b/types/persist/persist_clone.go index 9dbe7e0f6fa6d..f5fa36b6da0fc 100644 --- a/types/persist/persist_clone.go +++ b/types/persist/persist_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/types/persist/persist_test.go b/types/persist/persist_test.go index 713114b74dcd5..b25af5a0b2066 100644 --- a/types/persist/persist_test.go +++ b/types/persist/persist_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package persist diff --git a/types/persist/persist_view.go b/types/persist/persist_view.go index dbf8294ef5a7a..b18634917c651 100644 --- a/types/persist/persist_view.go +++ b/types/persist/persist_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/types/prefs/item.go b/types/prefs/item.go index 717a0c76cf291..fdb9301f9fdf8 100644 --- a/types/prefs/item.go +++ b/types/prefs/item.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/list.go b/types/prefs/list.go index ae6b2fae335db..20e4dad463135 100644 --- a/types/prefs/list.go +++ b/types/prefs/list.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/map.go b/types/prefs/map.go index 4b64690ed1351..6bf1948b87ab4 100644 --- a/types/prefs/map.go +++ b/types/prefs/map.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/options.go b/types/prefs/options.go index 3769b784b731a..bc0123a526084 100644 --- a/types/prefs/options.go +++ b/types/prefs/options.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/prefs.go b/types/prefs/prefs.go index a6caf12838b79..3f18886a724bc 100644 --- a/types/prefs/prefs.go +++ b/types/prefs/prefs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package prefs contains types and functions to work with arbitrary diff --git a/types/prefs/prefs_clone_test.go b/types/prefs/prefs_clone_test.go index 2a03fba8b092c..07dc24fdc7361 100644 --- a/types/prefs/prefs_clone_test.go +++ b/types/prefs/prefs_clone_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/types/prefs/prefs_example/prefs_example_clone.go b/types/prefs/prefs_example/prefs_example_clone.go index 5c707b46343e1..c5fdc49fc3b9b 100644 --- a/types/prefs/prefs_example/prefs_example_clone.go +++ b/types/prefs/prefs_example/prefs_example_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/types/prefs/prefs_example/prefs_example_view.go b/types/prefs/prefs_example/prefs_example_view.go index 6a1a36865fe00..67a284bb5b4bb 100644 --- a/types/prefs/prefs_example/prefs_example_view.go +++ b/types/prefs/prefs_example/prefs_example_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/types/prefs/prefs_example/prefs_test.go b/types/prefs/prefs_example/prefs_test.go index aefbae9f2873a..93ed5b4fea27b 100644 --- a/types/prefs/prefs_example/prefs_test.go +++ b/types/prefs/prefs_example/prefs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs_example diff --git a/types/prefs/prefs_example/prefs_types.go b/types/prefs/prefs_example/prefs_types.go index c35f1f62fde3d..d0764c64b6efc 100644 --- a/types/prefs/prefs_example/prefs_types.go +++ b/types/prefs/prefs_example/prefs_types.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package prefs_example contains a [Prefs] type, which is like [tailscale.com/ipn.Prefs], diff --git a/types/prefs/prefs_test.go b/types/prefs/prefs_test.go index dc1213adb27ab..ccc37b0a74df7 100644 --- a/types/prefs/prefs_test.go +++ b/types/prefs/prefs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/prefs_view_test.go b/types/prefs/prefs_view_test.go index 8993cb535bd67..ce4dee726badc 100644 --- a/types/prefs/prefs_view_test.go +++ b/types/prefs/prefs_view_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/types/prefs/struct_list.go b/types/prefs/struct_list.go index ba145e2cf7086..09aa808ccc37e 100644 --- a/types/prefs/struct_list.go +++ b/types/prefs/struct_list.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/struct_map.go b/types/prefs/struct_map.go index 83cc7447baedd..2f2715a62a94a 100644 --- a/types/prefs/struct_map.go +++ b/types/prefs/struct_map.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/preftype/netfiltermode.go b/types/preftype/netfiltermode.go index 273e173444365..f108bebc35688 100644 --- a/types/preftype/netfiltermode.go +++ b/types/preftype/netfiltermode.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package preftype is a leaf package containing types for various diff --git a/types/ptr/ptr.go b/types/ptr/ptr.go index beb17bee8ee0e..5b65a0e1c13e7 100644 --- a/types/ptr/ptr.go +++ b/types/ptr/ptr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ptr contains the ptr.To function. diff --git a/types/result/result.go b/types/result/result.go index 6bd1c2ea62004..4d537b084ea54 100644 --- a/types/result/result.go +++ b/types/result/result.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package result contains the Of result type, which is diff --git a/types/structs/structs.go b/types/structs/structs.go index 47c359f0caa0f..dd0cd809b8928 100644 --- a/types/structs/structs.go +++ b/types/structs/structs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package structs contains the Incomparable type. diff --git a/types/tkatype/tkatype.go b/types/tkatype/tkatype.go index 6ad51f6a90240..e315f4422ae98 100644 --- a/types/tkatype/tkatype.go +++ b/types/tkatype/tkatype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tkatype defines types for working with the tka package. diff --git a/types/tkatype/tkatype_test.go b/types/tkatype/tkatype_test.go index c81891b9ce103..337167a7d3bd8 100644 --- a/types/tkatype/tkatype_test.go +++ b/types/tkatype/tkatype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tkatype diff --git a/types/views/views.go b/types/views/views.go index 252f126a79f57..9260311edc29a 100644 --- a/types/views/views.go +++ b/types/views/views.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package views provides read-only accessors for commonly used diff --git a/types/views/views_test.go b/types/views/views_test.go index 5a30c11a13c86..7cdd1ab020312 100644 --- a/types/views/views_test.go +++ b/types/views/views_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package views diff --git a/util/backoff/backoff.go b/util/backoff/backoff.go index 95089fc2479ff..2edb1e7712e65 100644 --- a/util/backoff/backoff.go +++ b/util/backoff/backoff.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package backoff provides a back-off timer type. diff --git a/util/checkchange/checkchange.go b/util/checkchange/checkchange.go index 8ba64720d7e14..45e3c0bf54660 100644 --- a/util/checkchange/checkchange.go +++ b/util/checkchange/checkchange.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package checkchange defines a utility for determining whether a value diff --git a/util/cibuild/cibuild.go b/util/cibuild/cibuild.go index c1e337f9a142a..4a4e241ac2cf0 100644 --- a/util/cibuild/cibuild.go +++ b/util/cibuild/cibuild.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cibuild reports runtime CI information. diff --git a/util/clientmetric/clientmetric.go b/util/clientmetric/clientmetric.go index 50cf3b2960499..b67cbbd39aa1e 100644 --- a/util/clientmetric/clientmetric.go +++ b/util/clientmetric/clientmetric.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_clientmetrics diff --git a/util/clientmetric/clientmetric_test.go b/util/clientmetric/clientmetric_test.go index 555d7a71170a4..db1cfe1893512 100644 --- a/util/clientmetric/clientmetric_test.go +++ b/util/clientmetric/clientmetric_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package clientmetric diff --git a/util/clientmetric/omit.go b/util/clientmetric/omit.go index 6d678cf20d1ae..725b18fe48d3c 100644 --- a/util/clientmetric/omit.go +++ b/util/clientmetric/omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_clientmetrics diff --git a/util/cloudenv/cloudenv.go b/util/cloudenv/cloudenv.go index f55f7dfb0794a..aee4bac723f2d 100644 --- a/util/cloudenv/cloudenv.go +++ b/util/cloudenv/cloudenv.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cloudenv reports which known cloud environment we're running in. diff --git a/util/cloudenv/cloudenv_test.go b/util/cloudenv/cloudenv_test.go index c4486b2841ec1..c928fe660ee72 100644 --- a/util/cloudenv/cloudenv_test.go +++ b/util/cloudenv/cloudenv_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cloudenv diff --git a/util/cloudinfo/cloudinfo.go b/util/cloudinfo/cloudinfo.go index 2c4a32c031d2c..5f6a54ebdcb95 100644 --- a/util/cloudinfo/cloudinfo.go +++ b/util/cloudinfo/cloudinfo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(ios || android || js) diff --git a/util/cloudinfo/cloudinfo_nocloud.go b/util/cloudinfo/cloudinfo_nocloud.go index 6a525cd2a5725..b7ea210c15c6f 100644 --- a/util/cloudinfo/cloudinfo_nocloud.go +++ b/util/cloudinfo/cloudinfo_nocloud.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || android || js diff --git a/util/cloudinfo/cloudinfo_test.go b/util/cloudinfo/cloudinfo_test.go index 38817f47a6e56..721eca25f960d 100644 --- a/util/cloudinfo/cloudinfo_test.go +++ b/util/cloudinfo/cloudinfo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cloudinfo diff --git a/util/cmpver/version.go b/util/cmpver/version.go index 972c7b95f9a5e..69b01c48b1d4b 100644 --- a/util/cmpver/version.go +++ b/util/cmpver/version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cmpver implements a variant of debian version number diff --git a/util/cmpver/version_test.go b/util/cmpver/version_test.go index 8a3e470d1d37f..5688aa037927b 100644 --- a/util/cmpver/version_test.go +++ b/util/cmpver/version_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cmpver_test diff --git a/util/codegen/codegen.go b/util/codegen/codegen.go index ec02d652b8760..2023c8d9b1e08 100644 --- a/util/codegen/codegen.go +++ b/util/codegen/codegen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package codegen contains shared utilities for generating code. @@ -69,7 +69,7 @@ func HasNoClone(structTag string) bool { return false } -const copyrightHeader = `// Copyright (c) Tailscale Inc & AUTHORS +const copyrightHeader = `// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause ` diff --git a/util/codegen/codegen_test.go b/util/codegen/codegen_test.go index 74715eecae6ef..49656401a9b18 100644 --- a/util/codegen/codegen_test.go +++ b/util/codegen/codegen_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package codegen diff --git a/util/cstruct/cstruct.go b/util/cstruct/cstruct.go index 4d1d0a98b8032..afb0150bb1e77 100644 --- a/util/cstruct/cstruct.go +++ b/util/cstruct/cstruct.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cstruct provides a helper for decoding binary data that is in the diff --git a/util/cstruct/cstruct_example_test.go b/util/cstruct/cstruct_example_test.go index 17032267b9dc6..a665abe355f6a 100644 --- a/util/cstruct/cstruct_example_test.go +++ b/util/cstruct/cstruct_example_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Only built on 64-bit platforms to avoid complexity diff --git a/util/cstruct/cstruct_test.go b/util/cstruct/cstruct_test.go index 5a75f338502bc..95d4876ca9256 100644 --- a/util/cstruct/cstruct_test.go +++ b/util/cstruct/cstruct_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cstruct diff --git a/util/ctxkey/key.go b/util/ctxkey/key.go index e2b0e9d4ce0bf..982c65f04c8d7 100644 --- a/util/ctxkey/key.go +++ b/util/ctxkey/key.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // ctxkey provides type-safe key-value pairs for use with [context.Context]. diff --git a/util/ctxkey/key_test.go b/util/ctxkey/key_test.go index 20d85a3c0d2ae..413c3eacdd847 100644 --- a/util/ctxkey/key_test.go +++ b/util/ctxkey/key_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ctxkey diff --git a/util/deephash/debug.go b/util/deephash/debug.go index 50b3d5605f327..70c7a965551a4 100644 --- a/util/deephash/debug.go +++ b/util/deephash/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build deephash_debug diff --git a/util/deephash/deephash.go b/util/deephash/deephash.go index 29f47e3386ebd..ae082ef35e019 100644 --- a/util/deephash/deephash.go +++ b/util/deephash/deephash.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package deephash hashes a Go value recursively, in a predictable order, diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go index 413893ff967d2..c50d70bc6ed7f 100644 --- a/util/deephash/deephash_test.go +++ b/util/deephash/deephash_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deephash diff --git a/util/deephash/pointer.go b/util/deephash/pointer.go index aafae47a23673..448f12108eeee 100644 --- a/util/deephash/pointer.go +++ b/util/deephash/pointer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deephash diff --git a/util/deephash/pointer_norace.go b/util/deephash/pointer_norace.go index f98a70f6a18e5..dc77bbeaaf4a6 100644 --- a/util/deephash/pointer_norace.go +++ b/util/deephash/pointer_norace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !race diff --git a/util/deephash/pointer_race.go b/util/deephash/pointer_race.go index c638c7d39f393..15fe45b9113b3 100644 --- a/util/deephash/pointer_race.go +++ b/util/deephash/pointer_race.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build race diff --git a/util/deephash/tailscale_types_test.go b/util/deephash/tailscale_types_test.go index eeb7fdf84d11f..7e803c841c1f5 100644 --- a/util/deephash/tailscale_types_test.go +++ b/util/deephash/tailscale_types_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains tests and benchmarks that use types from other packages diff --git a/util/deephash/testtype/testtype.go b/util/deephash/testtype/testtype.go index 3c90053d6dfd5..b5775c62a0af1 100644 --- a/util/deephash/testtype/testtype.go +++ b/util/deephash/testtype/testtype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package testtype contains types for testing deephash. diff --git a/util/deephash/types.go b/util/deephash/types.go index 54edcbffc3fe8..ef19207bf68b8 100644 --- a/util/deephash/types.go +++ b/util/deephash/types.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deephash diff --git a/util/deephash/types_test.go b/util/deephash/types_test.go index 78b40d88e5094..7a0a43b27e6d3 100644 --- a/util/deephash/types_test.go +++ b/util/deephash/types_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deephash diff --git a/util/dirwalk/dirwalk.go b/util/dirwalk/dirwalk.go index 811766892896a..38f58d517df77 100644 --- a/util/dirwalk/dirwalk.go +++ b/util/dirwalk/dirwalk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dirwalk contains code to walk a directory. diff --git a/util/dirwalk/dirwalk_linux.go b/util/dirwalk/dirwalk_linux.go index 256467ebd8ac5..4a12f8ebe075b 100644 --- a/util/dirwalk/dirwalk_linux.go +++ b/util/dirwalk/dirwalk_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirwalk diff --git a/util/dirwalk/dirwalk_test.go b/util/dirwalk/dirwalk_test.go index 15ebc13dd404d..f9ba842977d6b 100644 --- a/util/dirwalk/dirwalk_test.go +++ b/util/dirwalk/dirwalk_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirwalk diff --git a/util/dnsname/dnsname.go b/util/dnsname/dnsname.go index ef898ebbd842f..263c376aac674 100644 --- a/util/dnsname/dnsname.go +++ b/util/dnsname/dnsname.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dnsname contains string functions for working with DNS names. @@ -94,6 +94,18 @@ func (f FQDN) Contains(other FQDN) bool { return strings.HasSuffix(other.WithTrailingDot(), cmp) } +// Parent returns the parent domain by stripping the first label. +// For "foo.bar.baz.", it returns "bar.baz." +// It returns an empty FQDN for root or single-label domains. +func (f FQDN) Parent() FQDN { + s := f.WithTrailingDot() + _, rest, ok := strings.Cut(s, ".") + if !ok || rest == "" { + return "" + } + return FQDN(rest) +} + // ValidLabel reports whether label is a valid DNS label. All errors are // [vizerror.Error]. func ValidLabel(label string) error { diff --git a/util/dnsname/dnsname_test.go b/util/dnsname/dnsname_test.go index b038bb1bd10e1..e349e51c7ad99 100644 --- a/util/dnsname/dnsname_test.go +++ b/util/dnsname/dnsname_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnsname @@ -123,6 +123,34 @@ func TestFQDNContains(t *testing.T) { } } +func TestFQDNParent(t *testing.T) { + tests := []struct { + in string + want FQDN + }{ + {"", ""}, + {".", ""}, + {"com.", ""}, + {"foo.com.", "com."}, + {"www.foo.com.", "foo.com."}, + {"a.b.c.d.", "b.c.d."}, + {"sub.node.tailnet.ts.net.", "node.tailnet.ts.net."}, + } + + for _, test := range tests { + t.Run(test.in, func(t *testing.T) { + in, err := ToFQDN(test.in) + if err != nil { + t.Fatalf("ToFQDN(%q): %v", test.in, err) + } + got := in.Parent() + if got != test.want { + t.Errorf("ToFQDN(%q).Parent() = %q, want %q", test.in, got, test.want) + } + }) + } +} + func TestSanitizeLabel(t *testing.T) { tests := []struct { name string diff --git a/util/eventbus/bench_test.go b/util/eventbus/bench_test.go index 25f5b80020880..7cd7a424184d2 100644 --- a/util/eventbus/bench_test.go +++ b/util/eventbus/bench_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus_test diff --git a/util/eventbus/bus.go b/util/eventbus/bus.go index 880e075ccaf3c..1bc8aaed63dfc 100644 --- a/util/eventbus/bus.go +++ b/util/eventbus/bus.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/bus_test.go b/util/eventbus/bus_test.go index 88e11e7199aee..e7fa7577f2bdd 100644 --- a/util/eventbus/bus_test.go +++ b/util/eventbus/bus_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus_test diff --git a/util/eventbus/client.go b/util/eventbus/client.go index a7a5ab673bdfd..f405146ce4a82 100644 --- a/util/eventbus/client.go +++ b/util/eventbus/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/debug-demo/main.go b/util/eventbus/debug-demo/main.go index 71894d2eab94e..64b51a0fa4fac 100644 --- a/util/eventbus/debug-demo/main.go +++ b/util/eventbus/debug-demo/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // debug-demo is a program that serves a bus's debug interface over diff --git a/util/eventbus/debug.go b/util/eventbus/debug.go index 0453defb1a77e..7a37aeac8b6f3 100644 --- a/util/eventbus/debug.go +++ b/util/eventbus/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/debughttp.go b/util/eventbus/debughttp.go index 9e03676d07128..1c5a64074e441 100644 --- a/util/eventbus/debughttp.go +++ b/util/eventbus/debughttp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !ts_omit_debugeventbus diff --git a/util/eventbus/debughttp_off.go b/util/eventbus/debughttp_off.go index 332525262aa29..4b31bd6b78a79 100644 --- a/util/eventbus/debughttp_off.go +++ b/util/eventbus/debughttp_off.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || android || ts_omit_debugeventbus diff --git a/util/eventbus/doc.go b/util/eventbus/doc.go index f95f9398c8de9..89af076f9b637 100644 --- a/util/eventbus/doc.go +++ b/util/eventbus/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package eventbus provides an in-process event bus. diff --git a/util/eventbus/eventbustest/doc.go b/util/eventbus/eventbustest/doc.go index 1e9928b9d7cf9..504d40d9546fe 100644 --- a/util/eventbus/eventbustest/doc.go +++ b/util/eventbus/eventbustest/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package eventbustest provides helper methods for testing an [eventbus.Bus]. diff --git a/util/eventbus/eventbustest/eventbustest.go b/util/eventbus/eventbustest/eventbustest.go index fd8a150812e0d..b3ef6c884d89f 100644 --- a/util/eventbus/eventbustest/eventbustest.go +++ b/util/eventbus/eventbustest/eventbustest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbustest diff --git a/util/eventbus/eventbustest/eventbustest_test.go b/util/eventbus/eventbustest/eventbustest_test.go index ac454023c9c47..810312fcb411a 100644 --- a/util/eventbus/eventbustest/eventbustest_test.go +++ b/util/eventbus/eventbustest/eventbustest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbustest_test diff --git a/util/eventbus/eventbustest/examples_test.go b/util/eventbus/eventbustest/examples_test.go index c848113173bc6..87a0efe31c6e9 100644 --- a/util/eventbus/eventbustest/examples_test.go +++ b/util/eventbus/eventbustest/examples_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbustest_test diff --git a/util/eventbus/fetch-htmx.go b/util/eventbus/fetch-htmx.go index f80d5025727fd..6a780d3025681 100644 --- a/util/eventbus/fetch-htmx.go +++ b/util/eventbus/fetch-htmx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/util/eventbus/monitor.go b/util/eventbus/monitor.go index db6fe1be44737..0d3056e206664 100644 --- a/util/eventbus/monitor.go +++ b/util/eventbus/monitor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/publish.go b/util/eventbus/publish.go index 348bb9dff950c..f6fd029b7902e 100644 --- a/util/eventbus/publish.go +++ b/util/eventbus/publish.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/queue.go b/util/eventbus/queue.go index 2589b75cef999..0483a05a68d5c 100644 --- a/util/eventbus/queue.go +++ b/util/eventbus/queue.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/subscribe.go b/util/eventbus/subscribe.go index b0348e125c393..3edf6deb44bb2 100644 --- a/util/eventbus/subscribe.go +++ b/util/eventbus/subscribe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/execqueue/execqueue.go b/util/execqueue/execqueue.go index 87616a6b50a45..b2c7014377e13 100644 --- a/util/execqueue/execqueue.go +++ b/util/execqueue/execqueue.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package execqueue implements an ordered asynchronous queue for executing functions. diff --git a/util/execqueue/execqueue_test.go b/util/execqueue/execqueue_test.go index 1bce69556e1f7..c9f3a449ef67e 100644 --- a/util/execqueue/execqueue_test.go +++ b/util/execqueue/execqueue_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package execqueue diff --git a/util/expvarx/expvarx.go b/util/expvarx/expvarx.go index bcdc4a91a7982..6dc2379b961a5 100644 --- a/util/expvarx/expvarx.go +++ b/util/expvarx/expvarx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package expvarx provides some extensions to the [expvar] package. diff --git a/util/expvarx/expvarx_test.go b/util/expvarx/expvarx_test.go index 9ed2e8f209115..f8d2139d3ecb1 100644 --- a/util/expvarx/expvarx_test.go +++ b/util/expvarx/expvarx_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package expvarx diff --git a/util/goroutines/goroutines.go b/util/goroutines/goroutines.go index d40cbecb10876..fd0a4dd7eb321 100644 --- a/util/goroutines/goroutines.go +++ b/util/goroutines/goroutines.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The goroutines package contains utilities for tracking and getting active goroutines. diff --git a/util/goroutines/goroutines_test.go b/util/goroutines/goroutines_test.go index ae17c399ca274..97adccf1c2ab1 100644 --- a/util/goroutines/goroutines_test.go +++ b/util/goroutines/goroutines_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package goroutines diff --git a/util/goroutines/tracker.go b/util/goroutines/tracker.go index c2a0cb8c3a3ed..b0513ef4efa3f 100644 --- a/util/goroutines/tracker.go +++ b/util/goroutines/tracker.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package goroutines diff --git a/util/groupmember/groupmember.go b/util/groupmember/groupmember.go index d604168169022..090e1561dcded 100644 --- a/util/groupmember/groupmember.go +++ b/util/groupmember/groupmember.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package groupmember verifies group membership of the provided user on the diff --git a/util/hashx/block512.go b/util/hashx/block512.go index e637c0c030653..5f32f33a6c8d2 100644 --- a/util/hashx/block512.go +++ b/util/hashx/block512.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package hashx provides a concrete implementation of [hash.Hash] diff --git a/util/hashx/block512_test.go b/util/hashx/block512_test.go index ca3ee0d784514..91d5d9ee67749 100644 --- a/util/hashx/block512_test.go +++ b/util/hashx/block512_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package hashx diff --git a/util/httphdr/httphdr.go b/util/httphdr/httphdr.go index 852e28b8fae03..01e8eddc67ac1 100644 --- a/util/httphdr/httphdr.go +++ b/util/httphdr/httphdr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package httphdr implements functionality for parsing and formatting diff --git a/util/httphdr/httphdr_test.go b/util/httphdr/httphdr_test.go index 81feeaca080d8..37906a5bf6603 100644 --- a/util/httphdr/httphdr_test.go +++ b/util/httphdr/httphdr_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package httphdr diff --git a/util/httpm/httpm.go b/util/httpm/httpm.go index a9a691b8a69e2..f15912ecb772a 100644 --- a/util/httpm/httpm.go +++ b/util/httpm/httpm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package httpm has shorter names for HTTP method constants. diff --git a/util/httpm/httpm_test.go b/util/httpm/httpm_test.go index 0c71edc2f3c42..4e7f7b5ab277c 100644 --- a/util/httpm/httpm_test.go +++ b/util/httpm/httpm_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package httpm diff --git a/util/limiter/limiter.go b/util/limiter/limiter.go index b86efdf29cfd0..f48114d531fa4 100644 --- a/util/limiter/limiter.go +++ b/util/limiter/limiter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package limiter provides a keyed token bucket rate limiter. @@ -187,6 +187,9 @@ func (lm *Limiter[K]) collectDump(now time.Time) []dumpEntry[K] { lm.mu.Lock() defer lm.mu.Unlock() + if lm.cache == nil { + return nil + } ret := make([]dumpEntry[K], 0, lm.cache.Len()) lm.cache.ForEach(func(k K, v *bucket) { lm.updateBucketLocked(v, now) // so stats are accurate diff --git a/util/limiter/limiter_test.go b/util/limiter/limiter_test.go index 77b1d562b23fb..5210322bbd3b0 100644 --- a/util/limiter/limiter_test.go +++ b/util/limiter/limiter_test.go @@ -1,10 +1,11 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package limiter import ( "bytes" + "io" "strings" "testing" "time" @@ -175,6 +176,10 @@ func TestDumpHTML(t *testing.T) { } } +func TestDumpHTMLEmpty(t *testing.T) { + new(Limiter[string]).DumpHTML(io.Discard, false) // should not panic +} + func allowed(t *testing.T, limiter *Limiter[string], key string, count int, now time.Time) { t.Helper() for i := range count { diff --git a/util/lineiter/lineiter.go b/util/lineiter/lineiter.go index 5cb1eeef3ee1d..06d35909022b8 100644 --- a/util/lineiter/lineiter.go +++ b/util/lineiter/lineiter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lineiter iterates over lines in things. diff --git a/util/lineiter/lineiter_test.go b/util/lineiter/lineiter_test.go index 3373d5fe7b122..6e9e285501fab 100644 --- a/util/lineiter/lineiter_test.go +++ b/util/lineiter/lineiter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lineiter diff --git a/util/lineread/lineread.go b/util/lineread/lineread.go index 6b01d2b69ffd7..25cc63247e953 100644 --- a/util/lineread/lineread.go +++ b/util/lineread/lineread.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lineread reads lines from files. It's not fancy, but it got repetitive. diff --git a/util/linuxfw/detector.go b/util/linuxfw/detector.go index 149e0c96049c8..a3a1c1ddaa547 100644 --- a/util/linuxfw/detector.go +++ b/util/linuxfw/detector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/fake.go b/util/linuxfw/fake.go index d01849a2e5c9d..deeae87603f8a 100644 --- a/util/linuxfw/fake.go +++ b/util/linuxfw/fake.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -25,13 +25,15 @@ type fakeRule struct { func newFakeIPTables() *fakeIPTables { return &fakeIPTables{ n: map[string][]string{ - "filter/INPUT": nil, - "filter/OUTPUT": nil, - "filter/FORWARD": nil, - "nat/PREROUTING": nil, - "nat/OUTPUT": nil, - "nat/POSTROUTING": nil, - "mangle/FORWARD": nil, + "filter/INPUT": nil, + "filter/OUTPUT": nil, + "filter/FORWARD": nil, + "nat/PREROUTING": nil, + "nat/OUTPUT": nil, + "nat/POSTROUTING": nil, + "mangle/FORWARD": nil, + "mangle/PREROUTING": nil, + "mangle/OUTPUT": nil, }, } } @@ -81,7 +83,7 @@ func (n *fakeIPTables) Delete(table, chain string, args ...string) error { return nil } } - return fmt.Errorf("delete of unknown rule %q from %s", strings.Join(args, " "), k) + return errors.New("exitcode:1") } else { return fmt.Errorf("unknown table/chain %s", k) } diff --git a/util/linuxfw/fake_netfilter.go b/util/linuxfw/fake_netfilter.go index a998ed765fd63..eac5d904cff3a 100644 --- a/util/linuxfw/fake_netfilter.go +++ b/util/linuxfw/fake_netfilter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -71,6 +71,8 @@ func (f *FakeNetfilterRunner) AddHooks() error { retur func (f *FakeNetfilterRunner) DelHooks(logf logger.Logf) error { return nil } func (f *FakeNetfilterRunner) AddSNATRule() error { return nil } func (f *FakeNetfilterRunner) DelSNATRule() error { return nil } +func (f *FakeNetfilterRunner) AddConnmarkSaveRule() error { return nil } +func (f *FakeNetfilterRunner) DelConnmarkSaveRule() error { return nil } func (f *FakeNetfilterRunner) AddStatefulRule(tunname string) error { return nil } func (f *FakeNetfilterRunner) DelStatefulRule(tunname string) error { return nil } func (f *FakeNetfilterRunner) AddLoopbackRule(addr netip.Addr) error { return nil } diff --git a/util/linuxfw/helpers.go b/util/linuxfw/helpers.go index a4b9fdf402558..a369b6a8841b3 100644 --- a/util/linuxfw/helpers.go +++ b/util/linuxfw/helpers.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/iptables.go b/util/linuxfw/iptables.go index 76c5400becff8..f054e7abe1718 100644 --- a/util/linuxfw/iptables.go +++ b/util/linuxfw/iptables.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_iptables diff --git a/util/linuxfw/iptables_disabled.go b/util/linuxfw/iptables_disabled.go index 538e33647381a..c986fe7c206ea 100644 --- a/util/linuxfw/iptables_disabled.go +++ b/util/linuxfw/iptables_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && ts_omit_iptables diff --git a/util/linuxfw/iptables_for_svcs.go b/util/linuxfw/iptables_for_svcs.go index 2cd8716e4622b..acc2baf6c6fcf 100644 --- a/util/linuxfw/iptables_for_svcs.go +++ b/util/linuxfw/iptables_for_svcs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/iptables_for_svcs_test.go b/util/linuxfw/iptables_for_svcs_test.go index 0e56d70ba7078..b4dfe19c84c14 100644 --- a/util/linuxfw/iptables_for_svcs_test.go +++ b/util/linuxfw/iptables_for_svcs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/iptables_runner.go b/util/linuxfw/iptables_runner.go index 41116f019bf9c..117df3fc04c7f 100644 --- a/util/linuxfw/iptables_runner.go +++ b/util/linuxfw/iptables_runner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -539,6 +539,104 @@ func (i *iptablesRunner) DelStatefulRule(tunname string) error { return nil } +// AddConnmarkSaveRule adds conntrack marking rules to save and restore marks. +// These rules run in mangle/PREROUTING (to restore marks from conntrack) and +// mangle/OUTPUT (to save marks to conntrack) before rp_filter checks, enabling +// proper routing table lookups for exit nodes and subnet routers. +func (i *iptablesRunner) AddConnmarkSaveRule() error { + // Check if rules already exist (idempotency) + for _, ipt := range i.getTables() { + rules, err := ipt.List("mangle", "PREROUTING") + if err != nil { + continue + } + // Look for existing connmark restore rule + for _, rule := range rules { + if strings.Contains(rule, "CONNMARK") && + strings.Contains(rule, "restore-mark") && + strings.Contains(rule, "ctmask 0xff0000") { + // Rules already exist, skip adding + return nil + } + } + } + + // mangle/PREROUTING: Restore mark from conntrack for ESTABLISHED/RELATED connections + // This runs BEFORE routing decision and rp_filter check + for _, ipt := range i.getTables() { + args := []string{ + "-m", "conntrack", + "--ctstate", "ESTABLISHED,RELATED", + "-j", "CONNMARK", + "--restore-mark", + "--nfmask", fwmarkMask, + "--ctmask", fwmarkMask, + } + if err := ipt.Insert("mangle", "PREROUTING", 1, args...); err != nil { + return fmt.Errorf("adding %v in mangle/PREROUTING: %w", args, err) + } + } + + // mangle/OUTPUT: Save mark to conntrack for NEW connections with non-zero marks + for _, ipt := range i.getTables() { + args := []string{ + "-m", "conntrack", + "--ctstate", "NEW", + "-m", "mark", + "!", "--mark", "0x0/" + fwmarkMask, + "-j", "CONNMARK", + "--save-mark", + "--nfmask", fwmarkMask, + "--ctmask", fwmarkMask, + } + if err := ipt.Insert("mangle", "OUTPUT", 1, args...); err != nil { + return fmt.Errorf("adding %v in mangle/OUTPUT: %w", args, err) + } + } + + return nil +} + +// DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule. +func (i *iptablesRunner) DelConnmarkSaveRule() error { + for _, ipt := range i.getTables() { + // Delete PREROUTING rule + args := []string{ + "-m", "conntrack", + "--ctstate", "ESTABLISHED,RELATED", + "-j", "CONNMARK", + "--restore-mark", + "--nfmask", fwmarkMask, + "--ctmask", fwmarkMask, + } + if err := ipt.Delete("mangle", "PREROUTING", args...); err != nil { + if !isNotExistError(err) { + return fmt.Errorf("deleting connmark rule in mangle/PREROUTING: %w", err) + } + // Rule doesn't exist - this is fine for idempotency + } + + // Delete OUTPUT rule + args = []string{ + "-m", "conntrack", + "--ctstate", "NEW", + "-m", "mark", + "!", "--mark", "0x0/" + fwmarkMask, + "-j", "CONNMARK", + "--save-mark", + "--nfmask", fwmarkMask, + "--ctmask", fwmarkMask, + } + if err := ipt.Delete("mangle", "OUTPUT", args...); err != nil { + if !isNotExistError(err) { + return fmt.Errorf("deleting connmark rule in mangle/OUTPUT: %w", err) + } + // Rule doesn't exist - this is fine for idempotency + } + } + return nil +} + // buildMagicsockPortRule generates the string slice containing the arguments // to describe a rule accepting traffic on a particular port to iptables. It is // separated out here to avoid repetition in AddMagicsockPortRule and diff --git a/util/linuxfw/iptables_runner_test.go b/util/linuxfw/iptables_runner_test.go index ce905aef3f75b..77c753004a770 100644 --- a/util/linuxfw/iptables_runner_test.go +++ b/util/linuxfw/iptables_runner_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -364,3 +364,143 @@ func checkSNATRuleCount(t *testing.T, iptr *iptablesRunner, ip netip.Addr, wants t.Fatalf("wants %d rules, got %d", wantsRules, len(rules)) } } + +func TestAddAndDelConnmarkSaveRule(t *testing.T) { + preroutingArgs := []string{ + "-m", "conntrack", + "--ctstate", "ESTABLISHED,RELATED", + "-j", "CONNMARK", + "--restore-mark", + "--nfmask", "0xff0000", + "--ctmask", "0xff0000", + } + + outputArgs := []string{ + "-m", "conntrack", + "--ctstate", "NEW", + "-m", "mark", + "!", "--mark", "0x0/0xff0000", + "-j", "CONNMARK", + "--save-mark", + "--nfmask", "0xff0000", + "--ctmask", "0xff0000", + } + + t.Run("with_ipv6", func(t *testing.T) { + iptr := newFakeIPTablesRunner() + + // Add connmark rules + if err := iptr.AddConnmarkSaveRule(); err != nil { + t.Fatalf("AddConnmarkSaveRule failed: %v", err) + } + + // Verify rules exist in both IPv4 and IPv6 + for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} { + if exists, err := proto.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil { + t.Fatalf("error checking PREROUTING rule: %v", err) + } else if !exists { + t.Errorf("PREROUTING connmark rule doesn't exist") + } + + if exists, err := proto.Exists("mangle", "OUTPUT", outputArgs...); err != nil { + t.Fatalf("error checking OUTPUT rule: %v", err) + } else if !exists { + t.Errorf("OUTPUT connmark rule doesn't exist") + } + } + + // Test idempotency - calling AddConnmarkSaveRule again should not fail or duplicate + if err := iptr.AddConnmarkSaveRule(); err != nil { + t.Fatalf("AddConnmarkSaveRule (second call) failed: %v", err) + } + + // Verify rules still exist and weren't duplicated + for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} { + preroutingRules, err := proto.List("mangle", "PREROUTING") + if err != nil { + t.Fatalf("error listing PREROUTING rules: %v", err) + } + connmarkCount := 0 + for _, rule := range preroutingRules { + if strings.Contains(rule, "CONNMARK") && strings.Contains(rule, "restore-mark") { + connmarkCount++ + } + } + if connmarkCount != 1 { + t.Errorf("expected 1 PREROUTING connmark rule, got %d", connmarkCount) + } + } + + // Delete connmark rules + if err := iptr.DelConnmarkSaveRule(); err != nil { + t.Fatalf("DelConnmarkSaveRule failed: %v", err) + } + + // Verify rules are deleted + for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} { + if exists, err := proto.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil { + t.Fatalf("error checking PREROUTING rule: %v", err) + } else if exists { + t.Errorf("PREROUTING connmark rule still exists after deletion") + } + + if exists, err := proto.Exists("mangle", "OUTPUT", outputArgs...); err != nil { + t.Fatalf("error checking OUTPUT rule: %v", err) + } else if exists { + t.Errorf("OUTPUT connmark rule still exists after deletion") + } + } + + // Test idempotency of deletion + if err := iptr.DelConnmarkSaveRule(); err != nil { + t.Fatalf("DelConnmarkSaveRule (second call) failed: %v", err) + } + }) + + t.Run("without_ipv6", func(t *testing.T) { + // Create an iptables runner with only IPv4 (simulating system without IPv6) + iptr := &iptablesRunner{ + ipt4: newFakeIPTables(), + ipt6: nil, // IPv6 not available + v6Available: false, + v6NATAvailable: false, + v6FilterAvailable: false, + } + + // Add connmark rules should NOT panic with nil ipt6 + if err := iptr.AddConnmarkSaveRule(); err != nil { + t.Fatalf("AddConnmarkSaveRule failed with IPv6 disabled: %v", err) + } + + // Verify rules exist ONLY in IPv4 + if exists, err := iptr.ipt4.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil { + t.Fatalf("error checking IPv4 PREROUTING rule: %v", err) + } else if !exists { + t.Errorf("IPv4 PREROUTING connmark rule doesn't exist") + } + + if exists, err := iptr.ipt4.Exists("mangle", "OUTPUT", outputArgs...); err != nil { + t.Fatalf("error checking IPv4 OUTPUT rule: %v", err) + } else if !exists { + t.Errorf("IPv4 OUTPUT connmark rule doesn't exist") + } + + // Delete connmark rules should NOT panic with nil ipt6 + if err := iptr.DelConnmarkSaveRule(); err != nil { + t.Fatalf("DelConnmarkSaveRule failed with IPv6 disabled: %v", err) + } + + // Verify rules are deleted from IPv4 + if exists, err := iptr.ipt4.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil { + t.Fatalf("error checking IPv4 PREROUTING rule: %v", err) + } else if exists { + t.Errorf("IPv4 PREROUTING connmark rule still exists after deletion") + } + + if exists, err := iptr.ipt4.Exists("mangle", "OUTPUT", outputArgs...); err != nil { + t.Fatalf("error checking IPv4 OUTPUT rule: %v", err) + } else if exists { + t.Errorf("IPv4 OUTPUT connmark rule still exists after deletion") + } + }) +} diff --git a/util/linuxfw/linuxfw.go b/util/linuxfw/linuxfw.go index ec73aaceea03a..325a5809f8586 100644 --- a/util/linuxfw/linuxfw.go +++ b/util/linuxfw/linuxfw.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/linuxfwtest/linuxfwtest.go b/util/linuxfw/linuxfwtest/linuxfwtest.go index ee2cbd1b227f4..bf1477ad9b994 100644 --- a/util/linuxfw/linuxfwtest/linuxfwtest.go +++ b/util/linuxfw/linuxfwtest/linuxfwtest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo && linux diff --git a/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go b/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go index 6e95699001d4b..ec2d24d3521c9 100644 --- a/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go +++ b/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !cgo || !linux diff --git a/util/linuxfw/nftables.go b/util/linuxfw/nftables.go index 94ce51a1405a4..6059128a97c2f 100644 --- a/util/linuxfw/nftables.go +++ b/util/linuxfw/nftables.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO(#8502): add support for more architectures diff --git a/util/linuxfw/nftables_for_svcs.go b/util/linuxfw/nftables_for_svcs.go index 474b980869691..c2425e2ff285b 100644 --- a/util/linuxfw/nftables_for_svcs.go +++ b/util/linuxfw/nftables_for_svcs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/nftables_for_svcs_test.go b/util/linuxfw/nftables_for_svcs_test.go index 73472ce20cbe5..c3be3fc3b7bee 100644 --- a/util/linuxfw/nftables_for_svcs_test.go +++ b/util/linuxfw/nftables_for_svcs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/nftables_runner.go b/util/linuxfw/nftables_runner.go index d6b5e78842640..7de9b6cb05edd 100644 --- a/util/linuxfw/nftables_runner.go +++ b/util/linuxfw/nftables_runner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -521,6 +521,15 @@ type NetfilterRunner interface { // using conntrack. DelStatefulRule(tunname string) error + // AddConnmarkSaveRule adds conntrack marking rules to save marks from packets. + // These rules run in mangle/PREROUTING and mangle/OUTPUT to mark connections + // and restore marks on reply packets before rp_filter checks, enabling proper + // routing table lookups for exit nodes and subnet routers. + AddConnmarkSaveRule() error + + // DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule. + DelConnmarkSaveRule() error + // HasIPV6 reports true if the system supports IPv6. HasIPV6() bool @@ -1962,6 +1971,242 @@ func (n *nftablesRunner) DelStatefulRule(tunname string) error { return nil } +// makeConnmarkRestoreExprs creates nftables expressions to restore mark from conntrack. +// Implements: ct state established,related ct mark & 0xff0000 != 0 meta mark set ct mark & 0xff0000 +func makeConnmarkRestoreExprs() []expr.Any { + return []expr.Any{ + // Load conntrack state into register 1 + &expr.Ct{ + Register: 1, + Key: expr.CtKeySTATE, + }, + // Check if state is ESTABLISHED or RELATED + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: nativeUint32( + expr.CtStateBitESTABLISHED | + expr.CtStateBitRELATED), + Xor: nativeUint32(0), + }, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0, 0, 0, 0}, + }, + // Load conntrack mark into register 1 + &expr.Ct{ + Register: 1, + Key: expr.CtKeyMARK, + }, + // Mask to Tailscale mark bits (0xff0000) + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: getTailscaleFwmarkMask(), + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // Set packet mark from register 1 + &expr.Meta{ + Key: expr.MetaKeyMARK, + SourceRegister: true, + Register: 1, + }, + } +} + +// makeConnmarkSaveExprs creates nftables expressions to save mark to conntrack. +// Implements: ct state new meta mark & 0xff0000 != 0 ct mark set meta mark & 0xff0000 +func makeConnmarkSaveExprs() []expr.Any { + return []expr.Any{ + // Load conntrack state into register 1 + &expr.Ct{ + Register: 1, + Key: expr.CtKeySTATE, + }, + // Check if state is NEW + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: nativeUint32(expr.CtStateBitNEW), + Xor: nativeUint32(0), + }, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0, 0, 0, 0}, + }, + // Load packet mark into register 1 + &expr.Meta{ + Key: expr.MetaKeyMARK, + Register: 1, + }, + // Mask to Tailscale mark bits (0xff0000) + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: getTailscaleFwmarkMask(), + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // Check if mark is non-zero + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0, 0, 0, 0}, + }, + // Load packet mark again for saving + &expr.Meta{ + Key: expr.MetaKeyMARK, + Register: 1, + }, + // Mask again + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: getTailscaleFwmarkMask(), + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // Set conntrack mark from register 1 + &expr.Ct{ + Key: expr.CtKeyMARK, + SourceRegister: true, + Register: 1, + }, + } +} + +// AddConnmarkSaveRule adds conntrack marking rules to save and restore marks. +// These rules run in mangle/PREROUTING (to restore marks from conntrack) and +// mangle/OUTPUT (to save marks to conntrack) before rp_filter checks, enabling +// proper routing table lookups for exit nodes and subnet routers. +func (n *nftablesRunner) AddConnmarkSaveRule() error { + conn := n.conn + + // Check if rules already exist (idempotency) + for _, table := range n.getTables() { + mangleTable := &nftables.Table{ + Family: table.Proto, + Name: "mangle", + } + + // Check PREROUTING chain for restore rule + preroutingChain, err := getChainFromTable(conn, mangleTable, "PREROUTING") + if err == nil { + rules, _ := conn.GetRules(preroutingChain.Table, preroutingChain) + for _, rule := range rules { + if string(rule.UserData) == "ts-connmark-restore" { + // Rules already exist, skip adding + return nil + } + } + } + } + + // Add rules for both IPv4 and IPv6 + for _, table := range n.getTables() { + // Get or create mangle table + mangleTable := &nftables.Table{ + Family: table.Proto, + Name: "mangle", + } + conn.AddTable(mangleTable) + + // Get or create PREROUTING chain + preroutingChain, err := getChainFromTable(conn, mangleTable, "PREROUTING") + if err != nil { + // Chain doesn't exist, create it + preroutingChain = conn.AddChain(&nftables.Chain{ + Name: "PREROUTING", + Table: mangleTable, + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookPrerouting, + Priority: nftables.ChainPriorityMangle, + }) + } + + // Add PREROUTING rule to restore mark from conntrack + conn.InsertRule(&nftables.Rule{ + Table: mangleTable, + Chain: preroutingChain, + Exprs: makeConnmarkRestoreExprs(), + UserData: []byte("ts-connmark-restore"), + }) + + // Get or create OUTPUT chain + outputChain, err := getChainFromTable(conn, mangleTable, "OUTPUT") + if err != nil { + // Chain doesn't exist, create it + outputChain = conn.AddChain(&nftables.Chain{ + Name: "OUTPUT", + Table: mangleTable, + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookOutput, + Priority: nftables.ChainPriorityMangle, + }) + } + + // Add OUTPUT rule to save mark to conntrack + conn.InsertRule(&nftables.Rule{ + Table: mangleTable, + Chain: outputChain, + Exprs: makeConnmarkSaveExprs(), + UserData: []byte("ts-connmark-save"), + }) + } + + if err := conn.Flush(); err != nil { + return fmt.Errorf("flush add connmark rules: %w", err) + } + + return nil +} + +// DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule. +func (n *nftablesRunner) DelConnmarkSaveRule() error { + conn := n.conn + + for _, table := range n.getTables() { + mangleTable := &nftables.Table{ + Family: table.Proto, + Name: "mangle", + } + + // Remove PREROUTING rule - look for restore-mark rule by UserData + preroutingChain, err := getChainFromTable(conn, mangleTable, "PREROUTING") + if err == nil { + rules, _ := conn.GetRules(preroutingChain.Table, preroutingChain) + for _, rule := range rules { + if string(rule.UserData) == "ts-connmark-restore" { + conn.DelRule(rule) + break + } + } + } + + // Remove OUTPUT rule - look for save-mark rule by UserData + outputChain, err := getChainFromTable(conn, mangleTable, "OUTPUT") + if err == nil { + rules, _ := conn.GetRules(outputChain.Table, outputChain) + for _, rule := range rules { + if string(rule.UserData) == "ts-connmark-save" { + conn.DelRule(rule) + break + } + } + } + } + + // Ignore errors during deletion - rules might not exist + conn.Flush() + + return nil +} + // cleanupChain removes a jump rule from hookChainName to tsChainName, and then // the entire chain tsChainName. Errors are logged, but attempts to remove both // the jump rule and chain continue even if one errors. diff --git a/util/linuxfw/nftables_runner_test.go b/util/linuxfw/nftables_runner_test.go index 6fb180ed67ce6..8299a9cbd72da 100644 --- a/util/linuxfw/nftables_runner_test.go +++ b/util/linuxfw/nftables_runner_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux @@ -1070,3 +1070,246 @@ func checkSNATRule_nft(t *testing.T, runner *nftablesRunner, fam nftables.TableF wantsRule := snatRule(chain.Table, chain, src, dst, meta) checkRule(t, wantsRule, runner.conn) } + +// TestNFTAddAndDelConnmarkRules tests adding and removing connmark rules +// in a real network namespace. This verifies the rules are correctly created +// and cleaned up. +func TestNFTAddAndDelConnmarkRules(t *testing.T) { + conn := newSysConn(t) + runner := newFakeNftablesRunnerWithConn(t, conn, true) + + // Helper to get mangle chains + getMangleChains := func(fam nftables.TableFamily) (prerouting, output *nftables.Chain, err error) { + chains, err := conn.ListChainsOfTableFamily(fam) + if err != nil { + return nil, nil, err + } + for _, ch := range chains { + if ch.Table.Name != "mangle" { + continue + } + if ch.Name == "PREROUTING" { + prerouting = ch + } else if ch.Name == "OUTPUT" { + output = ch + } + } + return prerouting, output, nil + } + + // Check initial state - mangle chains might not exist yet + prerouting4Before, output4Before, _ := getMangleChains(nftables.TableFamilyIPv4) + prerouting6Before, output6Before, _ := getMangleChains(nftables.TableFamilyIPv6) + + var prerouting4RulesBefore, output4RulesBefore, prerouting6RulesBefore, output6RulesBefore int + if prerouting4Before != nil { + rules, _ := conn.GetRules(prerouting4Before.Table, prerouting4Before) + prerouting4RulesBefore = len(rules) + } + if output4Before != nil { + rules, _ := conn.GetRules(output4Before.Table, output4Before) + output4RulesBefore = len(rules) + } + if prerouting6Before != nil { + rules, _ := conn.GetRules(prerouting6Before.Table, prerouting6Before) + prerouting6RulesBefore = len(rules) + } + if output6Before != nil { + rules, _ := conn.GetRules(output6Before.Table, output6Before) + output6RulesBefore = len(rules) + } + + // Add connmark rules + if err := runner.AddConnmarkSaveRule(); err != nil { + t.Fatalf("AddConnmarkSaveRule() failed: %v", err) + } + + // Verify rules were added + prerouting4After, output4After, err := getMangleChains(nftables.TableFamilyIPv4) + if err != nil { + t.Fatalf("Failed to get IPv4 mangle chains: %v", err) + } + if prerouting4After == nil || output4After == nil { + t.Fatal("IPv4 mangle chains not created") + } + + prerouting4Rules, err := conn.GetRules(prerouting4After.Table, prerouting4After) + if err != nil { + t.Fatalf("GetRules(PREROUTING) failed: %v", err) + } + output4Rules, err := conn.GetRules(output4After.Table, output4After) + if err != nil { + t.Fatalf("GetRules(OUTPUT) failed: %v", err) + } + + // Should have added 1 rule to each chain + if len(prerouting4Rules) != prerouting4RulesBefore+1 { + t.Fatalf("PREROUTING rules: got %d, want %d", len(prerouting4Rules), prerouting4RulesBefore+1) + } + if len(output4Rules) != output4RulesBefore+1 { + t.Fatalf("OUTPUT rules: got %d, want %d", len(output4Rules), output4RulesBefore+1) + } + + // Verify IPv6 rules + prerouting6After, output6After, err := getMangleChains(nftables.TableFamilyIPv6) + if err != nil { + t.Fatalf("Failed to get IPv6 mangle chains: %v", err) + } + if prerouting6After == nil || output6After == nil { + t.Fatal("IPv6 mangle chains not created") + } + + prerouting6Rules, err := conn.GetRules(prerouting6After.Table, prerouting6After) + if err != nil { + t.Fatalf("GetRules(IPv6 PREROUTING) failed: %v", err) + } + output6Rules, err := conn.GetRules(output6After.Table, output6After) + if err != nil { + t.Fatalf("GetRules(IPv6 OUTPUT) failed: %v", err) + } + + if len(prerouting6Rules) != prerouting6RulesBefore+1 { + t.Fatalf("IPv6 PREROUTING rules: got %d, want %d", len(prerouting6Rules), prerouting6RulesBefore+1) + } + if len(output6Rules) != output6RulesBefore+1 { + t.Fatalf("IPv6 OUTPUT rules: got %d, want %d", len(output6Rules), output6RulesBefore+1) + } + + // Verify the rules contain conntrack expressions + foundCtInPrerouting := false + foundCtInOutput := false + for _, e := range prerouting4Rules[0].Exprs { + if _, ok := e.(*expr.Ct); ok { + foundCtInPrerouting = true + break + } + } + for _, e := range output4Rules[0].Exprs { + if _, ok := e.(*expr.Ct); ok { + foundCtInOutput = true + break + } + } + if !foundCtInPrerouting { + t.Error("PREROUTING rule doesn't contain conntrack expression") + } + if !foundCtInOutput { + t.Error("OUTPUT rule doesn't contain conntrack expression") + } + + // Delete connmark rules + if err := runner.DelConnmarkSaveRule(); err != nil { + t.Fatalf("DelConnmarkSaveRule() failed: %v", err) + } + + // Verify rules were deleted + prerouting4After, output4After, _ = getMangleChains(nftables.TableFamilyIPv4) + if prerouting4After != nil { + rules, _ := conn.GetRules(prerouting4After.Table, prerouting4After) + if len(rules) != prerouting4RulesBefore { + t.Fatalf("IPv4 PREROUTING rules after delete: got %d, want %d", len(rules), prerouting4RulesBefore) + } + } + if output4After != nil { + rules, _ := conn.GetRules(output4After.Table, output4After) + if len(rules) != output4RulesBefore { + t.Fatalf("IPv4 OUTPUT rules after delete: got %d, want %d", len(rules), output4RulesBefore) + } + } + + prerouting6After, output6After, _ = getMangleChains(nftables.TableFamilyIPv6) + if prerouting6After != nil { + rules, _ := conn.GetRules(prerouting6After.Table, prerouting6After) + if len(rules) != prerouting6RulesBefore { + t.Fatalf("IPv6 PREROUTING rules after delete: got %d, want %d", len(rules), prerouting6RulesBefore) + } + } + if output6After != nil { + rules, _ := conn.GetRules(output6After.Table, output6After) + if len(rules) != output6RulesBefore { + t.Fatalf("IPv6 OUTPUT rules after delete: got %d, want %d", len(rules), output6RulesBefore) + } + } +} + +// TestMakeConnmarkRestoreExprs tests the nftables expressions for restoring +// marks from conntrack. This is a regression test that ensures the byte encoding +// doesn't change unexpectedly. +func TestMakeConnmarkRestoreExprs(t *testing.T) { + // Expected netlink bytes for the restore rule + // Generated by running makeConnmarkRestoreExprs() and capturing the output + want := [][]byte{ + // batch begin + []byte("\x00\x00\x00\x0a"), + // nft add table ip mangle + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), + // nft add chain ip mangle PREROUTING { type filter hook prerouting priority mangle; } + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x03\x00\x50\x52\x45\x52\x4f\x55\x54\x49\x4e\x47\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\xff\xff\xff\x6a\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), + // nft add rule ip mangle PREROUTING ct state established,related ct mark & 0xff0000 != 0 meta mark set ct mark & 0xff0000 + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x02\x00\x50\x52\x45\x52\x4f\x55\x54\x49\x4e\x47\x00\x00\x1c\x01\x04\x80\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x06\x00\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x00\x00\x00\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x03\x00\x00\x00\x00\x01"), + // batch end + []byte("\x00\x00\x00\x0a"), + } + + testConn := newTestConn(t, want, nil) + table := testConn.AddTable(&nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: "mangle", + }) + chain := testConn.AddChain(&nftables.Chain{ + Name: "PREROUTING", + Table: table, + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookPrerouting, + Priority: nftables.ChainPriorityMangle, + }) + testConn.InsertRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: makeConnmarkRestoreExprs(), + }) + if err := testConn.Flush(); err != nil { + t.Fatalf("Flush() failed: %v", err) + } +} + +// TestMakeConnmarkSaveExprs tests the nftables expressions for saving marks +// to conntrack. This is a regression test that ensures the byte encoding +// doesn't change unexpectedly. +func TestMakeConnmarkSaveExprs(t *testing.T) { + // Expected netlink bytes for the save rule + // Generated by running makeConnmarkSaveExprs() and capturing the output + want := [][]byte{ + // batch begin + []byte("\x00\x00\x00\x0a"), + // nft add table ip mangle + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), + // nft add chain ip mangle OUTPUT { type route hook output priority mangle; } + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0b\x00\x03\x00\x4f\x55\x54\x50\x55\x54\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x03\x08\x00\x02\x00\xff\xff\xff\x6a\x0a\x00\x07\x00\x72\x6f\x75\x74\x65\x00\x00\x00"), + // nft add rule ip mangle OUTPUT ct state new meta mark & 0xff0000 != 0 ct mark set meta mark & 0xff0000 + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0b\x00\x02\x00\x4f\x55\x54\x50\x55\x54\x00\x00\xb0\x01\x04\x80\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x08\x00\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x04\x00\x00\x00\x00\x01"), + // batch end + []byte("\x00\x00\x00\x0a"), + } + + testConn := newTestConn(t, want, nil) + table := testConn.AddTable(&nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: "mangle", + }) + chain := testConn.AddChain(&nftables.Chain{ + Name: "OUTPUT", + Table: table, + Type: nftables.ChainTypeRoute, + Hooknum: nftables.ChainHookOutput, + Priority: nftables.ChainPriorityMangle, + }) + testConn.InsertRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: makeConnmarkSaveExprs(), + }) + if err := testConn.Flush(); err != nil { + t.Fatalf("Flush() failed: %v", err) + } +} diff --git a/util/linuxfw/nftables_types.go b/util/linuxfw/nftables_types.go index b6e24d2a67b5b..27c5ee5981e13 100644 --- a/util/linuxfw/nftables_types.go +++ b/util/linuxfw/nftables_types.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO(#8502): add support for more architectures diff --git a/util/lru/lru.go b/util/lru/lru.go index 8e4dd417b98d2..7fb191535dcce 100644 --- a/util/lru/lru.go +++ b/util/lru/lru.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lru contains a typed Least-Recently-Used cache. diff --git a/util/lru/lru_test.go b/util/lru/lru_test.go index 04de2e5070c87..5fbc718b1decd 100644 --- a/util/lru/lru_test.go +++ b/util/lru/lru_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lru diff --git a/util/mak/mak.go b/util/mak/mak.go index fbdb40b0afd21..97daab98a7650 100644 --- a/util/mak/mak.go +++ b/util/mak/mak.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mak helps make maps. It contains generic helpers to make/assign diff --git a/util/mak/mak_test.go b/util/mak/mak_test.go index e47839a3c8fe9..7a4090c20292c 100644 --- a/util/mak/mak_test.go +++ b/util/mak/mak_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mak contains code to help make things. diff --git a/util/multierr/multierr.go b/util/multierr/multierr.go index 93ca068f56532..3acdb7d773222 100644 --- a/util/multierr/multierr.go +++ b/util/multierr/multierr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package multierr provides a simple multiple-error type. diff --git a/util/multierr/multierr_test.go b/util/multierr/multierr_test.go index de7721a665f40..35195b3770db1 100644 --- a/util/multierr/multierr_test.go +++ b/util/multierr/multierr_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package multierr_test diff --git a/util/must/must.go b/util/must/must.go index a292da2268c27..6a4b519361f2f 100644 --- a/util/must/must.go +++ b/util/must/must.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package must assists in calling functions that must succeed. diff --git a/util/nocasemaps/nocase.go b/util/nocasemaps/nocase.go index 2d91d8fe96a7a..737ab5de7c3bb 100644 --- a/util/nocasemaps/nocase.go +++ b/util/nocasemaps/nocase.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // nocasemaps provides efficient functions to set and get entries in Go maps diff --git a/util/nocasemaps/nocase_test.go b/util/nocasemaps/nocase_test.go index 5275b3ee6ef23..cae36242c3040 100644 --- a/util/nocasemaps/nocase_test.go +++ b/util/nocasemaps/nocase_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package nocasemaps diff --git a/util/osdiag/internal/wsc/wsc_windows.go b/util/osdiag/internal/wsc/wsc_windows.go index b402946eda4d2..8bc43ac54bbb9 100644 --- a/util/osdiag/internal/wsc/wsc_windows.go +++ b/util/osdiag/internal/wsc/wsc_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by 'go generate'; DO NOT EDIT. diff --git a/util/osdiag/mksyscall.go b/util/osdiag/mksyscall.go index bcbe113b051cd..688e0a31a7cc7 100644 --- a/util/osdiag/mksyscall.go +++ b/util/osdiag/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osdiag diff --git a/util/osdiag/osdiag.go b/util/osdiag/osdiag.go index 2ebecbdbf74a2..9845bd3f8be46 100644 --- a/util/osdiag/osdiag.go +++ b/util/osdiag/osdiag.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package osdiag provides loggers for OS-specific diagnostic information. diff --git a/util/osdiag/osdiag_notwindows.go b/util/osdiag/osdiag_notwindows.go index 0e46c97e50803..72237438b480b 100644 --- a/util/osdiag/osdiag_notwindows.go +++ b/util/osdiag/osdiag_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/util/osdiag/osdiag_windows.go b/util/osdiag/osdiag_windows.go index 5dcce3beaf76e..d6ba1d30bb674 100644 --- a/util/osdiag/osdiag_windows.go +++ b/util/osdiag/osdiag_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osdiag diff --git a/util/osdiag/osdiag_windows_test.go b/util/osdiag/osdiag_windows_test.go index b29b602ccb73c..f285f80feac43 100644 --- a/util/osdiag/osdiag_windows_test.go +++ b/util/osdiag/osdiag_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osdiag diff --git a/util/osshare/filesharingstatus_noop.go b/util/osshare/filesharingstatus_noop.go index 7f2b131904ea9..22f0a33785131 100644 --- a/util/osshare/filesharingstatus_noop.go +++ b/util/osshare/filesharingstatus_noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/util/osshare/filesharingstatus_windows.go b/util/osshare/filesharingstatus_windows.go index c125de15990c3..d21c394d0a27c 100644 --- a/util/osshare/filesharingstatus_windows.go +++ b/util/osshare/filesharingstatus_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package osshare provides utilities for enabling/disabling Taildrop file diff --git a/util/osuser/group_ids.go b/util/osuser/group_ids.go index 7c2b5b090cbcc..2a1f147d87b00 100644 --- a/util/osuser/group_ids.go +++ b/util/osuser/group_ids.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osuser diff --git a/util/osuser/group_ids_test.go b/util/osuser/group_ids_test.go index 69e8336ea6872..79e189ed8c866 100644 --- a/util/osuser/group_ids_test.go +++ b/util/osuser/group_ids_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osuser diff --git a/util/osuser/user.go b/util/osuser/user.go index 8b96194d716ce..2de3da762739d 100644 --- a/util/osuser/user.go +++ b/util/osuser/user.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package osuser implements OS user lookup. It's a wrapper around os/user that diff --git a/util/pidowner/pidowner.go b/util/pidowner/pidowner.go index 56bb640b785dd..cec92ba367e49 100644 --- a/util/pidowner/pidowner.go +++ b/util/pidowner/pidowner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package pidowner handles lookups from process ID to its owning user. diff --git a/util/pidowner/pidowner_linux.go b/util/pidowner/pidowner_linux.go index a07f512427062..f3f5cd97ddcb2 100644 --- a/util/pidowner/pidowner_linux.go +++ b/util/pidowner/pidowner_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package pidowner diff --git a/util/pidowner/pidowner_noimpl.go b/util/pidowner/pidowner_noimpl.go index 50add492fda76..4bc665d61071e 100644 --- a/util/pidowner/pidowner_noimpl.go +++ b/util/pidowner/pidowner_noimpl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !linux diff --git a/util/pidowner/pidowner_test.go b/util/pidowner/pidowner_test.go index 19c9ab46dff01..2774a8ab0fe36 100644 --- a/util/pidowner/pidowner_test.go +++ b/util/pidowner/pidowner_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package pidowner diff --git a/util/pidowner/pidowner_windows.go b/util/pidowner/pidowner_windows.go index dbf13ac8135f1..8edd7698d4207 100644 --- a/util/pidowner/pidowner_windows.go +++ b/util/pidowner/pidowner_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package pidowner diff --git a/util/pool/pool.go b/util/pool/pool.go index 7014751e7ab77..7042fb893a59e 100644 --- a/util/pool/pool.go +++ b/util/pool/pool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package pool contains a generic type for managing a pool of resources; for diff --git a/util/pool/pool_test.go b/util/pool/pool_test.go index 9d8eacbcb9d0b..ac7cf86be3ef7 100644 --- a/util/pool/pool_test.go +++ b/util/pool/pool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package pool diff --git a/util/precompress/precompress.go b/util/precompress/precompress.go index 6d1a26efdd767..80aed36821b2e 100644 --- a/util/precompress/precompress.go +++ b/util/precompress/precompress.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package precompress provides build- and serving-time support for diff --git a/util/progresstracking/progresstracking.go b/util/progresstracking/progresstracking.go index a9411fb46f7fd..21cbfa52ba3ff 100644 --- a/util/progresstracking/progresstracking.go +++ b/util/progresstracking/progresstracking.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package progresstracking provides wrappers around io.Reader and io.Writer diff --git a/util/prompt/prompt.go b/util/prompt/prompt.go index a6d86fb481769..b84993a0aeed9 100644 --- a/util/prompt/prompt.go +++ b/util/prompt/prompt.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package prompt provides a simple way to prompt the user for input. diff --git a/util/qrcodes/format.go b/util/qrcodes/format.go index dbd565b2ec9d3..99b58ff747fde 100644 --- a/util/qrcodes/format.go +++ b/util/qrcodes/format.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package qrcodes diff --git a/util/qrcodes/qrcodes.go b/util/qrcodes/qrcodes.go index 02e06e59b4be3..dc16fee8cfd2a 100644 --- a/util/qrcodes/qrcodes.go +++ b/util/qrcodes/qrcodes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_qrcodes diff --git a/util/qrcodes/qrcodes_disabled.go b/util/qrcodes/qrcodes_disabled.go index fa1b89cf437ef..bda8957573780 100644 --- a/util/qrcodes/qrcodes_disabled.go +++ b/util/qrcodes/qrcodes_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_qrcodes diff --git a/util/qrcodes/qrcodes_linux.go b/util/qrcodes/qrcodes_linux.go index 8f0d40f0a5e4a..474e231e23aff 100644 --- a/util/qrcodes/qrcodes_linux.go +++ b/util/qrcodes/qrcodes_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_qrcodes diff --git a/util/qrcodes/qrcodes_notlinux.go b/util/qrcodes/qrcodes_notlinux.go index 3149a60605bf3..4a7b493ff55a9 100644 --- a/util/qrcodes/qrcodes_notlinux.go +++ b/util/qrcodes/qrcodes_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !ts_omit_qrcodes diff --git a/util/quarantine/quarantine.go b/util/quarantine/quarantine.go index 7ad65a81d69ee..48c032d06cfcb 100644 --- a/util/quarantine/quarantine.go +++ b/util/quarantine/quarantine.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package quarantine sets platform specific "quarantine" attributes on files diff --git a/util/quarantine/quarantine_darwin.go b/util/quarantine/quarantine_darwin.go index 35405d9cc7a87..de1bbf70df985 100644 --- a/util/quarantine/quarantine_darwin.go +++ b/util/quarantine/quarantine_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package quarantine diff --git a/util/quarantine/quarantine_default.go b/util/quarantine/quarantine_default.go index 65954a4d25415..5158bda54314b 100644 --- a/util/quarantine/quarantine_default.go +++ b/util/quarantine/quarantine_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !darwin && !windows diff --git a/util/quarantine/quarantine_windows.go b/util/quarantine/quarantine_windows.go index 6fdf4e699b75b..886b2202a4beb 100644 --- a/util/quarantine/quarantine_windows.go +++ b/util/quarantine/quarantine_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package quarantine diff --git a/util/race/race.go b/util/race/race.go index 26c8e13eb468e..8e339dad2fd03 100644 --- a/util/race/race.go +++ b/util/race/race.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package race contains a helper to "race" two functions, returning the first diff --git a/util/race/race_test.go b/util/race/race_test.go index d3838271226ac..90b049909ce3c 100644 --- a/util/race/race_test.go +++ b/util/race/race_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package race diff --git a/util/racebuild/off.go b/util/racebuild/off.go index 8f4fe998fb4bb..2ffe9fd5370e5 100644 --- a/util/racebuild/off.go +++ b/util/racebuild/off.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !race diff --git a/util/racebuild/on.go b/util/racebuild/on.go index 69ae2bcae4239..794171c55a792 100644 --- a/util/racebuild/on.go +++ b/util/racebuild/on.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build race diff --git a/util/racebuild/racebuild.go b/util/racebuild/racebuild.go index d061276cb8a0a..9dc0fb9f77ce9 100644 --- a/util/racebuild/racebuild.go +++ b/util/racebuild/racebuild.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package racebuild exports a constant about whether the current binary diff --git a/util/rands/cheap.go b/util/rands/cheap.go index 69785e086e664..f3b931d34662b 100644 --- a/util/rands/cheap.go +++ b/util/rands/cheap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Copyright 2009 The Go Authors. All rights reserved. diff --git a/util/rands/cheap_test.go b/util/rands/cheap_test.go index 756b55b4e0ddc..874592a1b647e 100644 --- a/util/rands/cheap_test.go +++ b/util/rands/cheap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rands diff --git a/util/rands/rands.go b/util/rands/rands.go index d83e1e55898dc..94c6e6f4a1c29 100644 --- a/util/rands/rands.go +++ b/util/rands/rands.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package rands contains utility functions for randomness. diff --git a/util/rands/rands_test.go b/util/rands/rands_test.go index 5813f2bb46763..81cdf3bec02e0 100644 --- a/util/rands/rands_test.go +++ b/util/rands/rands_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rands diff --git a/util/reload/reload.go b/util/reload/reload.go index f18f9ebd1028c..edcb90c12a3f2 100644 --- a/util/reload/reload.go +++ b/util/reload/reload.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package reload contains functions that allow periodically reloading a value diff --git a/util/reload/reload_test.go b/util/reload/reload_test.go index f6a38168659cd..7e7963c3f7a9e 100644 --- a/util/reload/reload_test.go +++ b/util/reload/reload_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package reload diff --git a/util/ringlog/ringlog.go b/util/ringlog/ringlog.go index 62dfbae5bd5c3..d8197dda84deb 100644 --- a/util/ringlog/ringlog.go +++ b/util/ringlog/ringlog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ringlog contains a limited-size concurrency-safe generic ring log. diff --git a/util/ringlog/ringlog_test.go b/util/ringlog/ringlog_test.go index d6776e181a4f8..8ecf99cd0f3f1 100644 --- a/util/ringlog/ringlog_test.go +++ b/util/ringlog/ringlog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ringlog diff --git a/util/safediff/diff.go b/util/safediff/diff.go index cf8add94b21dd..c9a2c60bea1a8 100644 --- a/util/safediff/diff.go +++ b/util/safediff/diff.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package safediff computes the difference between two lists. diff --git a/util/safediff/diff_test.go b/util/safediff/diff_test.go index e580bd9222dd9..4251d788b10fe 100644 --- a/util/safediff/diff_test.go +++ b/util/safediff/diff_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safediff diff --git a/util/set/handle.go b/util/set/handle.go index 9c6b6dab0549b..1ad86d1fd307e 100644 --- a/util/set/handle.go +++ b/util/set/handle.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/intset.go b/util/set/intset.go index d325246914488..04f614742e796 100644 --- a/util/set/intset.go +++ b/util/set/intset.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/intset_test.go b/util/set/intset_test.go index d838215c97848..6cbf5a0bb472b 100644 --- a/util/set/intset_test.go +++ b/util/set/intset_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/set.go b/util/set/set.go index eb0697536f73b..c3d2350a7e6ba 100644 --- a/util/set/set.go +++ b/util/set/set.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package set contains set types. @@ -7,6 +7,8 @@ package set import ( "encoding/json" "maps" + "reflect" + "sort" ) // Set is a set of T. @@ -53,16 +55,53 @@ func (s *Set[T]) Make() { } } -// Slice returns the elements of the set as a slice. The elements will not be -// in any particular order. +// Slice returns the elements of the set as a slice. If the element type is +// ordered (integers, floats, or strings), the elements are returned in sorted +// order. Otherwise, the order is not defined. func (s Set[T]) Slice() []T { es := make([]T, 0, s.Len()) for k := range s { es = append(es, k) } + if f := genOrderedSwapper(reflect.TypeFor[T]()); f != nil { + sort.Slice(es, f(reflect.ValueOf(es))) + } return es } +// genOrderedSwapper returns a generator for a swap function that can be used to +// sort a slice of the given type. If rt is not an ordered type, +// genOrderedSwapper returns nil. +func genOrderedSwapper(rt reflect.Type) func(reflect.Value) func(i, j int) bool { + switch rt.Kind() { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return func(rv reflect.Value) func(i, j int) bool { + return func(i, j int) bool { + return rv.Index(i).Uint() < rv.Index(j).Uint() + } + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return func(rv reflect.Value) func(i, j int) bool { + return func(i, j int) bool { + return rv.Index(i).Int() < rv.Index(j).Int() + } + } + case reflect.Float32, reflect.Float64: + return func(rv reflect.Value) func(i, j int) bool { + return func(i, j int) bool { + return rv.Index(i).Float() < rv.Index(j).Float() + } + } + case reflect.String: + return func(rv reflect.Value) func(i, j int) bool { + return func(i, j int) bool { + return rv.Index(i).String() < rv.Index(j).String() + } + } + } + return nil +} + // Delete removes e from the set. func (s Set[T]) Delete(e T) { delete(s, e) } diff --git a/util/set/set_test.go b/util/set/set_test.go index 85913ad24a216..2188cbb4ddff7 100644 --- a/util/set/set_test.go +++ b/util/set/set_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set @@ -159,6 +159,39 @@ func TestSetJSONRoundTrip(t *testing.T) { } } +func checkSliceSorted[T comparable](t *testing.T, s Set[T], want []T) { + t.Helper() + got := s.Slice() + if !slices.Equal(got, want) { + t.Errorf("got %v; want %v", got, want) + } +} + +func TestSliceSorted(t *testing.T) { + t.Run("int", func(t *testing.T) { + checkSliceSorted(t, Of(3, 1, 4, 1, 5), []int{1, 3, 4, 5}) + }) + t.Run("int8", func(t *testing.T) { + checkSliceSorted(t, Of[int8](-1, 3, -100, 50), []int8{-100, -1, 3, 50}) + }) + t.Run("uint16", func(t *testing.T) { + checkSliceSorted(t, Of[uint16](300, 1, 65535, 0), []uint16{0, 1, 300, 65535}) + }) + t.Run("float64", func(t *testing.T) { + checkSliceSorted(t, Of(2.7, 1.0, 3.14), []float64{1.0, 2.7, 3.14}) + }) + t.Run("float32", func(t *testing.T) { + checkSliceSorted(t, Of[float32](2.5, 1.0, 3.0), []float32{1.0, 2.5, 3.0}) + }) + t.Run("string", func(t *testing.T) { + checkSliceSorted(t, Of("banana", "apple", "cherry"), []string{"apple", "banana", "cherry"}) + }) + t.Run("named-uint", func(t *testing.T) { + type Port uint16 + checkSliceSorted(t, Of[Port](443, 80, 8080), []Port{80, 443, 8080}) + }) +} + func TestMake(t *testing.T) { var s Set[int] s.Make() diff --git a/util/set/slice.go b/util/set/slice.go index 2fc65b82d1c6e..921da4fa21622 100644 --- a/util/set/slice.go +++ b/util/set/slice.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/slice_test.go b/util/set/slice_test.go index 9134c296292d3..468ba686c5772 100644 --- a/util/set/slice_test.go +++ b/util/set/slice_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/smallset.go b/util/set/smallset.go index 1b77419d27dc9..da52dd265a939 100644 --- a/util/set/smallset.go +++ b/util/set/smallset.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/smallset_test.go b/util/set/smallset_test.go index d6f446df08e81..019a9d24d1f5c 100644 --- a/util/set/smallset_test.go +++ b/util/set/smallset_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/singleflight/singleflight.go b/util/singleflight/singleflight.go index 9df47448b70ab..23cf7e21fec15 100644 --- a/util/singleflight/singleflight.go +++ b/util/singleflight/singleflight.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Copyright 2013 The Go Authors. All rights reserved. diff --git a/util/singleflight/singleflight_test.go b/util/singleflight/singleflight_test.go index 031922736fab6..9f0ca7f1de853 100644 --- a/util/singleflight/singleflight_test.go +++ b/util/singleflight/singleflight_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Copyright 2013 The Go Authors. All rights reserved. diff --git a/util/slicesx/slicesx.go b/util/slicesx/slicesx.go index ff9d473759fb0..660110a3c4f28 100644 --- a/util/slicesx/slicesx.go +++ b/util/slicesx/slicesx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package slicesx contains some helpful generic slice functions. diff --git a/util/slicesx/slicesx_test.go b/util/slicesx/slicesx_test.go index 34644928465d8..d5c87a3727748 100644 --- a/util/slicesx/slicesx_test.go +++ b/util/slicesx/slicesx_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package slicesx diff --git a/util/stringsx/stringsx.go b/util/stringsx/stringsx.go index 6c7a8d20d4221..5afea98a6a7c6 100644 --- a/util/stringsx/stringsx.go +++ b/util/stringsx/stringsx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package stringsx provides additional string manipulation functions diff --git a/util/stringsx/stringsx_test.go b/util/stringsx/stringsx_test.go index 8575c0b278fca..afce987c08a53 100644 --- a/util/stringsx/stringsx_test.go +++ b/util/stringsx/stringsx_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package stringsx diff --git a/util/syspolicy/internal/internal.go b/util/syspolicy/internal/internal.go index 6ab147de6d096..4179f26c82cb2 100644 --- a/util/syspolicy/internal/internal.go +++ b/util/syspolicy/internal/internal.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package internal contains miscellaneous functions and types diff --git a/util/syspolicy/internal/loggerx/logger.go b/util/syspolicy/internal/loggerx/logger.go index d1f48cbb428fe..412616cb132cd 100644 --- a/util/syspolicy/internal/loggerx/logger.go +++ b/util/syspolicy/internal/loggerx/logger.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package loggerx provides logging functions to the rest of the syspolicy packages. diff --git a/util/syspolicy/internal/loggerx/logger_test.go b/util/syspolicy/internal/loggerx/logger_test.go index 9735b5d30c20b..5c8fb7e2860d5 100644 --- a/util/syspolicy/internal/loggerx/logger_test.go +++ b/util/syspolicy/internal/loggerx/logger_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package loggerx diff --git a/util/syspolicy/internal/metrics/metrics.go b/util/syspolicy/internal/metrics/metrics.go index 8f27456735ca6..8a3b5327fbab8 100644 --- a/util/syspolicy/internal/metrics/metrics.go +++ b/util/syspolicy/internal/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package metrics provides logging and reporting for policy settings and scopes. diff --git a/util/syspolicy/internal/metrics/metrics_test.go b/util/syspolicy/internal/metrics/metrics_test.go index a99938769712f..ce9dea98b86d7 100644 --- a/util/syspolicy/internal/metrics/metrics_test.go +++ b/util/syspolicy/internal/metrics/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/util/syspolicy/internal/metrics/test_handler.go b/util/syspolicy/internal/metrics/test_handler.go index 36c3f2cad876a..1ec0c9f4c3015 100644 --- a/util/syspolicy/internal/metrics/test_handler.go +++ b/util/syspolicy/internal/metrics/test_handler.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/util/syspolicy/pkey/pkey.go b/util/syspolicy/pkey/pkey.go index e450625cd1710..9ed1d5b210b8d 100644 --- a/util/syspolicy/pkey/pkey.go +++ b/util/syspolicy/pkey/pkey.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package pkey defines the keys used to store system policies in the registry. diff --git a/util/syspolicy/policy_keys.go b/util/syspolicy/policy_keys.go index 3a54f9dde5dd7..2a4599cb85869 100644 --- a/util/syspolicy/policy_keys.go +++ b/util/syspolicy/policy_keys.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syspolicy diff --git a/util/syspolicy/policy_keys_test.go b/util/syspolicy/policy_keys_test.go index c2b8d5741831d..17e2e7a9b8f92 100644 --- a/util/syspolicy/policy_keys_test.go +++ b/util/syspolicy/policy_keys_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syspolicy diff --git a/util/syspolicy/policyclient/policyclient.go b/util/syspolicy/policyclient/policyclient.go index 728a16718e8e4..e6ad208b02f65 100644 --- a/util/syspolicy/policyclient/policyclient.go +++ b/util/syspolicy/policyclient/policyclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package policyclient contains the minimal syspolicy interface as needed by diff --git a/util/syspolicy/policytest/policytest.go b/util/syspolicy/policytest/policytest.go index e5c1c7856d0a3..ef5ce889dd2de 100644 --- a/util/syspolicy/policytest/policytest.go +++ b/util/syspolicy/policytest/policytest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package policytest contains test helpers for the syspolicy packages. diff --git a/util/syspolicy/ptype/ptype.go b/util/syspolicy/ptype/ptype.go index 65ca9e63108eb..ea8b03ad7db22 100644 --- a/util/syspolicy/ptype/ptype.go +++ b/util/syspolicy/ptype/ptype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ptype contains types used by syspolicy. diff --git a/util/syspolicy/ptype/ptype_test.go b/util/syspolicy/ptype/ptype_test.go index 7c963398b41b1..ba7eab471b5ea 100644 --- a/util/syspolicy/ptype/ptype_test.go +++ b/util/syspolicy/ptype/ptype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ptype diff --git a/util/syspolicy/rsop/change_callbacks.go b/util/syspolicy/rsop/change_callbacks.go index 71135bb2ac788..0b6cd10c4cc1a 100644 --- a/util/syspolicy/rsop/change_callbacks.go +++ b/util/syspolicy/rsop/change_callbacks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rsop diff --git a/util/syspolicy/rsop/resultant_policy.go b/util/syspolicy/rsop/resultant_policy.go index bdda909763008..5f8081a677a79 100644 --- a/util/syspolicy/rsop/resultant_policy.go +++ b/util/syspolicy/rsop/resultant_policy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rsop diff --git a/util/syspolicy/rsop/resultant_policy_test.go b/util/syspolicy/rsop/resultant_policy_test.go index 3ff1421197b1f..60132eae7a1d8 100644 --- a/util/syspolicy/rsop/resultant_policy_test.go +++ b/util/syspolicy/rsop/resultant_policy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rsop diff --git a/util/syspolicy/rsop/rsop.go b/util/syspolicy/rsop/rsop.go index 333dca64343c1..a57a4b34825ac 100644 --- a/util/syspolicy/rsop/rsop.go +++ b/util/syspolicy/rsop/rsop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package rsop facilitates [source.Store] registration via [RegisterStore] diff --git a/util/syspolicy/rsop/store_registration.go b/util/syspolicy/rsop/store_registration.go index a7c354b6d5678..99dbc7096fc56 100644 --- a/util/syspolicy/rsop/store_registration.go +++ b/util/syspolicy/rsop/store_registration.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rsop diff --git a/util/syspolicy/setting/errors.go b/util/syspolicy/setting/errors.go index 38dc6a88c7f1d..655018d4b5aff 100644 --- a/util/syspolicy/setting/errors.go +++ b/util/syspolicy/setting/errors.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/origin.go b/util/syspolicy/setting/origin.go index 4c7cc7025cc48..8ed629e72a322 100644 --- a/util/syspolicy/setting/origin.go +++ b/util/syspolicy/setting/origin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/policy_scope.go b/util/syspolicy/setting/policy_scope.go index c2039fdda15b8..4162614929dd2 100644 --- a/util/syspolicy/setting/policy_scope.go +++ b/util/syspolicy/setting/policy_scope.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/policy_scope_test.go b/util/syspolicy/setting/policy_scope_test.go index e1b6cf7ea0a78..a2f6328151d05 100644 --- a/util/syspolicy/setting/policy_scope_test.go +++ b/util/syspolicy/setting/policy_scope_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/raw_item.go b/util/syspolicy/setting/raw_item.go index ea97865f5a396..4bfb50faa1b62 100644 --- a/util/syspolicy/setting/raw_item.go +++ b/util/syspolicy/setting/raw_item.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/raw_item_test.go b/util/syspolicy/setting/raw_item_test.go index 05562d78c41f3..1a40bc829c351 100644 --- a/util/syspolicy/setting/raw_item_test.go +++ b/util/syspolicy/setting/raw_item_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/setting.go b/util/syspolicy/setting/setting.go index 97362b1dca8e0..4384e64c234f9 100644 --- a/util/syspolicy/setting/setting.go +++ b/util/syspolicy/setting/setting.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package setting contains types for defining and representing policy settings. diff --git a/util/syspolicy/setting/setting_test.go b/util/syspolicy/setting/setting_test.go index 9d99884f6436f..3ccd2ef606c50 100644 --- a/util/syspolicy/setting/setting_test.go +++ b/util/syspolicy/setting/setting_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/snapshot.go b/util/syspolicy/setting/snapshot.go index 94c7ecadb2533..74cadd0be7296 100644 --- a/util/syspolicy/setting/snapshot.go +++ b/util/syspolicy/setting/snapshot.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/snapshot_test.go b/util/syspolicy/setting/snapshot_test.go index 762a9681c6d7e..0385e4aefc8b4 100644 --- a/util/syspolicy/setting/snapshot_test.go +++ b/util/syspolicy/setting/snapshot_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/summary.go b/util/syspolicy/setting/summary.go index 9864822f7a235..4cb15c7c4d078 100644 --- a/util/syspolicy/setting/summary.go +++ b/util/syspolicy/setting/summary.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/source/env_policy_store.go b/util/syspolicy/source/env_policy_store.go index be363b79a84eb..9b7cebfbf19e5 100644 --- a/util/syspolicy/source/env_policy_store.go +++ b/util/syspolicy/source/env_policy_store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/env_policy_store_test.go b/util/syspolicy/source/env_policy_store_test.go index 3255095b2d286..5cda0f32a2984 100644 --- a/util/syspolicy/source/env_policy_store_test.go +++ b/util/syspolicy/source/env_policy_store_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/policy_reader.go b/util/syspolicy/source/policy_reader.go index 33ef22912f172..177985322d318 100644 --- a/util/syspolicy/source/policy_reader.go +++ b/util/syspolicy/source/policy_reader.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/policy_reader_test.go b/util/syspolicy/source/policy_reader_test.go index 32e8c51a6d3c9..e5a893f56877a 100644 --- a/util/syspolicy/source/policy_reader_test.go +++ b/util/syspolicy/source/policy_reader_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/policy_source.go b/util/syspolicy/source/policy_source.go index c4774217c09ac..3dfa83fd17a30 100644 --- a/util/syspolicy/source/policy_source.go +++ b/util/syspolicy/source/policy_source.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package source defines interfaces for policy stores, diff --git a/util/syspolicy/source/policy_store_windows.go b/util/syspolicy/source/policy_store_windows.go index f97b17f3afee6..edcdcae69b408 100644 --- a/util/syspolicy/source/policy_store_windows.go +++ b/util/syspolicy/source/policy_store_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/policy_store_windows_test.go b/util/syspolicy/source/policy_store_windows_test.go index 4ab1da805d6c8..b3ca5083d2a05 100644 --- a/util/syspolicy/source/policy_store_windows_test.go +++ b/util/syspolicy/source/policy_store_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/test_store.go b/util/syspolicy/source/test_store.go index ddec9efbb2d01..1baa138319337 100644 --- a/util/syspolicy/source/test_store.go +++ b/util/syspolicy/source/test_store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/syspolicy.go b/util/syspolicy/syspolicy.go index 48e430b674e35..7451bde758d4f 100644 --- a/util/syspolicy/syspolicy.go +++ b/util/syspolicy/syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package syspolicy contains the implementation of system policy management. diff --git a/util/syspolicy/syspolicy_test.go b/util/syspolicy/syspolicy_test.go index 10f8da48657d3..532cd03b8b9a7 100644 --- a/util/syspolicy/syspolicy_test.go +++ b/util/syspolicy/syspolicy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syspolicy diff --git a/util/syspolicy/syspolicy_windows.go b/util/syspolicy/syspolicy_windows.go index ca0fd329aca04..80c84b4570b9f 100644 --- a/util/syspolicy/syspolicy_windows.go +++ b/util/syspolicy/syspolicy_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syspolicy diff --git a/util/sysresources/memory.go b/util/sysresources/memory.go index 7363155cdb2ae..3c6b9ae852e47 100644 --- a/util/sysresources/memory.go +++ b/util/sysresources/memory.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sysresources diff --git a/util/sysresources/memory_bsd.go b/util/sysresources/memory_bsd.go index 26850dce652ff..945f86ea35ec9 100644 --- a/util/sysresources/memory_bsd.go +++ b/util/sysresources/memory_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build freebsd || openbsd || dragonfly || netbsd diff --git a/util/sysresources/memory_darwin.go b/util/sysresources/memory_darwin.go index e07bac0cd7f9b..165f12eb3b808 100644 --- a/util/sysresources/memory_darwin.go +++ b/util/sysresources/memory_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/util/sysresources/memory_linux.go b/util/sysresources/memory_linux.go index 0239b0e80d62a..3885a8aa6c66e 100644 --- a/util/sysresources/memory_linux.go +++ b/util/sysresources/memory_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/sysresources/memory_unsupported.go b/util/sysresources/memory_unsupported.go index 0fde256e0543d..c88e9ed5201e9 100644 --- a/util/sysresources/memory_unsupported.go +++ b/util/sysresources/memory_unsupported.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(linux || darwin || freebsd || openbsd || dragonfly || netbsd) diff --git a/util/sysresources/sysresources.go b/util/sysresources/sysresources.go index 32d972ab15513..33d0d5d96a96e 100644 --- a/util/sysresources/sysresources.go +++ b/util/sysresources/sysresources.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sysresources provides OS-independent methods of determining the diff --git a/util/sysresources/sysresources_test.go b/util/sysresources/sysresources_test.go index 331ad913bfba1..7fea1bf0f5b32 100644 --- a/util/sysresources/sysresources_test.go +++ b/util/sysresources/sysresources_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sysresources diff --git a/util/testenv/testenv.go b/util/testenv/testenv.go index aa6660411c91b..1ae1fe8a8a0f1 100644 --- a/util/testenv/testenv.go +++ b/util/testenv/testenv.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package testenv provides utility functions for tests. It does not depend on diff --git a/util/testenv/testenv_test.go b/util/testenv/testenv_test.go index c647d9aec1ea4..3001d19eb2722 100644 --- a/util/testenv/testenv_test.go +++ b/util/testenv/testenv_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package testenv diff --git a/util/topk/topk.go b/util/topk/topk.go index d3bbb2c6d1055..95ebd895d05aa 100644 --- a/util/topk/topk.go +++ b/util/topk/topk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package topk defines a count-min sketch and a cheap probabilistic top-K data diff --git a/util/topk/topk_test.go b/util/topk/topk_test.go index d30342e90de7b..06656c4204fe6 100644 --- a/util/topk/topk_test.go +++ b/util/topk/topk_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package topk diff --git a/util/truncate/truncate.go b/util/truncate/truncate.go index 310b81dd07100..7b98013f0bd59 100644 --- a/util/truncate/truncate.go +++ b/util/truncate/truncate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package truncate provides a utility function for safely truncating UTF-8 diff --git a/util/truncate/truncate_test.go b/util/truncate/truncate_test.go index c0d9e6e14df99..6a99a0efc4706 100644 --- a/util/truncate/truncate_test.go +++ b/util/truncate/truncate_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package truncate_test diff --git a/util/usermetric/metrics.go b/util/usermetric/metrics.go index be425fb87fd6c..14c2fabbec1f5 100644 --- a/util/usermetric/metrics.go +++ b/util/usermetric/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains user-facing metrics that are used by multiple packages. diff --git a/util/usermetric/omit.go b/util/usermetric/omit.go index 0611990abe89e..c2681ebdaa3b4 100644 --- a/util/usermetric/omit.go +++ b/util/usermetric/omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_usermetrics diff --git a/util/usermetric/usermetric.go b/util/usermetric/usermetric.go index 1805a5dbee626..f435f3ec23da3 100644 --- a/util/usermetric/usermetric.go +++ b/util/usermetric/usermetric.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_usermetrics diff --git a/util/usermetric/usermetric_test.go b/util/usermetric/usermetric_test.go index e92db5bfce130..cdbb44ec057bc 100644 --- a/util/usermetric/usermetric_test.go +++ b/util/usermetric/usermetric_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package usermetric diff --git a/util/vizerror/vizerror.go b/util/vizerror/vizerror.go index 919d765d0ef2d..479bd2de9e7c8 100644 --- a/util/vizerror/vizerror.go +++ b/util/vizerror/vizerror.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package vizerror provides types and utility funcs for handling visible errors diff --git a/util/vizerror/vizerror_test.go b/util/vizerror/vizerror_test.go index 242ca6462f37b..10e8376030beb 100644 --- a/util/vizerror/vizerror_test.go +++ b/util/vizerror/vizerror_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vizerror diff --git a/util/winutil/authenticode/authenticode_windows.go b/util/winutil/authenticode/authenticode_windows.go index 27c09b8cbb758..46f60caf76f79 100644 --- a/util/winutil/authenticode/authenticode_windows.go +++ b/util/winutil/authenticode/authenticode_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package authenticode contains Windows Authenticode signature verification code. diff --git a/util/winutil/authenticode/mksyscall.go b/util/winutil/authenticode/mksyscall.go index 8b7cabe6e4d7f..198081fce185a 100644 --- a/util/winutil/authenticode/mksyscall.go +++ b/util/winutil/authenticode/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package authenticode diff --git a/util/winutil/conpty/conpty_windows.go b/util/winutil/conpty/conpty_windows.go index 0a35759b49136..1071493f529b0 100644 --- a/util/winutil/conpty/conpty_windows.go +++ b/util/winutil/conpty/conpty_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package conpty implements support for Windows pseudo-consoles. diff --git a/util/winutil/gp/gp_windows.go b/util/winutil/gp/gp_windows.go index dd0e695eb08f2..dd13a27012d18 100644 --- a/util/winutil/gp/gp_windows.go +++ b/util/winutil/gp/gp_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package gp contains [Group Policy]-related functions and types. diff --git a/util/winutil/gp/gp_windows_test.go b/util/winutil/gp/gp_windows_test.go index f892068835bce..dfad029302c47 100644 --- a/util/winutil/gp/gp_windows_test.go +++ b/util/winutil/gp/gp_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gp diff --git a/util/winutil/gp/mksyscall.go b/util/winutil/gp/mksyscall.go index 3f3682d64d07e..22fd0c137894f 100644 --- a/util/winutil/gp/mksyscall.go +++ b/util/winutil/gp/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gp diff --git a/util/winutil/gp/policylock_windows.go b/util/winutil/gp/policylock_windows.go index 6c3ca0baf6d21..6e6f63f820489 100644 --- a/util/winutil/gp/policylock_windows.go +++ b/util/winutil/gp/policylock_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gp diff --git a/util/winutil/gp/watcher_windows.go b/util/winutil/gp/watcher_windows.go index ae66c391ff595..8a6538e8bb619 100644 --- a/util/winutil/gp/watcher_windows.go +++ b/util/winutil/gp/watcher_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gp diff --git a/util/winutil/mksyscall.go b/util/winutil/mksyscall.go index afee739986cda..f3b61f2dda4ad 100644 --- a/util/winutil/mksyscall.go +++ b/util/winutil/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/policy/policy_windows.go b/util/winutil/policy/policy_windows.go index 89142951f8bd5..c831066034608 100644 --- a/util/winutil/policy/policy_windows.go +++ b/util/winutil/policy/policy_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package policy contains higher-level abstractions for accessing Windows enterprise policies. diff --git a/util/winutil/policy/policy_windows_test.go b/util/winutil/policy/policy_windows_test.go index cf2390c568cce..881c08356b99a 100644 --- a/util/winutil/policy/policy_windows_test.go +++ b/util/winutil/policy/policy_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package policy diff --git a/util/winutil/restartmgr_windows.go b/util/winutil/restartmgr_windows.go index 6f549de557653..3ef8a0383b2ba 100644 --- a/util/winutil/restartmgr_windows.go +++ b/util/winutil/restartmgr_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/restartmgr_windows_test.go b/util/winutil/restartmgr_windows_test.go index 6b2d75c3c5459..eb11ffc9ce51f 100644 --- a/util/winutil/restartmgr_windows_test.go +++ b/util/winutil/restartmgr_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/s4u/lsa_windows.go b/util/winutil/s4u/lsa_windows.go index 3276b26766c08..a26a7bcf094fa 100644 --- a/util/winutil/s4u/lsa_windows.go +++ b/util/winutil/s4u/lsa_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package s4u diff --git a/util/winutil/s4u/mksyscall.go b/util/winutil/s4u/mksyscall.go index 8925c0209b124..b8ab33672c563 100644 --- a/util/winutil/s4u/mksyscall.go +++ b/util/winutil/s4u/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package s4u diff --git a/util/winutil/s4u/s4u_windows.go b/util/winutil/s4u/s4u_windows.go index 8c8e02dbe83bc..a5b543cab5ea1 100644 --- a/util/winutil/s4u/s4u_windows.go +++ b/util/winutil/s4u/s4u_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package s4u is an API for accessing Service-For-User (S4U) functionality on Windows. diff --git a/util/winutil/startupinfo_windows.go b/util/winutil/startupinfo_windows.go index edf48fa651cb5..5ded67c7c78e1 100644 --- a/util/winutil/startupinfo_windows.go +++ b/util/winutil/startupinfo_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/svcdiag_windows.go b/util/winutil/svcdiag_windows.go index 372377cf93217..e28f9b6af58d6 100644 --- a/util/winutil/svcdiag_windows.go +++ b/util/winutil/svcdiag_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/testdata/testrestartableprocesses/restartableprocess_windows.go b/util/winutil/testdata/testrestartableprocesses/restartableprocess_windows.go index 8a4e1b7f72c79..3ef834fd54546 100644 --- a/util/winutil/testdata/testrestartableprocesses/restartableprocess_windows.go +++ b/util/winutil/testdata/testrestartableprocesses/restartableprocess_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The testrestartableprocesses is a program for a test. diff --git a/util/winutil/userprofile_windows.go b/util/winutil/userprofile_windows.go index d2e6067c7a93f..c7fb028966ba6 100644 --- a/util/winutil/userprofile_windows.go +++ b/util/winutil/userprofile_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/userprofile_windows_test.go b/util/winutil/userprofile_windows_test.go index 09dcfd59627aa..0a21cea6ac10f 100644 --- a/util/winutil/userprofile_windows_test.go +++ b/util/winutil/userprofile_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/winenv/mksyscall.go b/util/winutil/winenv/mksyscall.go index 9737c40c470bb..77d8ec66ab2a2 100644 --- a/util/winutil/winenv/mksyscall.go +++ b/util/winutil/winenv/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winenv diff --git a/util/winutil/winenv/winenv_windows.go b/util/winutil/winenv/winenv_windows.go index 81fe4202633fb..eb7e87cedc320 100644 --- a/util/winutil/winenv/winenv_windows.go +++ b/util/winutil/winenv/winenv_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package winenv provides information about the current Windows environment. diff --git a/util/winutil/winutil.go b/util/winutil/winutil.go index ca231363acf1b..84ac2b1e341ef 100644 --- a/util/winutil/winutil.go +++ b/util/winutil/winutil.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package winutil contains misc Windows/Win32 helper functions. diff --git a/util/winutil/winutil_notwindows.go b/util/winutil/winutil_notwindows.go index caa415e08a513..774a0dad7af08 100644 --- a/util/winutil/winutil_notwindows.go +++ b/util/winutil/winutil_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/util/winutil/winutil_windows.go b/util/winutil/winutil_windows.go index c935b210e9e6a..cab0dabdf3694 100644 --- a/util/winutil/winutil_windows.go +++ b/util/winutil/winutil_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/winutil_windows_test.go b/util/winutil/winutil_windows_test.go index ead10a45d7ee8..955006789bc43 100644 --- a/util/winutil/winutil_windows_test.go +++ b/util/winutil/winutil_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/zstdframe/options.go b/util/zstdframe/options.go index b4b0f2b85304c..67ab27169166d 100644 --- a/util/zstdframe/options.go +++ b/util/zstdframe/options.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package zstdframe diff --git a/util/zstdframe/zstd.go b/util/zstdframe/zstd.go index b207984182b15..69fe3ee4017df 100644 --- a/util/zstdframe/zstd.go +++ b/util/zstdframe/zstd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package zstdframe provides functionality for encoding and decoding diff --git a/util/zstdframe/zstd_test.go b/util/zstdframe/zstd_test.go index 120fd3508460f..302090b9951b8 100644 --- a/util/zstdframe/zstd_test.go +++ b/util/zstdframe/zstd_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package zstdframe diff --git a/version-embed.go b/version-embed.go index 17bf578dd33f1..c368186ab3a7f 100644 --- a/version-embed.go +++ b/version-embed.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tailscaleroot embeds VERSION.txt into the binary. @@ -26,6 +26,12 @@ var AlpineDockerTag string //go:embed go.toolchain.rev var GoToolchainRev string +// GoToolchainNextRev is like GoToolchainRev, but when using the +// "go.toolchain.next.rev" when TS_GO_NEXT=1 is set in the environment. +// +//go:embed go.toolchain.next.rev +var GoToolchainNextRev string + //lint:ignore U1000 used by tests + assert_ts_toolchain_match.go w/ right build tags func tailscaleToolchainRev() (gitHash string, ok bool) { bi, ok := debug.ReadBuildInfo() diff --git a/version/cmdname.go b/version/cmdname.go index c38544ce1642c..8a4040f9718b9 100644 --- a/version/cmdname.go +++ b/version/cmdname.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios diff --git a/version/cmdname_ios.go b/version/cmdname_ios.go index 6bfed38b64226..1e6ec9dec4b23 100644 --- a/version/cmdname_ios.go +++ b/version/cmdname_ios.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios diff --git a/version/cmp.go b/version/cmp.go index 494a7ea72947f..4af0aec69ea6e 100644 --- a/version/cmp.go +++ b/version/cmp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/cmp_test.go b/version/cmp_test.go index e244d5e16fe22..10fc130b768eb 100644 --- a/version/cmp_test.go +++ b/version/cmp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version_test diff --git a/version/distro/distro.go b/version/distro/distro.go index 0e88bdd2fa297..03c02ccab91cf 100644 --- a/version/distro/distro.go +++ b/version/distro/distro.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package distro reports which distro we're running on. diff --git a/version/distro/distro_test.go b/version/distro/distro_test.go index 4d61c720581c7..f3460a180edc2 100644 --- a/version/distro/distro_test.go +++ b/version/distro/distro_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package distro diff --git a/version/exename.go b/version/exename.go index d5047c2038ffe..adb5236177e31 100644 --- a/version/exename.go +++ b/version/exename.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/export_test.go b/version/export_test.go index 8e8ce5ecb2129..ec43ad33248a7 100644 --- a/version/export_test.go +++ b/version/export_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/mkversion/mkversion.go b/version/mkversion/mkversion.go index 2fa84480dd144..45576e4c1161b 100644 --- a/version/mkversion/mkversion.go +++ b/version/mkversion/mkversion.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mkversion gets version info from git and provides a bunch of @@ -384,7 +384,7 @@ func infoFromCache(ref string, runner dirRunner) (verInfo, error) { } changeCount, err := strconv.Atoi(s) if err != nil { - return verInfo{}, fmt.Errorf("infoFromCache: parsing changeCount %q: %w", changeCount, err) + return verInfo{}, fmt.Errorf("infoFromCache: parsing changeCount %q: %w", s, err) } return verInfo{ diff --git a/version/mkversion/mkversion_test.go b/version/mkversion/mkversion_test.go index 210d3053a14a3..2f1a922c98924 100644 --- a/version/mkversion/mkversion_test.go +++ b/version/mkversion/mkversion_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package mkversion diff --git a/version/modinfo_test.go b/version/modinfo_test.go index 746e6296de795..ef75ce0771a47 100644 --- a/version/modinfo_test.go +++ b/version/modinfo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version_test diff --git a/version/print.go b/version/print.go index 43ee2b5591410..ca62226ee2b6d 100644 --- a/version/print.go +++ b/version/print.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/prop.go b/version/prop.go index 0d6a5c00df375..36d7699176f1e 100644 --- a/version/prop.go +++ b/version/prop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version @@ -15,6 +15,22 @@ import ( "tailscale.com/types/lazy" ) +// AppIdentifierFn, if non-nil, is a callback function that returns the +// application identifier of the running process or an empty string if unknown. +// +// tailscale(d) implementations can set an explicit callback to return an identifier +// for the running process if such a concept exists. The Apple bundle identifier, for example. +var AppIdentifierFn func() string // or nil + +const ( + macsysBundleID = "io.tailscale.ipn.macsys" // The macsys gui app and CLI + appStoreBundleID = "io.tailscale.ipn.macos" // The App Store gui app and CLI + macsysExtBundleId = "io.tailscale.ipn.macsys.network-extension" // The macsys system extension + appStoreExtBundleId = "io.tailscale.ipn.macos.network-extension" // The App Store network extension + tvOSExtBundleId = "io.tailscale.ipn.ios.network-extension-tvos" // The tvOS network extension + iOSExtBundleId = "io.tailscale.ipn.ios.network-extension" // The iOS network extension +) + // IsMobile reports whether this is a mobile client build. func IsMobile() bool { return runtime.GOOS == "android" || runtime.GOOS == "ios" @@ -52,8 +68,8 @@ func IsMacGUIVariant() bool { // IsSandboxedMacOS reports whether this process is a sandboxed macOS // process (either the app or the extension). It is true for the Mac App Store -// and macsys (System Extension) version on macOS, and false for -// tailscaled-on-macOS. +// and macsys (only its System Extension) variants on macOS, and false for +// tailscaled and the macsys GUI process on macOS. func IsSandboxedMacOS() bool { return IsMacAppStore() || IsMacSysExt() } @@ -73,10 +89,15 @@ func IsMacSysGUI() bool { if runtime.GOOS != "darwin" { return false } - return isMacSysApp.Get(func() bool { + if AppIdentifierFn != nil { + return AppIdentifierFn() == macsysBundleID + } + + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") || - strings.Contains(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.macsys") + strings.Contains(os.Getenv("XPC_SERVICE_NAME"), macsysBundleID) }) } @@ -90,11 +111,17 @@ func IsMacSysExt() bool { return false } return isMacSysExt.Get(func() bool { + if AppIdentifierFn != nil { + return AppIdentifierFn() == macsysExtBundleId + } + + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. exe, err := os.Executable() if err != nil { return false } - return filepath.Base(exe) == "io.tailscale.ipn.macsys.network-extension" + return filepath.Base(exe) == macsysExtBundleId }) } @@ -107,11 +134,17 @@ func IsMacAppStore() bool { return false } return isMacAppStore.Get(func() bool { + if AppIdentifierFn != nil { + id := AppIdentifierFn() + return id == appStoreBundleID || id == appStoreExtBundleId + } + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. // Both macsys and app store versions can run CLI executable with // suffix /Contents/MacOS/Tailscale. Check $HOME to filter out running // as macsys. return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macos/") || - strings.Contains(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.macos") + strings.Contains(os.Getenv("XPC_SERVICE_NAME"), appStoreBundleID) }) } @@ -124,6 +157,11 @@ func IsMacAppStoreGUI() bool { return false } return isMacAppStoreGUI.Get(func() bool { + if AppIdentifierFn != nil { + return AppIdentifierFn() == appStoreBundleID + } + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. exe, err := os.Executable() if err != nil { return false @@ -143,7 +181,13 @@ func IsAppleTV() bool { return false } return isAppleTV.Get(func() bool { - return strings.EqualFold(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.ios.network-extension-tvos") + if AppIdentifierFn != nil { + return AppIdentifierFn() == tvOSExtBundleId + } + + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. + return strings.EqualFold(os.Getenv("XPC_SERVICE_NAME"), tvOSExtBundleId) }) } @@ -187,6 +231,23 @@ func IsUnstableBuild() bool { }) } +// osVariant returns the OS variant string for systems where we support +// multiple ways of running tailscale(d), if any. +// +// For example: "appstore", "macsys", "darwin". +func osVariant() string { + if IsMacAppStore() { + return "appstore" + } + if IsMacSys() { + return "macsys" + } + if runtime.GOOS == "darwin" { + return "darwin" + } + return "" +} + var isDev = sync.OnceValue(func() bool { return strings.Contains(Short(), "-dev") }) @@ -227,6 +288,11 @@ type Meta struct { // setting "vcs.modified" was true). GitDirty bool `json:"gitDirty,omitempty"` + // OSVariant is specific variant of the binary, if applicable. For example, + // macsys/appstore/darwin for macOS builds. Nil/empty where not supported + // or on oses without variants. + OSVariant string `json:"osVariant,omitempty"` + // ExtraGitCommit, if non-empty, is the git commit of a "supplemental" // repository at which Tailscale was built. Its format is the same as // gitCommit. @@ -264,6 +330,7 @@ func GetMeta() Meta { GitCommitTime: getEmbeddedInfo().commitTime, GitCommit: gitCommit(), GitDirty: gitDirty(), + OSVariant: osVariant(), ExtraGitCommit: extraGitCommitStamp, IsDev: isDev(), UnstableBranch: IsUnstableBuild(), diff --git a/version/race.go b/version/race.go index e1dc76591ebf4..1cea65e7111e4 100644 --- a/version/race.go +++ b/version/race.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build race diff --git a/version/race_off.go b/version/race_off.go index 6db901974bb77..cbe7c198b2754 100644 --- a/version/race_off.go +++ b/version/race_off.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !race diff --git a/version/version.go b/version/version.go index 2add25689e1dd..1171ed2ffe722 100644 --- a/version/version.go +++ b/version/version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package version provides the version that the binary was built at. diff --git a/version/version_checkformat.go b/version/version_checkformat.go index 05a97d1912dbe..970010ddf21d6 100644 --- a/version/version_checkformat.go +++ b/version/version_checkformat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go && android diff --git a/version/version_internal_test.go b/version/version_internal_test.go index b3b848276e820..c78df4ff81a70 100644 --- a/version/version_internal_test.go +++ b/version/version_internal_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/version_test.go b/version/version_test.go index a515650586cc4..ebae7f177613a 100644 --- a/version/version_test.go +++ b/version/version_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version_test diff --git a/version_tailscale_test.go b/version_tailscale_test.go index 0a690e312202f..60a8d54f48093 100644 --- a/version_tailscale_test.go +++ b/version_tailscale_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go diff --git a/version_test.go b/version_test.go index 3d983a19d51db..6fb3ddef9d52b 100644 --- a/version_test.go +++ b/version_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscaleroot diff --git a/wf/firewall.go b/wf/firewall.go index 07e160eb36071..995a60c3e3356 100644 --- a/wf/firewall.go +++ b/wf/firewall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows @@ -25,6 +25,8 @@ var ( linkLocalMulticastIPv4Range = netip.MustParsePrefix("224.0.0.0/24") linkLocalMulticastIPv6Range = netip.MustParsePrefix("ff02::/16") + + limitedBroadcast = netip.MustParsePrefix("255.255.255.255/32") ) type direction int @@ -233,26 +235,41 @@ func (f *Firewall) UpdatePermittedRoutes(newRoutes []netip.Prefix) error { return err } - name = "link-local multicast - " + r.String() - conditions = matchLinkLocalMulticast(r, false) - multicastRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + multicastRules, err := f.addLinkLocalMulticastRules(p, r) if err != nil { return err } rules = append(rules, multicastRules...) - conditions = matchLinkLocalMulticast(r, true) - multicastRules, err = f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + broadcastRules, err := f.addLimitedBroadcastRules(p, r) if err != nil { return err } - rules = append(rules, multicastRules...) + rules = append(rules, broadcastRules...) f.permittedRoutes[r] = rules } return nil } +// addLinkLocalMulticastRules adds rules to allow inbound and outbound +// link-local multicast traffic to or from the specified network. +// It returns the added rules, or an error. +func (f *Firewall) addLinkLocalMulticastRules(p protocol, r netip.Prefix) ([]*wf.Rule, error) { + name := "link-local multicast - " + r.String() + conditions := matchLinkLocalMulticast(r, false) + outboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + if err != nil { + return nil, err + } + conditions = matchLinkLocalMulticast(r, true) + inboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + if err != nil { + return nil, err + } + return append(outboundRules, inboundRules...), nil +} + // matchLinkLocalMulticast returns a list of conditions that match // outbound or inbound link-local multicast traffic to or from the // specified network. @@ -288,6 +305,59 @@ func matchLinkLocalMulticast(pfx netip.Prefix, inbound bool) []*wf.Match { } } +// addLimitedBroadcastRules adds rules to allow inbound and outbound +// limited broadcast traffic to or from the specified network, +// if the network is IPv4. It returns the added rules, or an error. +func (f *Firewall) addLimitedBroadcastRules(p protocol, r netip.Prefix) ([]*wf.Rule, error) { + if !r.Addr().Is4() { + return nil, nil + } + name := "broadcast - " + r.String() + conditions := matchLimitedBroadcast(r, false) + outboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + if err != nil { + return nil, err + } + conditions = matchLimitedBroadcast(r, true) + inboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + if err != nil { + return nil, err + } + return append(outboundRules, inboundRules...), nil +} + +// matchLimitedBroadcast returns a list of conditions that match +// outbound or inbound limited broadcast traffic to or from the +// specified network. It panics if the pfx is not IPv4. +func matchLimitedBroadcast(pfx netip.Prefix, inbound bool) []*wf.Match { + if !pfx.Addr().Is4() { + panic("limited broadcast is only applicable to IPv4") + } + var localAddr, remoteAddr netip.Prefix + if inbound { + localAddr, remoteAddr = limitedBroadcast, pfx + } else { + localAddr, remoteAddr = pfx, limitedBroadcast + } + return []*wf.Match{ + { + Field: wf.FieldIPProtocol, + Op: wf.MatchTypeEqual, + Value: wf.IPProtoUDP, + }, + { + Field: wf.FieldIPLocalAddress, + Op: wf.MatchTypeEqual, + Value: localAddr, + }, + { + Field: wf.FieldIPRemoteAddress, + Op: wf.MatchTypeEqual, + Value: remoteAddr, + }, + } +} + func (f *Firewall) newRule(name string, w weight, layer wf.LayerID, conditions []*wf.Match, action wf.Action) (*wf.Rule, error) { id, err := windows.GenerateGUID() if err != nil { diff --git a/wgengine/bench/bench.go b/wgengine/bench/bench.go index 8695f18d15899..7ce673b488e4a 100644 --- a/wgengine/bench/bench.go +++ b/wgengine/bench/bench.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Create two wgengine instances and pass data through them, measuring diff --git a/wgengine/bench/bench_test.go b/wgengine/bench/bench_test.go index 4fae86c0580ba..8788f4721a0e0 100644 --- a/wgengine/bench/bench_test.go +++ b/wgengine/bench/bench_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Create two wgengine instances and pass data through them, measuring diff --git a/wgengine/bench/trafficgen.go b/wgengine/bench/trafficgen.go index ce79c616f86ed..3be398d5348d1 100644 --- a/wgengine/bench/trafficgen.go +++ b/wgengine/bench/trafficgen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/wgengine/bench/wg.go b/wgengine/bench/wg.go index ce6add866f9e8..7b35a089aebcc 100644 --- a/wgengine/bench/wg.go +++ b/wgengine/bench/wg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index 987fcee0153a6..b2be836c73395 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package filter is a stateful packet filter. @@ -32,8 +32,9 @@ import ( type Filter struct { logf logger.Logf // local4 and local6 report whether an IP is "local" to this node, for the - // respective address family. All packets coming in over tailscale must have - // a destination within local, regardless of the policy filter below. + // respective address family. Inbound packets that pass the direction-agnostic + // pre-checks and are not accepted by [Filter.IngressAllowHooks] must have a destination + // within local to be considered by the policy filter. local4 func(netip.Addr) bool local6 func(netip.Addr) bool @@ -66,8 +67,38 @@ type Filter struct { state *filterState shieldsUp bool + + // IngressAllowHooks are hooks that allow extensions to accept inbound + // packets beyond the standard filter rules. Packets that are not dropped + // by the direction-agnostic pre-check, but would be not accepted by the + // main filter rules, including the check for destinations in the node's + // local IP set, will be accepted if they match one of these hooks. + // As of 2026-02-24, the ingress filter does not implement explicit drop + // rules, but if it does, an explicitly dropped packet will be dropped, + // and these hooks will not be evaluated. + // + // Processing of hooks stop after the first one that returns true. + // The returned why string of the first match is used in logging. + // Returning false does not drop the packet. + // See also [filter.Filter.IngressAllowHooks]. + IngressAllowHooks []PacketMatch + + // LinkLocalAllowHooks are hooks that provide exceptions to the default + // policy of dropping link-local unicast packets. They run inside the + // direction-agnostic pre-checks for both ingress and egress. + // + // A hook can allow a link-local packet to pass the link-local check, + // but the packet is still subject to all other filter rules, and could be + // dropped elsewhere. Matching link-local packets are not logged. + // See also [filter.Filter.LinkLocalAllowHooks]. + LinkLocalAllowHooks []PacketMatch } +// PacketMatch is a function that inspects a packet and reports whether it +// matches a custom filter criterion. If match is true, why should be a short +// human-readable reason for the match, used in filter logging (e.g. "corp-dns ok"). +type PacketMatch func(packet.Parsed) (match bool, why string) + // filterState is a state cache of past seen packets. type filterState struct { mu sync.Mutex @@ -426,6 +457,16 @@ func (f *Filter) RunIn(q *packet.Parsed, rf RunFlags) Response { default: r, why = Drop, "not-ip" } + + if r == noVerdict { + for _, pm := range f.IngressAllowHooks { + if match, why := pm(*q); match { + f.logRateLimit(rf, q, dir, Accept, why) + return Accept + } + } + r = Drop + } f.logRateLimit(rf, q, dir, r, why) return r } @@ -439,6 +480,7 @@ func (f *Filter) RunOut(q *packet.Parsed, rf RunFlags) (Response, usermetric.Dro // already logged return r, reason } + r, why := f.runOut(q) f.logRateLimit(rf, q, dir, r, why) return r, "" @@ -455,12 +497,14 @@ func unknownProtoString(proto ipproto.Proto) string { return s } +// runIn4 returns noVerdict for unaccepted packets that may ultimately +// be accepted through [Filter.IngressAllowHooks]. func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) { // A compromised peer could try to send us packets for // destinations we didn't explicitly advertise. This check is to // prevent that. if !f.local4(q.Dst.Addr()) { - return Drop, "destination not allowed" + return noVerdict, "destination not allowed" } switch q.IPProto { @@ -510,17 +554,19 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) { if f.matches4.matchProtoAndIPsOnlyIfAllPorts(q) { return Accept, "other-portless ok" } - return Drop, unknownProtoString(q.IPProto) + return noVerdict, unknownProtoString(q.IPProto) } - return Drop, "no rules matched" + return noVerdict, "no rules matched" } +// runIn6 returns noVerdict for unaccepted packets that may ultimately +// be accepted through [Filter.IngressAllowHooks]. func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) { // A compromised peer could try to send us packets for // destinations we didn't explicitly advertise. This check is to // prevent that. if !f.local6(q.Dst.Addr()) { - return Drop, "destination not allowed" + return noVerdict, "destination not allowed" } switch q.IPProto { @@ -570,9 +616,9 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) { if f.matches6.matchProtoAndIPsOnlyIfAllPorts(q) { return Accept, "other-portless ok" } - return Drop, unknownProtoString(q.IPProto) + return noVerdict, unknownProtoString(q.IPProto) } - return Drop, "no rules matched" + return noVerdict, "no rules matched" } // runIn runs the output-specific part of the filter logic. @@ -609,6 +655,18 @@ func (d direction) String() string { var gcpDNSAddr = netaddr.IPv4(169, 254, 169, 254) +func (f *Filter) isAllowedLinkLocal(q *packet.Parsed) bool { + if q.Dst.Addr() == gcpDNSAddr { + return true + } + for _, pm := range f.LinkLocalAllowHooks { + if match, _ := pm(*q); match { + return true + } + } + return false +} + // pre runs the direction-agnostic filter logic. dir is only used for // logging. func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) (Response, usermetric.DropReason) { @@ -630,7 +688,7 @@ func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) (Response, us f.logRateLimit(rf, q, dir, Drop, "multicast") return Drop, usermetric.ReasonMulticast } - if q.Dst.Addr().IsLinkLocalUnicast() && q.Dst.Addr() != gcpDNSAddr { + if q.Dst.Addr().IsLinkLocalUnicast() && !f.isAllowedLinkLocal(q) { f.logRateLimit(rf, q, dir, Drop, "link-local-unicast") return Drop, usermetric.ReasonLinkLocalUnicast } diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index ae39eeb08692f..c588a506e0dc9 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filter @@ -171,12 +171,8 @@ func TestFilter(t *testing.T) { {Drop, parsed(ipproto.TCP, ipWithoutCap.String(), "1.2.3.4", 30000, 22)}, } for i, test := range tests { - aclFunc := filt.runIn4 - if test.p.IPVersion == 6 { - aclFunc = filt.runIn6 - } - if got, why := aclFunc(&test.p); test.want != got { - t.Errorf("#%d runIn got=%v want=%v why=%q packet:%v", i, got, test.want, why, test.p) + if got := filt.RunIn(&test.p, 0); test.want != got { + t.Errorf("#%d RunIn got=%v want=%v packet:%v", i, got, test.want, test.p) continue } if test.p.IPProto == ipproto.TCP { @@ -191,8 +187,8 @@ func TestFilter(t *testing.T) { } // TCP and UDP are treated equivalently in the filter - verify that. test.p.IPProto = ipproto.UDP - if got, why := aclFunc(&test.p); test.want != got { - t.Errorf("#%d runIn (UDP) got=%v want=%v why=%q packet:%v", i, got, test.want, why, test.p) + if got := filt.RunIn(&test.p, 0); test.want != got { + t.Errorf("#%d RunIn (UDP) got=%v want=%v packet:%v", i, got, test.want, test.p) } } // Update UDP state @@ -1071,6 +1067,192 @@ type benchOpt struct { udp, udpOpen bool } +func TestIngressAllowHooks(t *testing.T) { + matchSrc := func(ip string) PacketMatch { + return func(q packet.Parsed) (bool, string) { + return q.Src.Addr() == mustIP(ip), "match-src" + } + } + matchDst := func(ip string) PacketMatch { + return func(q packet.Parsed) (bool, string) { + return q.Dst.Addr() == mustIP(ip), "match-dst" + } + } + noMatch := func(q packet.Parsed) (bool, string) { return false, "" } + + tests := []struct { + name string + p packet.Parsed + hooks []PacketMatch + want Response + }{ + { + name: "no_hooks_denied_src", + p: parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22), + want: Drop, + }, + { + name: "non_matching_hook", + p: parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22), + hooks: []PacketMatch{noMatch}, + want: Drop, + }, + { + name: "matching_hook_denied_src", + p: parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22), + hooks: []PacketMatch{matchSrc("99.99.99.99")}, + want: Accept, + }, + { + name: "non_local_dst_no_hooks", + p: parsed(ipproto.TCP, "8.1.1.1", "16.32.48.64", 0, 443), + want: Drop, + }, + { + name: "non_local_dst_with_hook", + p: parsed(ipproto.TCP, "8.1.1.1", "16.32.48.64", 0, 443), + hooks: []PacketMatch{matchDst("16.32.48.64")}, + want: Accept, + }, + { + name: "first_match_wins", + p: parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22), + hooks: []PacketMatch{noMatch, matchSrc("99.99.99.99")}, + want: Accept, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filt := newFilter(t.Logf) + filt.IngressAllowHooks = tt.hooks + if got := filt.RunIn(&tt.p, 0); got != tt.want { + t.Errorf("RunIn = %v; want %v", got, tt.want) + } + }) + } + + // Verify first-match-wins stops calling subsequent hooks. + t.Run("first_match_stops_iteration", func(t *testing.T) { + filt := newFilter(t.Logf) + p := parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22) + var called []int + filt.IngressAllowHooks = []PacketMatch{ + func(q packet.Parsed) (bool, string) { + called = append(called, 0) + return true, "first" + }, + func(q packet.Parsed) (bool, string) { + called = append(called, 1) + return true, "second" + }, + } + filt.RunIn(&p, 0) + if len(called) != 1 || called[0] != 0 { + t.Errorf("called = %v; want [0]", called) + } + }) +} + +func TestLinkLocalAllowHooks(t *testing.T) { + matchDst := func(ip string) PacketMatch { + return func(q packet.Parsed) (bool, string) { + return q.Dst.Addr() == mustIP(ip), "match-dst" + } + } + noMatch := func(q packet.Parsed) (bool, string) { return false, "" } + + llPkt := func() packet.Parsed { + p := parsed(ipproto.UDP, "8.1.1.1", "169.254.1.2", 0, 53) + p.StuffForTesting(1024) + return p + } + gcpPkt := func() packet.Parsed { + p := parsed(ipproto.UDP, "8.1.1.1", "169.254.169.254", 0, 53) + p.StuffForTesting(1024) + return p + } + + tests := []struct { + name string + p packet.Parsed + hooks []PacketMatch + dir direction + want Response + }{ + { + name: "dropped_by_default", + p: llPkt(), + dir: in, + want: Drop, + }, + { + name: "non_matching_hook", + p: llPkt(), + hooks: []PacketMatch{noMatch}, + dir: in, + want: Drop, + }, + { + name: "matching_hook_allows", + p: llPkt(), + hooks: []PacketMatch{matchDst("169.254.1.2")}, + dir: in, + want: noVerdict, + }, + { + name: "gcp_dns_always_allowed", + p: gcpPkt(), + dir: in, + want: noVerdict, + }, + { + name: "matching_hook_allows_egress", + p: llPkt(), + hooks: []PacketMatch{matchDst("169.254.1.2")}, + dir: out, + want: noVerdict, + }, + { + name: "first_match_wins", + p: llPkt(), + hooks: []PacketMatch{noMatch, matchDst("169.254.1.2")}, + dir: in, + want: noVerdict, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filt := newFilter(t.Logf) + filt.LinkLocalAllowHooks = tt.hooks + got, reason := filt.pre(&tt.p, 0, tt.dir) + if got != tt.want { + t.Errorf("pre = %v (%s); want %v", got, reason, tt.want) + } + }) + } + + // Verify first-match-wins stops calling subsequent hooks. + t.Run("first_match_stops_iteration", func(t *testing.T) { + filt := newFilter(t.Logf) + p := llPkt() + var called []int + filt.LinkLocalAllowHooks = []PacketMatch{ + func(q packet.Parsed) (bool, string) { + called = append(called, 0) + return true, "first" + }, + func(q packet.Parsed) (bool, string) { + called = append(called, 1) + return true, "second" + }, + } + filt.pre(&p, 0, in) + if len(called) != 1 || called[0] != 0 { + t.Errorf("called = %v; want [0]", called) + } + }) +} + func benchmarkFile(b *testing.B, file string, opt benchOpt) { var matches []Match bts, err := os.ReadFile(file) diff --git a/wgengine/filter/filtertype/filtertype.go b/wgengine/filter/filtertype/filtertype.go index 212eda43f1404..aab5fe8eef046 100644 --- a/wgengine/filter/filtertype/filtertype.go +++ b/wgengine/filter/filtertype/filtertype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package filtertype defines the types used by wgengine/filter. diff --git a/wgengine/filter/filtertype/filtertype_clone.go b/wgengine/filter/filtertype/filtertype_clone.go index 63709188ea5c1..094063a5d1305 100644 --- a/wgengine/filter/filtertype/filtertype_clone.go +++ b/wgengine/filter/filtertype/filtertype_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/wgengine/filter/match.go b/wgengine/filter/match.go index 6292c49714a49..eee6ddf258fa1 100644 --- a/wgengine/filter/match.go +++ b/wgengine/filter/match.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filter diff --git a/wgengine/filter/tailcfg.go b/wgengine/filter/tailcfg.go index ff81077f727b7..e7e71526a43bc 100644 --- a/wgengine/filter/tailcfg.go +++ b/wgengine/filter/tailcfg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filter diff --git a/wgengine/magicsock/blockforever_conn.go b/wgengine/magicsock/blockforever_conn.go index 272a12513b353..a215826b751f7 100644 --- a/wgengine/magicsock/blockforever_conn.go +++ b/wgengine/magicsock/blockforever_conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/debughttp.go b/wgengine/magicsock/debughttp.go index 9aecab74b4278..68019d0a76cbb 100644 --- a/wgengine/magicsock/debughttp.go +++ b/wgengine/magicsock/debughttp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/debugknobs.go b/wgengine/magicsock/debugknobs.go index b0a47ff87f31b..580d954c0bc40 100644 --- a/wgengine/magicsock/debugknobs.go +++ b/wgengine/magicsock/debugknobs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !js @@ -62,7 +62,8 @@ var ( // //lint:ignore U1000 used on Linux/Darwin only debugPMTUD = envknob.RegisterBool("TS_DEBUG_PMTUD") - // debugNeverDirectUDP disables the use of direct UDP connections, forcing + // debugNeverDirectUDP disables the use of direct UDP connections by + // suppressing/dropping inbound/outbound [disco.Ping] messages, forcing // all peer communication over DERP or peer relay. debugNeverDirectUDP = envknob.RegisterBool("TS_DEBUG_NEVER_DIRECT_UDP") // Hey you! Adding a new debugknob? Make sure to stub it out in the diff --git a/wgengine/magicsock/debugknobs_stubs.go b/wgengine/magicsock/debugknobs_stubs.go index 7dee1d6b0b91c..c156ff8a7d92b 100644 --- a/wgengine/magicsock/debugknobs_stubs.go +++ b/wgengine/magicsock/debugknobs_stubs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || js diff --git a/wgengine/magicsock/derp.go b/wgengine/magicsock/derp.go index 1c5225e2249b5..f9e5050705b31 100644 --- a/wgengine/magicsock/derp.go +++ b/wgengine/magicsock/derp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock @@ -436,7 +436,14 @@ func (c *Conn) derpWriteChanForRegion(regionID int, peer key.NodePublic) chan de go c.runDerpReader(ctx, regionID, dc, wg, startGate) go c.runDerpWriter(ctx, dc, ch, wg, startGate) - go c.derpActiveFunc() + go func() { + select { + case <-ctx.Done(): + return + case <-startGate: + c.derpActiveFunc() + } + }() return ad.writeCh } diff --git a/wgengine/magicsock/derp_test.go b/wgengine/magicsock/derp_test.go index ffb230789e4c8..084f710d8526d 100644 --- a/wgengine/magicsock/derp_test.go +++ b/wgengine/magicsock/derp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/disco_atomic.go b/wgengine/magicsock/disco_atomic.go index 5b765fbc2c9a0..e17ce2f97eb30 100644 --- a/wgengine/magicsock/disco_atomic.go +++ b/wgengine/magicsock/disco_atomic.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/disco_atomic_test.go b/wgengine/magicsock/disco_atomic_test.go index a1de9b843379f..cec4b1133b274 100644 --- a/wgengine/magicsock/disco_atomic_test.go +++ b/wgengine/magicsock/disco_atomic_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/discopingpurpose_string.go b/wgengine/magicsock/discopingpurpose_string.go index 8eebf97a2dbd9..4cfbc751cf81c 100644 --- a/wgengine/magicsock/discopingpurpose_string.go +++ b/wgengine/magicsock/discopingpurpose_string.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by "stringer -type=discoPingPurpose -trimprefix=ping"; DO NOT EDIT. diff --git a/wgengine/magicsock/endpoint.go b/wgengine/magicsock/endpoint.go index eda589e14b1b6..1f99f57ec2d16 100644 --- a/wgengine/magicsock/endpoint.go +++ b/wgengine/magicsock/endpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock @@ -80,6 +80,7 @@ type endpoint struct { lastSendAny mono.Time // last time there were outgoing packets sent this peer from any trigger, internal or external to magicsock lastFullPing mono.Time // last time we pinged all disco or wireguard only endpoints lastUDPRelayPathDiscovery mono.Time // last time we ran UDP relay path discovery + sentDiscoKeyAdvertisement bool // wether we sent a TSMPDiscoAdvertisement or not to this endpoint derpAddr netip.AddrPort // fallback/bootstrap path, if non-zero (non-zero for well-behaved clients) bestAddr addrQuality // best non-DERP path; zero if none; mutate via setBestAddrLocked() diff --git a/wgengine/magicsock/endpoint_default.go b/wgengine/magicsock/endpoint_default.go index 1ed6e5e0e2399..59a47a98602bc 100644 --- a/wgengine/magicsock/endpoint_default.go +++ b/wgengine/magicsock/endpoint_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !wasm && !plan9 diff --git a/wgengine/magicsock/endpoint_stub.go b/wgengine/magicsock/endpoint_stub.go index a209c352bfe5e..da153abe57152 100644 --- a/wgengine/magicsock/endpoint_stub.go +++ b/wgengine/magicsock/endpoint_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build wasm || plan9 diff --git a/wgengine/magicsock/endpoint_test.go b/wgengine/magicsock/endpoint_test.go index f1dab924f5d3b..43ff012c73d61 100644 --- a/wgengine/magicsock/endpoint_test.go +++ b/wgengine/magicsock/endpoint_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/endpoint_tracker.go b/wgengine/magicsock/endpoint_tracker.go index e95852d2491b7..372f346853723 100644 --- a/wgengine/magicsock/endpoint_tracker.go +++ b/wgengine/magicsock/endpoint_tracker.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/endpoint_tracker_test.go b/wgengine/magicsock/endpoint_tracker_test.go index 6fccdfd576878..b3b1a63d94e29 100644 --- a/wgengine/magicsock/endpoint_tracker_test.go +++ b/wgengine/magicsock/endpoint_tracker_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 8fbd07013797d..169369f4bb472 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package magicsock implements a socket that can change its communication path while @@ -177,11 +177,9 @@ type Conn struct { connCtxCancel func() // closes connCtx donec <-chan struct{} // connCtx.Done()'s to avoid context.cancelCtx.Done()'s mutex per call - // A publisher for synchronization points to ensure correct ordering of - // config changes between magicsock and wireguard. - syncPub *eventbus.Publisher[syncPoint] - allocRelayEndpointPub *eventbus.Publisher[UDPRelayAllocReq] - portUpdatePub *eventbus.Publisher[router.PortUpdate] + allocRelayEndpointPub *eventbus.Publisher[UDPRelayAllocReq] + portUpdatePub *eventbus.Publisher[router.PortUpdate] + tsmpDiscoKeyAvailablePub *eventbus.Publisher[NewDiscoKeyAvailable] // pconn4 and pconn6 are the underlying UDP sockets used to // send/receive packets for wireguard and other magicsock @@ -361,11 +359,11 @@ type Conn struct { netInfoLast *tailcfg.NetInfo derpMap *tailcfg.DERPMap // nil (or zero regions/nodes) means DERP is disabled - self tailcfg.NodeView // from last onNodeViewsUpdate - peers views.Slice[tailcfg.NodeView] // from last onNodeViewsUpdate, sorted by Node.ID; Note: [netmap.NodeMutation]'s rx'd in onNodeMutationsUpdate are never applied - filt *filter.Filter // from last onFilterUpdate + self tailcfg.NodeView // from last SetNetworkMap + peers views.Slice[tailcfg.NodeView] // from last SetNetworkMap, sorted by Node.ID; Note: [netmap.NodeMutation]'s rx'd in UpdateNetmapDelta are never applied + filt *filter.Filter // from last SetFilter relayClientEnabled bool // whether we can allocate UDP relay endpoints on UDP relay servers or receive CallMeMaybeVia messages from peers - lastFlags debugFlags // at time of last onNodeViewsUpdate + lastFlags debugFlags // at time of last SetNetworkMap privateKey key.NodePrivate // WireGuard private key for this node everHadKey bool // whether we ever had a non-zero private key myDerp int // nearest DERP region ID; 0 means none/unknown @@ -520,47 +518,6 @@ func (o *Options) derpActiveFunc() func() { return o.DERPActiveFunc } -// NodeViewsUpdate represents an update event of [tailcfg.NodeView] for all -// nodes. This event is published over an [eventbus.Bus]. It may be published -// with an invalid SelfNode, and/or zero/nil Peers. [magicsock.Conn] is the sole -// subscriber as of 2025-06. If you are adding more subscribers consider moving -// this type out of magicsock. -type NodeViewsUpdate struct { - SelfNode tailcfg.NodeView - Peers []tailcfg.NodeView // sorted by Node.ID -} - -// NodeMutationsUpdate represents an update event of one or more -// [netmap.NodeMutation]. This event is published over an [eventbus.Bus]. -// [magicsock.Conn] is the sole subscriber as of 2025-06. If you are adding more -// subscribers consider moving this type out of magicsock. -type NodeMutationsUpdate struct { - Mutations []netmap.NodeMutation -} - -// FilterUpdate represents an update event for a [*filter.Filter]. This event is -// signaled over an [eventbus.Bus]. [magicsock.Conn] is the sole subscriber as -// of 2025-06. If you are adding more subscribers consider moving this type out -// of magicsock. -type FilterUpdate struct { - *filter.Filter -} - -// syncPoint is an event published over an [eventbus.Bus] by [Conn.Synchronize]. -// It serves as a synchronization point, allowing to wait until magicsock -// has processed all pending events. -type syncPoint chan struct{} - -// Wait blocks until [syncPoint.Signal] is called. -func (s syncPoint) Wait() { - <-s -} - -// Signal signals the sync point, unblocking the [syncPoint.Wait] call. -func (s syncPoint) Signal() { - close(s) -} - // UDPRelayAllocReq represents a [*disco.AllocateUDPRelayEndpointRequest] // reception event. This is signaled over an [eventbus.Bus] from // [magicsock.Conn] towards [relayserver.extension]. @@ -653,21 +610,6 @@ func (c *Conn) onUDPRelayAllocResp(allocResp UDPRelayAllocResp) { } } -// Synchronize waits for all [eventbus] events published -// prior to this call to be processed by the receiver. -func (c *Conn) Synchronize() { - if c.syncPub == nil { - // Eventbus is not used; no need to synchronize (in certain tests). - return - } - sp := syncPoint(make(chan struct{})) - c.syncPub.Publish(sp) - select { - case <-sp: - case <-c.donec: - } -} - // NewConn creates a magic Conn listening on opts.Port. // As the set of possible endpoints for a Conn changes, the // callback opts.EndpointsFunc is called. @@ -693,17 +635,10 @@ func NewConn(opts Options) (*Conn, error) { // NewConn otherwise published events can be missed. ec := c.eventBus.Client("magicsock.Conn") c.eventClient = ec - c.syncPub = eventbus.Publish[syncPoint](ec) c.allocRelayEndpointPub = eventbus.Publish[UDPRelayAllocReq](ec) c.portUpdatePub = eventbus.Publish[router.PortUpdate](ec) + c.tsmpDiscoKeyAvailablePub = eventbus.Publish[NewDiscoKeyAvailable](ec) eventbus.SubscribeFunc(ec, c.onPortMapChanged) - eventbus.SubscribeFunc(ec, c.onFilterUpdate) - eventbus.SubscribeFunc(ec, c.onNodeViewsUpdate) - eventbus.SubscribeFunc(ec, c.onNodeMutationsUpdate) - eventbus.SubscribeFunc(ec, func(sp syncPoint) { - c.dlogf("magicsock: received sync point after reconfig") - sp.Signal() - }) eventbus.SubscribeFunc(ec, c.onUDPRelayAllocResp) c.connCtx, c.connCtxCancel = context.WithCancel(context.Background()) @@ -1249,7 +1184,8 @@ func (c *Conn) DiscoPublicKey() key.DiscoPublic { // RotateDiscoKey generates a new discovery key pair and updates the connection // to use it. This invalidates all existing disco sessions and will cause peers -// to re-establish discovery sessions with the new key. +// to re-establish discovery sessions with the new key. Addtionally, the +// lastTSMPDiscoAdvertisement on all endpoints is reset to 0. // // This is primarily for debugging and testing purposes, a future enhancement // should provide a mechanism for seamless rotation by supporting short term use @@ -1263,6 +1199,11 @@ func (c *Conn) RotateDiscoKey() { newShort := c.discoAtomic.Short() c.discoInfo = make(map[key.DiscoPublic]*discoInfo) connCtx := c.connCtx + for _, endpoint := range c.peerMap.byEpAddr { + endpoint.ep.mu.Lock() + endpoint.ep.sentDiscoKeyAdvertisement = false + endpoint.ep.mu.Unlock() + } c.mu.Unlock() c.logf("magicsock: rotated disco key from %v to %v", oldShort, newShort) @@ -2247,6 +2188,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src epAddr, shouldBeRelayHandshake if debugDisco() { c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?) via %s", sender, via) } + metricRecvDiscoBadKey.Add(1) return } @@ -2546,6 +2488,10 @@ func (c *Conn) handlePingLocked(dm *disco.Ping, src epAddr, di *discoInfo, derpN // This is a naked [disco.Ping] without a VNI. + if debugNeverDirectUDP() && !isDerp { + return + } + // If we can figure out with certainty which node key this disco // message is for, eagerly update our [epAddr]<>node and disco<>node // mappings to make p2p path discovery faster in simple @@ -2654,6 +2600,8 @@ func (c *Conn) enqueueCallMeMaybe(derpAddr netip.AddrPort, de *endpoint) { return } + c.maybeSendTSMPDiscoAdvert(de) + eps := make([]netip.AddrPort, 0, len(c.lastEndpoints)) for _, ep := range c.lastEndpoints { eps = append(eps, ep.Addr) @@ -2892,11 +2840,12 @@ func capVerIsRelayCapable(version tailcfg.CapabilityVersion) bool { return version >= 121 } -// onFilterUpdate is called when a [FilterUpdate] is received over the -// [eventbus.Bus]. -func (c *Conn) onFilterUpdate(f FilterUpdate) { +// SetFilter updates the packet filter used by the connection. +// It must be called synchronously from the caller's goroutine to ensure +// magicsock has the current filter before subsequent operations proceed. +func (c *Conn) SetFilter(f *filter.Filter) { c.mu.Lock() - c.filt = f.Filter + c.filt = f self := c.self peers := c.peers relayClientEnabled := c.relayClientEnabled @@ -2909,7 +2858,7 @@ func (c *Conn) onFilterUpdate(f FilterUpdate) { // The filter has changed, and we are operating as a relay server client. // Re-evaluate it in order to produce an updated relay server set. - c.updateRelayServersSet(f.Filter, self, peers) + c.updateRelayServersSet(f, self, peers) } // updateRelayServersSet iterates all peers and self, evaluating filt for each @@ -3000,21 +2949,24 @@ func (c *candidatePeerRelay) isValid() bool { return !c.nodeKey.IsZero() && !c.discoKey.IsZero() } -// onNodeViewsUpdate is called when a [NodeViewsUpdate] is received over the -// [eventbus.Bus]. -func (c *Conn) onNodeViewsUpdate(update NodeViewsUpdate) { - peersChanged := c.updateNodes(update) +// SetNetworkMap updates the network map with the given self node and peers. +// It must be called synchronously from the caller's goroutine to ensure +// magicsock has the current state before subsequent operations proceed. +// +// self may be invalid if there's no network map. +func (c *Conn) SetNetworkMap(self tailcfg.NodeView, peers []tailcfg.NodeView) { + peersChanged := c.updateNodes(self, peers) - relayClientEnabled := update.SelfNode.Valid() && - !update.SelfNode.HasCap(tailcfg.NodeAttrDisableRelayClient) && - !update.SelfNode.HasCap(tailcfg.NodeAttrOnlyTCP443) + relayClientEnabled := self.Valid() && + !self.HasCap(tailcfg.NodeAttrDisableRelayClient) && + !self.HasCap(tailcfg.NodeAttrOnlyTCP443) c.mu.Lock() relayClientChanged := c.relayClientEnabled != relayClientEnabled c.relayClientEnabled = relayClientEnabled filt := c.filt - self := c.self - peers := c.peers + selfView := c.self + peersView := c.peers isClosed := c.closed c.mu.Unlock() // release c.mu before potentially calling c.updateRelayServersSet which is O(m * n) @@ -3027,15 +2979,14 @@ func (c *Conn) onNodeViewsUpdate(update NodeViewsUpdate) { c.relayManager.handleRelayServersSet(nil) c.hasPeerRelayServers.Store(false) } else { - c.updateRelayServersSet(filt, self, peers) + c.updateRelayServersSet(filt, selfView, peersView) } } } -// updateNodes updates [Conn] to reflect the [tailcfg.NodeView]'s contained -// in update. It returns true if update.Peers was unequal to c.peers, otherwise -// false. -func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { +// updateNodes updates [Conn] to reflect the given self node and peers. +// It reports whether the peers were changed from before. +func (c *Conn) updateNodes(self tailcfg.NodeView, peers []tailcfg.NodeView) (peersChanged bool) { c.mu.Lock() defer c.mu.Unlock() @@ -3044,11 +2995,11 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { } priorPeers := c.peers - metricNumPeers.Set(int64(len(update.Peers))) + metricNumPeers.Set(int64(len(peers))) // Update c.self & c.peers regardless, before the following early return. - c.self = update.SelfNode - curPeers := views.SliceOf(update.Peers) + c.self = self + curPeers := views.SliceOf(peers) c.peers = curPeers // [debugFlags] are mutable in [Conn.SetSilentDisco] & @@ -3057,7 +3008,7 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { // [controlknobs.Knobs] are simply self [tailcfg.NodeCapability]'s. They are // useful as a global view of notable feature toggles, but the magicsock // setters are completely unnecessary as we have the same values right here - // (update.SelfNode.Capabilities) at a time they are considered most + // (self.Capabilities) at a time they are considered most // up-to-date. // TODO: mutate [debugFlags] here instead of in various [Conn] setters. flags := c.debugFlagsLocked() @@ -3073,16 +3024,16 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { c.lastFlags = flags - c.logf("[v1] magicsock: got updated network map; %d peers", len(update.Peers)) + c.logf("[v1] magicsock: got updated network map; %d peers", len(peers)) - entriesPerBuffer := debugRingBufferSize(len(update.Peers)) + entriesPerBuffer := debugRingBufferSize(len(peers)) // Try a pass of just upserting nodes and creating missing // endpoints. If the set of nodes is the same, this is an // efficient alloc-free update. If the set of nodes is different, // we'll fall through to the next pass, which allocates but can // handle full set updates. - for _, n := range update.Peers { + for _, n := range peers { if n.ID() == 0 { devPanicf("node with zero ID") continue @@ -3129,8 +3080,18 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { // we don't get this far. If ok was false above, that means it's a key // that differs from the one the NodeID had. But double check. if ep.nodeID != n.ID() { - // Server error. - devPanicf("public key moved between nodeIDs (old=%v new=%v, key=%s)", ep.nodeID, n.ID(), n.Key().String()) + // Server error. This is known to be a particular issue for Mullvad + // nodes (http://go/corp/27300), so log a distinct error for the + // Mullvad and non-Mullvad cases. The error will be logged either way, + // so an approximate heuristic is fine. + // + // When #27300 is fixed, we can delete this branch and log the same + // panic for any public key moving. + if strings.HasSuffix(n.Name(), ".mullvad.ts.net.") { + devPanicf("public key moved between Mullvad nodeIDs (old=%v new=%v, key=%s); see http://go/corp/27300", ep.nodeID, n.ID(), n.Key().String()) + } else { + devPanicf("public key moved between nodeIDs (old=%v new=%v, key=%s)", ep.nodeID, n.ID(), n.Key().String()) + } } else { // Internal data structures out of sync. devPanicf("public key found in peerMap but not by nodeID") @@ -3182,14 +3143,14 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { c.peerMap.upsertEndpoint(ep, key.DiscoPublic{}) } - // If the set of nodes changed since the last onNodeViewsUpdate, the + // If the set of nodes changed since the last SetNetworkMap, the // upsert loop just above made c.peerMap contain the union of the // old and new peers - which will be larger than the set from the // current netmap. If that happens, go through the allocful // deletion path to clean up moribund nodes. - if c.peerMap.nodeCount() != len(update.Peers) { + if c.peerMap.nodeCount() != len(peers) { keep := set.Set[key.NodePublic]{} - for _, n := range update.Peers { + for _, n := range peers { keep.Add(n.Key()) } c.peerMap.forEachEndpoint(func(ep *endpoint) { @@ -3724,13 +3685,15 @@ func simpleDur(d time.Duration) time.Duration { return d.Round(time.Minute) } -// onNodeMutationsUpdate is called when a [NodeMutationsUpdate] is received over -// the [eventbus.Bus]. Note: It does not apply these mutations to c.peers. -func (c *Conn) onNodeMutationsUpdate(update NodeMutationsUpdate) { +// UpdateNetmapDelta applies the given node mutations to the connection's peer +// state. It must be called synchronously from the caller's goroutine to ensure +// magicsock has the current state before subsequent operations proceed. +// Note: It does not apply these mutations to c.peers. +func (c *Conn) UpdateNetmapDelta(muts []netmap.NodeMutation) { c.mu.Lock() defer c.mu.Unlock() - for _, m := range update.Mutations { + for _, m := range muts { nodeID := m.NodeIDBeingMutated() ep, ok := c.peerMap.endpointForNodeID(nodeID) if !ok { @@ -4314,3 +4277,49 @@ func (c *Conn) HandleDiscoKeyAdvertisement(node tailcfg.NodeView, update packet. c.logf("magicsock: updated disco key for peer %v to %v", nodeKey.ShortString(), discoKey.ShortString()) metricTSMPDiscoKeyAdvertisementApplied.Add(1) } + +// NewDiscoKeyAvailable is an eventbus topic that is emitted when we're sending +// a packet to a node and observe we haven't told it our current DiscoKey before. +// +// The publisher is magicsock, when we're sending a packet. +// The subscriber is userspaceEngine, which sends a TSMP packet, also via +// magicsock. This doesn't recurse infinitely because we only publish it once per +// DiscoKey. +// In the common case, a DiscoKey is not rotated within a process generation +// (as of 2026-01-21), except with debug commands to simulate process restarts. +// +// The address is the first node address (tailscale address) of the node. It +// does not matter if the address is v4/v6, the receiver should handle either. +// +// Since we have not yet communicated with the node at the time we are +// sending this event, the resulting TSMPDiscoKeyAdvertisement will with all +// likelihood be transmitted via DERP. +type NewDiscoKeyAvailable struct { + NodeFirstAddr netip.Addr + NodeID tailcfg.NodeID +} + +// maybeSendTSMPDiscoAdvert conditionally emits an event indicating that we +// should send our DiscoKey to the first node address of the magicksock endpoint. +// The event is only emitted if we have not yet contacted that endpoint since +// the DiscoKey changed. +// +// This condition is most likely met only once per endpoint, after the start of +// tailscaled, but not until we contact the endpoint for the first time. +// +// We do not need the Conn to be locked, but the endpoint should be. +func (c *Conn) maybeSendTSMPDiscoAdvert(de *endpoint) { + if !buildfeatures.HasCacheNetMap || !envknob.Bool("TS_USE_CACHED_NETMAP") { + return + } + + de.mu.Lock() + defer de.mu.Unlock() + if !de.sentDiscoKeyAdvertisement { + de.sentDiscoKeyAdvertisement = true + c.tsmpDiscoKeyAvailablePub.Publish(NewDiscoKeyAvailable{ + NodeFirstAddr: de.nodeAddr, + NodeID: de.nodeID, + }) + } +} diff --git a/wgengine/magicsock/magicsock_default.go b/wgengine/magicsock/magicsock_default.go index 88759d3acc2e3..fc3e656b245f3 100644 --- a/wgengine/magicsock/magicsock_default.go +++ b/wgengine/magicsock/magicsock_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || ts_omit_listenrawdisco diff --git a/wgengine/magicsock/magicsock_linux.go b/wgengine/magicsock/magicsock_linux.go index f37e19165141f..522341e721317 100644 --- a/wgengine/magicsock/magicsock_linux.go +++ b/wgengine/magicsock/magicsock_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_listenrawdisco diff --git a/wgengine/magicsock/magicsock_linux_test.go b/wgengine/magicsock/magicsock_linux_test.go index 28ccd220ee784..b670fa6bab601 100644 --- a/wgengine/magicsock/magicsock_linux_test.go +++ b/wgengine/magicsock/magicsock_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/magicsock_notplan9.go b/wgengine/magicsock/magicsock_notplan9.go index 86d099ee7f48c..6bb9db5d7f1d6 100644 --- a/wgengine/magicsock/magicsock_notplan9.go +++ b/wgengine/magicsock/magicsock_notplan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 @@ -8,6 +8,8 @@ package magicsock import ( "errors" "syscall" + + "tailscale.com/net/neterror" ) // shouldRebind returns if the error is one that is known to be healed by a @@ -17,7 +19,7 @@ func shouldRebind(err error) (ok bool, reason string) { // EPIPE/ENOTCONN are common errors when a send fails due to a closed // socket. There is some platform and version inconsistency in which // error is returned, but the meaning is the same. - case errors.Is(err, syscall.EPIPE), errors.Is(err, syscall.ENOTCONN): + case neterror.IsClosedPipeError(err): return true, "broken-pipe" // EPERM is typically caused by EDR software, and has been observed to be diff --git a/wgengine/magicsock/magicsock_plan9.go b/wgengine/magicsock/magicsock_plan9.go index 65714c3e13c33..a234beecf6f8a 100644 --- a/wgengine/magicsock/magicsock_plan9.go +++ b/wgengine/magicsock/magicsock_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build plan9 diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 68ab4dfa012a7..9d6cae87bdcc6 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock @@ -171,7 +171,7 @@ type magicStack struct { } // newMagicStack builds and initializes an idle magicsock and -// friends. You need to call conn.onNodeViewsUpdate and dev.Reconfig +// friends. You need to call conn.SetNetworkMap and dev.Reconfig // before anything interesting happens. func newMagicStack(t testing.TB, logf logger.Logf, ln nettype.PacketListener, derpMap *tailcfg.DERPMap) *magicStack { privateKey := key.NewNode() @@ -346,13 +346,9 @@ func meshStacks(logf logger.Logf, mutateNetmap func(idx int, nm *netmap.NetworkM for i, m := range ms { nm := buildNetmapLocked(i) - nv := NodeViewsUpdate{ - SelfNode: nm.SelfNode, - Peers: nm.Peers, - } - m.conn.onNodeViewsUpdate(nv) - peerSet := make(set.Set[key.NodePublic], len(nv.Peers)) - for _, peer := range nv.Peers { + m.conn.SetNetworkMap(nm.SelfNode, nm.Peers) + peerSet := make(set.Set[key.NodePublic], len(nm.Peers)) + for _, peer := range nm.Peers { peerSet.Add(peer.Key()) } m.conn.UpdatePeers(peerSet) @@ -534,6 +530,57 @@ func TestPickDERPFallback(t *testing.T) { // have fixed DERP fallback logic. } +// TestDERPActiveFuncCalledAfterConnect verifies that DERPActiveFunc is not +// called until the DERP connection is actually established (i.e. after +// startGate / derpStarted is closed). +func TestDERPActiveFuncCalledAfterConnect(t *testing.T) { + derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1)) + defer cleanup() + + bus := eventbustest.NewBus(t) + + netMon, err := netmon.New(bus, t.Logf) + if err != nil { + t.Fatal(err) + } + + resultCh := make(chan bool, 1) + var conn *Conn + + conn, err = NewConn(Options{ + Logf: t.Logf, + NetMon: netMon, + EventBus: bus, + HealthTracker: health.NewTracker(bus), + Metrics: new(usermetric.Registry), + DisablePortMapper: true, + TestOnlyPacketListener: localhostListener{}, + EndpointsFunc: func([]tailcfg.Endpoint) {}, + DERPActiveFunc: func() { + // derpStarted should already be closed when DERPActiveFunc is called. + select { + case <-conn.derpStarted: + resultCh <- true + default: + resultCh <- false + } + }, + }) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + conn.SetDERPMap(derpMap) + if err := conn.SetPrivateKey(key.NewNode()); err != nil { + t.Fatal(err) + } + + if ok := <-resultCh; !ok { + t.Error("DERPActiveFunc was called before DERP connection was established") + } +} + // TestDeviceStartStop exercises the startup and shutdown logic of // wireguard-go, which is intimately intertwined with magicsock's own // lifecycle. We seem to be good at generating deadlocks here, so if @@ -1388,16 +1435,14 @@ func addTestEndpoint(tb testing.TB, conn *Conn, sendConn net.PacketConn) (key.No // codepath. discoKey := key.DiscoPublicFromRaw32(mem.B([]byte{31: 1})) nodeKey := key.NodePublicFromRaw32(mem.B([]byte{0: 'N', 1: 'K', 31: 0})) - conn.onNodeViewsUpdate(NodeViewsUpdate{ - Peers: nodeViews([]*tailcfg.Node{ - { - ID: 1, - Key: nodeKey, - DiscoKey: discoKey, - Endpoints: eps(sendConn.LocalAddr().String()), - }, - }), - }) + conn.SetNetworkMap(tailcfg.NodeView{}, nodeViews([]*tailcfg.Node{ + { + ID: 1, + Key: nodeKey, + DiscoKey: discoKey, + Endpoints: eps(sendConn.LocalAddr().String()), + }, + })) conn.SetPrivateKey(key.NodePrivateFromRaw32(mem.B([]byte{0: 1, 31: 0}))) _, err := conn.ParseEndpoint(nodeKey.UntypedHexString()) if err != nil { @@ -1581,7 +1626,7 @@ func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView { // doesn't change its disco key doesn't result in a broken state. // // https://github.com/tailscale/tailscale/issues/1391 -func TestOnNodeViewsUpdateChangingNodeKey(t *testing.T) { +func TestSetNetworkMapChangingNodeKey(t *testing.T) { conn := newTestConn(t) t.Cleanup(func() { conn.Close() }) var buf tstest.MemLogger @@ -1593,32 +1638,28 @@ func TestOnNodeViewsUpdateChangingNodeKey(t *testing.T) { nodeKey1 := key.NodePublicFromRaw32(mem.B([]byte{0: 'N', 1: 'K', 2: '1', 31: 0})) nodeKey2 := key.NodePublicFromRaw32(mem.B([]byte{0: 'N', 1: 'K', 2: '2', 31: 0})) - conn.onNodeViewsUpdate(NodeViewsUpdate{ - Peers: nodeViews([]*tailcfg.Node{ - { - ID: 1, - Key: nodeKey1, - DiscoKey: discoKey, - Endpoints: eps("192.168.1.2:345"), - }, - }), - }) + conn.SetNetworkMap(tailcfg.NodeView{}, nodeViews([]*tailcfg.Node{ + { + ID: 1, + Key: nodeKey1, + DiscoKey: discoKey, + Endpoints: eps("192.168.1.2:345"), + }, + })) _, err := conn.ParseEndpoint(nodeKey1.UntypedHexString()) if err != nil { t.Fatal(err) } for range 3 { - conn.onNodeViewsUpdate(NodeViewsUpdate{ - Peers: nodeViews([]*tailcfg.Node{ - { - ID: 2, - Key: nodeKey2, - DiscoKey: discoKey, - Endpoints: eps("192.168.1.2:345"), - }, - }), - }) + conn.SetNetworkMap(tailcfg.NodeView{}, nodeViews([]*tailcfg.Node{ + { + ID: 2, + Key: nodeKey2, + DiscoKey: discoKey, + Endpoints: eps("192.168.1.2:345"), + }, + })) } de, ok := conn.peerMap.endpointForNodeKey(nodeKey2) @@ -1932,7 +1973,7 @@ func eps(s ...string) []netip.AddrPort { return eps } -func TestStressOnNodeViewsUpdate(t *testing.T) { +func TestStressSetNetworkMap(t *testing.T) { t.Parallel() conn := newTestConn(t) @@ -1988,9 +2029,7 @@ func TestStressOnNodeViewsUpdate(t *testing.T) { } } // Set the node views. - conn.onNodeViewsUpdate(NodeViewsUpdate{ - Peers: nodeViews(peers), - }) + conn.SetNetworkMap(tailcfg.NodeView{}, nodeViews(peers)) // Check invariants. if err := conn.peerMap.validate(); err != nil { t.Error(err) @@ -2113,10 +2152,10 @@ func TestRebindingUDPConn(t *testing.T) { } // https://github.com/tailscale/tailscale/issues/6680: don't ignore -// onNodeViewsUpdate calls when there are no peers. (A too aggressive fast path was +// SetNetworkMap calls when there are no peers. (A too aggressive fast path was // previously bailing out early, thinking there were no changes since all zero // peers didn't change, but the node views has non-peer info in it too we shouldn't discard) -func TestOnNodeViewsUpdateWithNoPeers(t *testing.T) { +func TestSetNetworkMapWithNoPeers(t *testing.T) { var c Conn knobs := &controlknobs.Knobs{} c.logf = logger.Discard @@ -2125,9 +2164,7 @@ func TestOnNodeViewsUpdateWithNoPeers(t *testing.T) { for i := 1; i <= 3; i++ { v := !debugEnableSilentDisco() envknob.Setenv("TS_DEBUG_ENABLE_SILENT_DISCO", fmt.Sprint(v)) - nv := NodeViewsUpdate{} - c.onNodeViewsUpdate(nv) - t.Logf("ptr %d: %p", i, nv) + c.SetNetworkMap(tailcfg.NodeView{}, nil) if c.lastFlags.heartbeatDisabled != v { t.Fatalf("call %d: didn't store netmap", i) } @@ -2215,11 +2252,7 @@ func TestIsWireGuardOnlyPeer(t *testing.T) { }, }), } - nv := NodeViewsUpdate{ - SelfNode: nm.SelfNode, - Peers: nm.Peers, - } - m.conn.onNodeViewsUpdate(nv) + m.conn.SetNetworkMap(nm.SelfNode, nm.Peers) cfg, err := nmcfg.WGCfg(m.privateKey, nm, t.Logf, netmap.AllowSubnetRoutes, "") if err != nil { @@ -2280,11 +2313,7 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) { }, }), } - nv := NodeViewsUpdate{ - SelfNode: nm.SelfNode, - Peers: nm.Peers, - } - m.conn.onNodeViewsUpdate(nv) + m.conn.SetNetworkMap(nm.SelfNode, nm.Peers) cfg, err := nmcfg.WGCfg(m.privateKey, nm, t.Logf, netmap.AllowSubnetRoutes, "") if err != nil { @@ -2321,11 +2350,7 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) { // configures WG. func applyNetworkMap(t *testing.T, m *magicStack, nm *netmap.NetworkMap) { t.Helper() - nv := NodeViewsUpdate{ - SelfNode: nm.SelfNode, - Peers: nm.Peers, - } - m.conn.onNodeViewsUpdate(nv) + m.conn.SetNetworkMap(nm.SelfNode, nm.Peers) // Make sure we can't use v6 to avoid test failures. m.conn.noV6.Store(true) @@ -3590,7 +3615,7 @@ func Test_nodeHasCap(t *testing.T) { } } -func TestConn_onNodeViewsUpdate_updateRelayServersSet(t *testing.T) { +func TestConn_SetNetworkMap_updateRelayServersSet(t *testing.T) { peerNodeCandidateRelay := &tailcfg.Node{ Cap: 121, ID: 1, @@ -3752,10 +3777,7 @@ func TestConn_onNodeViewsUpdate_updateRelayServersSet(t *testing.T) { c.hasPeerRelayServers.Store(true) } - c.onNodeViewsUpdate(NodeViewsUpdate{ - SelfNode: tt.self, - Peers: tt.peers, - }) + c.SetNetworkMap(tt.self, tt.peers) got := c.relayManager.getServers() if !got.Equal(tt.wantRelayServers) { t.Fatalf("got: %v != want: %v", got, tt.wantRelayServers) diff --git a/wgengine/magicsock/peermap.go b/wgengine/magicsock/peermap.go index 136353563e2bd..b6e9b08a360ed 100644 --- a/wgengine/magicsock/peermap.go +++ b/wgengine/magicsock/peermap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/peermap_test.go b/wgengine/magicsock/peermap_test.go index 171e22a6d5795..7fcd09384e540 100644 --- a/wgengine/magicsock/peermap_test.go +++ b/wgengine/magicsock/peermap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/peermtu.go b/wgengine/magicsock/peermtu.go index b675bf409cfa4..6f3df50a3c435 100644 --- a/wgengine/magicsock/peermtu.go +++ b/wgengine/magicsock/peermtu.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (darwin && !ios) || (linux && !android) diff --git a/wgengine/magicsock/peermtu_darwin.go b/wgengine/magicsock/peermtu_darwin.go index a0a1aacb55f5f..007c413f5efc5 100644 --- a/wgengine/magicsock/peermtu_darwin.go +++ b/wgengine/magicsock/peermtu_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/wgengine/magicsock/peermtu_linux.go b/wgengine/magicsock/peermtu_linux.go index b76f30f081042..5a6b5a64e9bc9 100644 --- a/wgengine/magicsock/peermtu_linux.go +++ b/wgengine/magicsock/peermtu_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/wgengine/magicsock/peermtu_stubs.go b/wgengine/magicsock/peermtu_stubs.go index e4f8038a42f21..a7fc5c99ba869 100644 --- a/wgengine/magicsock/peermtu_stubs.go +++ b/wgengine/magicsock/peermtu_stubs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (!linux && !darwin) || android || ios diff --git a/wgengine/magicsock/peermtu_unix.go b/wgengine/magicsock/peermtu_unix.go index eec3d744f3ded..7c394e98e0fa5 100644 --- a/wgengine/magicsock/peermtu_unix.go +++ b/wgengine/magicsock/peermtu_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (darwin && !ios) || (linux && !android) diff --git a/wgengine/magicsock/rebinding_conn.go b/wgengine/magicsock/rebinding_conn.go index c98e645705b46..e00eed1f5c88c 100644 --- a/wgengine/magicsock/rebinding_conn.go +++ b/wgengine/magicsock/rebinding_conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/relaymanager.go b/wgengine/magicsock/relaymanager.go index 69831a4df19f8..e4cd5eb9ff537 100644 --- a/wgengine/magicsock/relaymanager.go +++ b/wgengine/magicsock/relaymanager.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/relaymanager_test.go b/wgengine/magicsock/relaymanager_test.go index e8fddfd91b46e..7d773e381a7c4 100644 --- a/wgengine/magicsock/relaymanager_test.go +++ b/wgengine/magicsock/relaymanager_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/mem_ios.go b/wgengine/mem_ios.go index cc266ea3aadc8..f278359a8809b 100644 --- a/wgengine/mem_ios.go +++ b/wgengine/mem_ios.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine diff --git a/wgengine/netlog/netlog.go b/wgengine/netlog/netlog.go index 12fe9c797641a..e03a520f29bce 100644 --- a/wgengine/netlog/netlog.go +++ b/wgengine/netlog/netlog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail diff --git a/wgengine/netlog/netlog_omit.go b/wgengine/netlog/netlog_omit.go index 03610a1ef017a..041a183769554 100644 --- a/wgengine/netlog/netlog_omit.go +++ b/wgengine/netlog/netlog_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_netlog || ts_omit_logtail diff --git a/wgengine/netlog/netlog_test.go b/wgengine/netlog/netlog_test.go index b4758c7ec7beb..cc6968547cdbf 100644 --- a/wgengine/netlog/netlog_test.go +++ b/wgengine/netlog/netlog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail diff --git a/wgengine/netlog/record.go b/wgengine/netlog/record.go index 25b6b1148793a..62c3a6866da2a 100644 --- a/wgengine/netlog/record.go +++ b/wgengine/netlog/record.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail diff --git a/wgengine/netlog/record_test.go b/wgengine/netlog/record_test.go index ec0229534f244..1edae7450c842 100644 --- a/wgengine/netlog/record_test.go +++ b/wgengine/netlog/record_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail diff --git a/wgengine/netstack/gro/gro.go b/wgengine/netstack/gro/gro.go index c8e5e56e1acb5..152b252951c80 100644 --- a/wgengine/netstack/gro/gro.go +++ b/wgengine/netstack/gro/gro.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netstack diff --git a/wgengine/netstack/gro/gro_default.go b/wgengine/netstack/gro/gro_default.go index c70e19f7c5861..ac9d672ab8a6e 100644 --- a/wgengine/netstack/gro/gro_default.go +++ b/wgengine/netstack/gro/gro_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_gro diff --git a/wgengine/netstack/gro/gro_disabled.go b/wgengine/netstack/gro/gro_disabled.go index d7ffbd9139d99..9b2ae69955c97 100644 --- a/wgengine/netstack/gro/gro_disabled.go +++ b/wgengine/netstack/gro/gro_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || ts_omit_gro diff --git a/wgengine/netstack/gro/gro_test.go b/wgengine/netstack/gro/gro_test.go index 1eb200a05134c..49171b78c97aa 100644 --- a/wgengine/netstack/gro/gro_test.go +++ b/wgengine/netstack/gro/gro_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gro diff --git a/wgengine/netstack/gro/netstack_disabled.go b/wgengine/netstack/gro/netstack_disabled.go index a0f56fa4499cf..a61b90b48ed91 100644 --- a/wgengine/netstack/gro/netstack_disabled.go +++ b/wgengine/netstack/gro/netstack_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_netstack diff --git a/wgengine/netstack/link_endpoint.go b/wgengine/netstack/link_endpoint.go index c5a9dbcbca538..82b5446ac8789 100644 --- a/wgengine/netstack/link_endpoint.go +++ b/wgengine/netstack/link_endpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstack @@ -7,6 +7,7 @@ import ( "context" "sync" + "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -198,6 +199,43 @@ func (ep *linkEndpoint) injectInbound(p *packet.Parsed) { pkt.DecRef() } +// DeliverLoopback delivers pkt back into gVisor's network stack as if it +// arrived from the network, for self-addressed (loopback) packets. It takes +// ownership of one reference count on pkt. The caller must not use pkt after +// calling this method. It returns false if the dispatcher is not attached. +// +// Outbound packets from gVisor have their headers already parsed into separate +// views (NetworkHeader, TransportHeader, Data). DeliverNetworkPacket expects +// a raw unparsed packet, so we must re-serialize the packet into a new +// PacketBuffer with all bytes in the payload for gVisor to parse on inbound. +func (ep *linkEndpoint) DeliverLoopback(pkt *stack.PacketBuffer) bool { + ep.mu.RLock() + d := ep.dispatcher + ep.mu.RUnlock() + if d == nil { + pkt.DecRef() + return false + } + + // Serialize the outbound packet back to raw bytes. + raw := stack.PayloadSince(pkt.NetworkHeader()).AsSlice() + proto := pkt.NetworkProtocolNumber + + // We're done with the original outbound packet. + pkt.DecRef() + + // Create a new PacketBuffer from the raw bytes for inbound delivery. + newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(raw), + }) + newPkt.NetworkProtocolNumber = proto + newPkt.RXChecksumValidated = true + + d.DeliverNetworkPacket(proto, newPkt) + newPkt.DecRef() + return true +} + // Attach saves the stack network-layer dispatcher for use later when packets // are injected. func (ep *linkEndpoint) Attach(dispatcher stack.NetworkDispatcher) { diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index c2b5d8a3266c7..59c2613451fa5 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netstack wires up gVisor's netstack into Tailscale. @@ -51,6 +51,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/types/netmap" "tailscale.com/types/nettype" + "tailscale.com/types/views" "tailscale.com/util/clientmetric" "tailscale.com/util/set" "tailscale.com/version" @@ -165,6 +166,17 @@ type Impl struct { // over the UDP flow. GetUDPHandlerForFlow func(src, dst netip.AddrPort) (handler func(nettype.ConnPacketConn), intercept bool) + // CheckLocalTransportEndpoints, if true, causes netstack to check if gVisor + // has a registered endpoint for incoming packets to local IPs. This is used + // by tsnet to intercept packets for registered listeners and outbound + // connections when ProcessLocalIPs is false (i.e., when using a TUN). + // It can only be set before calling Start. + // TODO(raggi): refactor the way we handle both CheckLocalTransportEndpoints + // and the earlier netstack registrations for serve, funnel, peerAPI and so + // on. Currently this optimizes away cost for tailscaled in TUN mode, while + // enabling extension support when using tsnet in TUN mode. See #18423. + CheckLocalTransportEndpoints bool + // ProcessLocalIPs is whether netstack should handle incoming // traffic directed at the Node.Addresses (local IPs). // It can only be set before calling Start. @@ -189,6 +201,10 @@ type Impl struct { lb *ipnlocal.LocalBackend // or nil dns *dns.Manager + // Before Start is called, there can IPv6 Neighbor Discovery from the + // OS landing on netstack. We need to drop those packets until Start. + ready atomic.Bool // set to true once Start has been called + // loopbackPort, if non-nil, will enable Impl to loop back (dnat to // :loopbackPort) TCP & UDP flows originally // destined to serviceIP{v6}:loopbackPort. @@ -205,6 +221,10 @@ type Impl struct { atomicIsVIPServiceIPFunc syncs.AtomicValue[func(netip.Addr) bool] + atomicIPVIPServiceMap syncs.AtomicValue[netmap.IPServiceMappings] + // make this a set of strings for faster lookup + atomicActiveVIPServices syncs.AtomicValue[set.Set[tailcfg.ServiceName]] + // forwardDialFunc, if non-nil, is the net.Dialer.DialContext-style // function that is used to make outgoing connections when forwarding a // TCP connection to another host (e.g. in subnet router mode). @@ -593,10 +613,13 @@ func (ns *Impl) Start(b LocalBackend) error { } ns.lb = lb tcpFwd := tcp.NewForwarder(ns.ipstack, tcpRXBufDefSize, maxInFlightConnectionAttempts(), ns.acceptTCP) - udpFwd := udp.NewForwarder(ns.ipstack, ns.acceptUDP) + udpFwd := udp.NewForwarder(ns.ipstack, ns.acceptUDPNoICMP) ns.ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, ns.wrapTCPProtocolHandler(tcpFwd.HandlePacket)) ns.ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, ns.wrapUDPProtocolHandler(udpFwd.HandlePacket)) go ns.inject() + if ns.ready.Swap(true) { + panic("already started") + } return nil } @@ -754,6 +777,25 @@ func (ns *Impl) UpdateNetstackIPs(nm *netmap.NetworkMap) { } } +// UpdateIPServiceMappings updates the IPServiceMappings when there is a change +// in this value in localbackend. This is usually triggered from a netmap update. +func (ns *Impl) UpdateIPServiceMappings(mappings netmap.IPServiceMappings) { + ns.mu.Lock() + defer ns.mu.Unlock() + ns.atomicIPVIPServiceMap.Store(mappings) +} + +// UpdateActiveVIPServices updates the set of active VIP services names. +func (ns *Impl) UpdateActiveVIPServices(activeServices views.Slice[string]) { + ns.mu.Lock() + defer ns.mu.Unlock() + activeServicesSet := make(set.Set[tailcfg.ServiceName], activeServices.Len()) + for _, s := range activeServices.All() { + activeServicesSet.Add(tailcfg.AsServiceName(s)) + } + ns.atomicActiveVIPServices.Store(activeServicesSet) +} + func (ns *Impl) isLoopbackPort(port uint16) bool { if ns.loopbackPort != nil && int(port) == *ns.loopbackPort { return true @@ -764,13 +806,15 @@ func (ns *Impl) isLoopbackPort(port uint16) bool { // handleLocalPackets is hooked into the tun datapath for packets leaving // the host and arriving at tailscaled. This method returns filter.DropSilently // to intercept a packet for handling, for instance traffic to quad-100. +// Caution: can be called before Start func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper, gro *gro.GRO) (filter.Response, *gro.GRO) { - if ns.ctx.Err() != nil { + if !ns.ready.Load() || ns.ctx.Err() != nil { return filter.DropSilently, gro } // Determine if we care about this local packet. dst := p.Dst.Addr() + serviceName, isVIPServiceIP := ns.atomicIPVIPServiceMap.Load()[dst] switch { case dst == serviceIP || dst == serviceIPv6: // We want to intercept some traffic to the "service IP" (e.g. @@ -787,6 +831,25 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper, gro *gro. return filter.Accept, gro } } + case isVIPServiceIP: + // returns all active VIP services in a set, since the IPVIPServiceMap + // contains inactive service IPs when node hosts the service, we need to + // check the service is active or not before dropping the packet. + activeServices := ns.atomicActiveVIPServices.Load() + if !activeServices.Contains(serviceName) { + // Other host might have the service active, so we let the packet go through. + return filter.Accept, gro + } + if p.IPProto != ipproto.TCP { + // We currenly only support VIP services over TCP. If service is in Tun mode, + // it's up to the service host to set up local packet handling which shouldn't + // arrive here. + return filter.DropSilently, gro + } + if debugNetstack() { + ns.logf("netstack: intercepting local VIP service packet: proto=%v dst=%v src=%v", + p.IPProto, p.Dst, p.Src) + } case viaRange.Contains(dst): // We need to handle 4via6 packets leaving the host if the via // route is for this host; otherwise the packet will be dropped @@ -974,6 +1037,16 @@ func (ns *Impl) inject() { return } } else { + // Self-addressed packet: deliver back into gVisor directly + // via the link endpoint's dispatcher, but only if the packet is not + // earmarked for the host. Neither the inbound path (fakeTUN Write is a + // no-op) nor the outbound path (WireGuard has no peer for our own IP) + // can handle these. + if ns.isSelfDst(pkt) { + ns.linkEP.DeliverLoopback(pkt) + continue + } + if err := ns.tundev.InjectOutboundPacketBuffer(pkt); err != nil { ns.logf("netstack inject outbound: %v", err) return @@ -998,12 +1071,32 @@ func (ns *Impl) shouldSendToHost(pkt *stack.PacketBuffer) bool { return true } + if ns.isVIPServiceIP(srcIP) { + dstIP := netip.AddrFrom4(v.DestinationAddress().As4()) + if ns.isLocalIP(dstIP) { + if debugNetstack() { + ns.logf("netstack: sending VIP service packet to host: src=%v dst=%v", srcIP, dstIP) + } + return true + } + } + case header.IPv6: srcIP := netip.AddrFrom16(v.SourceAddress().As16()) if srcIP == serviceIPv6 { return true } + if ns.isVIPServiceIP(srcIP) { + dstIP := netip.AddrFrom16(v.DestinationAddress().As16()) + if ns.isLocalIP(dstIP) { + if debugNetstack() { + ns.logf("netstack: sending VIP service packet to host: src=%v dst=%v", srcIP, dstIP) + } + return true + } + } + if viaRange.Contains(srcIP) { // Only send to the host if this 4via6 route is // something this node handles. @@ -1033,6 +1126,20 @@ func (ns *Impl) shouldSendToHost(pkt *stack.PacketBuffer) bool { return false } +// isSelfDst reports whether pkt's destination IP is a local Tailscale IP +// assigned to this node. This is used by inject() to detect self-addressed +// packets that need loopback delivery. +func (ns *Impl) isSelfDst(pkt *stack.PacketBuffer) bool { + hdr := pkt.Network() + switch v := hdr.(type) { + case header.IPv4: + return ns.isLocalIP(netip.AddrFrom4(v.DestinationAddress().As4())) + case header.IPv6: + return ns.isLocalIP(netip.AddrFrom16(v.DestinationAddress().As16())) + } + return false +} + // isLocalIP reports whether ip is a Tailscale IP assigned to this // node directly (but not a subnet-routed IP). func (ns *Impl) isLocalIP(ip netip.Addr) bool { @@ -1109,6 +1216,45 @@ func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool { if ns.ProcessSubnets && !isLocal { return true } + if isLocal && ns.CheckLocalTransportEndpoints { + // Handle packets to registered listeners and replies to outbound + // connections by checking if gVisor has a registered endpoint. + // This covers TCP listeners, UDP listeners, and outbound TCP replies. + if p.IPProto == ipproto.TCP || p.IPProto == ipproto.UDP { + var netProto tcpip.NetworkProtocolNumber + var id stack.TransportEndpointID + if p.Dst.Addr().Is4() { + netProto = ipv4.ProtocolNumber + id = stack.TransportEndpointID{ + LocalAddress: tcpip.AddrFrom4(p.Dst.Addr().As4()), + LocalPort: p.Dst.Port(), + RemoteAddress: tcpip.AddrFrom4(p.Src.Addr().As4()), + RemotePort: p.Src.Port(), + } + } else { + netProto = ipv6.ProtocolNumber + id = stack.TransportEndpointID{ + LocalAddress: tcpip.AddrFrom16(p.Dst.Addr().As16()), + LocalPort: p.Dst.Port(), + RemoteAddress: tcpip.AddrFrom16(p.Src.Addr().As16()), + RemotePort: p.Src.Port(), + } + } + var transProto tcpip.TransportProtocolNumber + if p.IPProto == ipproto.TCP { + transProto = tcp.ProtocolNumber + } else { + transProto = udp.ProtocolNumber + } + ep := ns.ipstack.FindTransportEndpoint(netProto, transProto, id, nicID) + if debugNetstack() { + ns.logf("[v2] FindTransportEndpoint: id=%+v found=%v", id, ep != nil) + } + if ep != nil { + return true + } + } + } return false } @@ -1183,8 +1329,9 @@ func (ns *Impl) userPing(dstIP netip.Addr, pingResPkt []byte, direction userPing // continue normally (typically being delivered to the host networking stack), // whereas returning filter.DropSilently is done when netstack intercepts the // packet and no further processing towards to host should be done. +// Caution: can be called before Start func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper, gro *gro.GRO) (filter.Response, *gro.GRO) { - if ns.ctx.Err() != nil { + if !ns.ready.Load() || ns.ctx.Err() != nil { return filter.DropSilently, gro } @@ -1575,7 +1722,7 @@ func (ns *Impl) forwardTCP(getClient func(...tcpip.SettableSocketOption) *gonet. func (ns *Impl) ListenPacket(network, address string) (net.PacketConn, error) { ap, err := netip.ParseAddrPort(address) if err != nil { - return nil, fmt.Errorf("netstack: ParseAddrPort(%q): %v", address, err) + return nil, fmt.Errorf("netstack: ParseAddrPort(%q): %w", address, err) } var networkProto tcpip.NetworkProtocolNumber @@ -1612,6 +1759,53 @@ func (ns *Impl) ListenPacket(network, address string) (net.PacketConn, error) { return gonet.NewUDPConn(&wq, ep), nil } +// ListenTCP listens for TCP connections on the given address. +func (ns *Impl) ListenTCP(network, address string) (*gonet.TCPListener, error) { + ap, err := netip.ParseAddrPort(address) + if err != nil { + return nil, fmt.Errorf("netstack: ParseAddrPort(%q): %w", address, err) + } + + var networkProto tcpip.NetworkProtocolNumber + switch network { + case "tcp4": + networkProto = ipv4.ProtocolNumber + if ap.Addr().IsValid() && !ap.Addr().Is4() { + return nil, fmt.Errorf("netstack: tcp4 requires an IPv4 address") + } + case "tcp6": + networkProto = ipv6.ProtocolNumber + if ap.Addr().IsValid() && !ap.Addr().Is6() { + return nil, fmt.Errorf("netstack: tcp6 requires an IPv6 address") + } + default: + return nil, fmt.Errorf("netstack: unsupported network %q", network) + } + + localAddress := tcpip.FullAddress{ + NIC: nicID, + Port: ap.Port(), + } + if ap.Addr().IsValid() && !ap.Addr().IsUnspecified() { + localAddress.Addr = tcpip.AddrFromSlice(ap.Addr().AsSlice()) + } + + return gonet.ListenTCP(ns.ipstack, localAddress, networkProto) +} + +// acceptUDPNoICMP wraps acceptUDP to satisfy udp.ForwarderHandler. +// A gvisor bump from 9414b50a to 573d5e71 on 2026-02-27 changed +// udp.ForwarderHandler from func(*ForwarderRequest) to +// func(*ForwarderRequest) bool, where returning false means unhandled +// and causes gvisor to send an ICMP port unreachable. Previously there +// was no such distinction and all packets were implicitly treated as +// handled. Always returning true preserves the old behavior of silently +// dropping packets we don't service rather than sending ICMP errors. +func (ns *Impl) acceptUDPNoICMP(r *udp.ForwarderRequest) bool { + ns.acceptUDP(r) + return true +} + func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) { sess := r.ID() if debugNetstack() { diff --git a/wgengine/netstack/netstack_linux.go b/wgengine/netstack/netstack_linux.go index a0bfb44567da7..a0f431cf6fdb4 100644 --- a/wgengine/netstack/netstack_linux.go +++ b/wgengine/netstack/netstack_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstack diff --git a/wgengine/netstack/netstack_tcpbuf_default.go b/wgengine/netstack/netstack_tcpbuf_default.go index 3640964ffe399..ed93175c4fed1 100644 --- a/wgengine/netstack/netstack_tcpbuf_default.go +++ b/wgengine/netstack/netstack_tcpbuf_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios diff --git a/wgengine/netstack/netstack_tcpbuf_ios.go b/wgengine/netstack/netstack_tcpbuf_ios.go index a4210c9ac7517..a5368da8633d7 100644 --- a/wgengine/netstack/netstack_tcpbuf_ios.go +++ b/wgengine/netstack/netstack_tcpbuf_ios.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go index 93022811ce409..da262fc13acbd 100644 --- a/wgengine/netstack/netstack_test.go +++ b/wgengine/netstack/netstack_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstack @@ -15,6 +15,7 @@ import ( "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/stack" "tailscale.com/envknob" @@ -31,6 +32,7 @@ import ( "tailscale.com/tstest" "tailscale.com/types/ipproto" "tailscale.com/types/logid" + "tailscale.com/types/netmap" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" ) @@ -125,6 +127,7 @@ func makeNetstack(tb testing.TB, config func(*Impl)) *Impl { tb.Fatal(err) } tb.Cleanup(func() { ns.Close() }) + sys.Set(ns) lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0) if err != nil { @@ -741,13 +744,20 @@ func TestHandleLocalPackets(t *testing.T) { // fd7a:115c:a1e0:b1a:0:7:a01:100/120 netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:7:a01:100/120"), } + prefs.AdvertiseServices = []string{"svc:test-service"} _, err := impl.lb.EditPrefs(&ipn.MaskedPrefs{ - Prefs: *prefs, - AdvertiseRoutesSet: true, + Prefs: *prefs, + AdvertiseRoutesSet: true, + AdvertiseServicesSet: true, }) if err != nil { t.Fatalf("EditPrefs: %v", err) } + IPServiceMap := netmap.IPServiceMappings{ + netip.MustParseAddr("100.99.55.111"): "svc:test-service", + netip.MustParseAddr("fd7a:115c:a1e0::abcd"): "svc:test-service", + } + impl.lb.SetIPServiceMappingsForTest(IPServiceMap) t.Run("ShouldHandleServiceIP", func(t *testing.T) { pkt := &packet.Parsed{ @@ -784,6 +794,19 @@ func TestHandleLocalPackets(t *testing.T) { t.Errorf("got filter outcome %v, want filter.DropSilently", resp) } }) + t.Run("ShouldHandleLocalTailscaleServices", func(t *testing.T) { + pkt := &packet.Parsed{ + IPVersion: 4, + IPProto: ipproto.TCP, + Src: netip.MustParseAddrPort("127.0.0.1:9999"), + Dst: netip.MustParseAddrPort("100.99.55.111:80"), + TCPFlags: packet.TCPSyn, + } + resp, _ := impl.handleLocalPackets(pkt, impl.tundev, nil) + if resp != filter.DropSilently { + t.Errorf("got filter outcome %v, want filter.DropSilently", resp) + } + }) t.Run("OtherNonHandled", func(t *testing.T) { pkt := &packet.Parsed{ IPVersion: 6, @@ -809,8 +832,10 @@ func TestHandleLocalPackets(t *testing.T) { func TestShouldSendToHost(t *testing.T) { var ( - selfIP4 = netip.MustParseAddr("100.64.1.2") - selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123") + selfIP4 = netip.MustParseAddr("100.64.1.2") + selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123") + tailscaleServiceIP4 = netip.MustParseAddr("100.99.55.111") + tailscaleServiceIP6 = netip.MustParseAddr("fd7a:115c:a1e0::abcd") ) makeTestNetstack := func(tb testing.TB) *Impl { @@ -820,6 +845,9 @@ func TestShouldSendToHost(t *testing.T) { impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool { return addr == selfIP4 || addr == selfIP6 }) + impl.atomicIsVIPServiceIPFunc.Store(func(addr netip.Addr) bool { + return addr == tailscaleServiceIP4 || addr == tailscaleServiceIP6 + }) }) prefs := ipn.NewPrefs() @@ -919,6 +947,33 @@ func TestShouldSendToHost(t *testing.T) { dst: netip.MustParseAddrPort("[fd7a:115:a1e0::99]:7777"), want: false, }, + // After accessing the Tailscale service from host, replies from Tailscale Service IPs + // to the local Tailscale IPs should be sent to the host. + { + name: "from_service_ip_to_local_ip", + src: netip.AddrPortFrom(tailscaleServiceIP4, 80), + dst: netip.AddrPortFrom(selfIP4, 12345), + want: true, + }, + { + name: "from_service_ip_to_local_ip_v6", + src: netip.AddrPortFrom(tailscaleServiceIP6, 80), + dst: netip.AddrPortFrom(selfIP6, 12345), + want: true, + }, + // Traffic from remote IPs to Tailscale Service IPs should be sent over WireGuard. + { + name: "from_service_ip_to_remote", + src: netip.AddrPortFrom(tailscaleServiceIP4, 80), + dst: netip.MustParseAddrPort("173.201.32.56:54321"), + want: false, + }, + { + name: "from_service_ip_to_remote_v6", + src: netip.AddrPortFrom(tailscaleServiceIP6, 80), + dst: netip.MustParseAddrPort("[2001:4860:4860::8888]:54321"), + want: false, + }, } for _, tt := range testCases { @@ -1019,3 +1074,295 @@ func makeUDP6PacketBuffer(src, dst netip.AddrPort) *stack.PacketBuffer { return pkt } + +// TestIsSelfDst verifies that isSelfDst correctly identifies packets whose +// destination IP is a local Tailscale IP assigned to this node. +func TestIsSelfDst(t *testing.T) { + var ( + selfIP4 = netip.MustParseAddr("100.64.1.2") + selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123") + remoteIP4 = netip.MustParseAddr("100.64.99.88") + remoteIP6 = netip.MustParseAddr("fd7a:115c:a1e0::99") + ) + + ns := makeNetstack(t, func(impl *Impl) { + impl.ProcessLocalIPs = true + impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool { + return addr == selfIP4 || addr == selfIP6 + }) + }) + + testCases := []struct { + name string + src, dst netip.AddrPort + want bool + }{ + { + name: "self_to_self_v4", + src: netip.AddrPortFrom(selfIP4, 12345), + dst: netip.AddrPortFrom(selfIP4, 8081), + want: true, + }, + { + name: "self_to_self_v6", + src: netip.AddrPortFrom(selfIP6, 12345), + dst: netip.AddrPortFrom(selfIP6, 8081), + want: true, + }, + { + name: "remote_to_self_v4", + src: netip.AddrPortFrom(remoteIP4, 12345), + dst: netip.AddrPortFrom(selfIP4, 8081), + want: true, + }, + { + name: "remote_to_self_v6", + src: netip.AddrPortFrom(remoteIP6, 12345), + dst: netip.AddrPortFrom(selfIP6, 8081), + want: true, + }, + { + name: "self_to_remote_v4", + src: netip.AddrPortFrom(selfIP4, 12345), + dst: netip.AddrPortFrom(remoteIP4, 8081), + want: false, + }, + { + name: "self_to_remote_v6", + src: netip.AddrPortFrom(selfIP6, 12345), + dst: netip.AddrPortFrom(remoteIP6, 8081), + want: false, + }, + { + name: "remote_to_remote_v4", + src: netip.AddrPortFrom(remoteIP4, 12345), + dst: netip.MustParseAddrPort("100.64.77.66:7777"), + want: false, + }, + { + name: "service_ip_to_self_v4", + src: netip.AddrPortFrom(serviceIP, 53), + dst: netip.AddrPortFrom(selfIP4, 9999), + want: true, + }, + { + name: "service_ip_to_self_v6", + src: netip.AddrPortFrom(serviceIPv6, 53), + dst: netip.AddrPortFrom(selfIP6, 9999), + want: true, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + var pkt *stack.PacketBuffer + if tt.src.Addr().Is4() { + pkt = makeUDP4PacketBuffer(tt.src, tt.dst) + } else { + pkt = makeUDP6PacketBuffer(tt.src, tt.dst) + } + defer pkt.DecRef() + + if got := ns.isSelfDst(pkt); got != tt.want { + t.Errorf("isSelfDst(%v -> %v) = %v, want %v", tt.src, tt.dst, got, tt.want) + } + }) + } +} + +// TestDeliverLoopback verifies that DeliverLoopback correctly re-serializes an +// outbound packet and delivers it back into gVisor's inbound path. +func TestDeliverLoopback(t *testing.T) { + ep := newLinkEndpoint(64, 1280, "", groNotSupported) + + // Track delivered packets via a mock dispatcher. + type delivered struct { + proto tcpip.NetworkProtocolNumber + data []byte + } + deliveredCh := make(chan delivered, 4) + ep.Attach(&mockDispatcher{ + onDeliverNetworkPacket: func(proto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { + // Capture the raw bytes from the delivered packet. At this + // point the packet is unparsed — everything is in the + // payload, no headers have been consumed yet. + buf := pkt.ToBuffer() + raw := buf.Flatten() + deliveredCh <- delivered{proto: proto, data: raw} + }, + }) + + t.Run("ipv4", func(t *testing.T) { + selfAddr := netip.MustParseAddrPort("100.64.1.2:8081") + pkt := makeUDP4PacketBuffer(selfAddr, selfAddr) + // Capture what the outbound bytes look like before loopback. + wantLen := pkt.Size() + wantProto := pkt.NetworkProtocolNumber + + if !ep.DeliverLoopback(pkt) { + t.Fatal("DeliverLoopback returned false") + } + + select { + case got := <-deliveredCh: + if got.proto != wantProto { + t.Errorf("proto = %d, want %d", got.proto, wantProto) + } + if len(got.data) != wantLen { + t.Errorf("data length = %d, want %d", len(got.data), wantLen) + } + case <-time.After(time.Second): + t.Fatal("timeout waiting for loopback delivery") + } + }) + + t.Run("ipv6", func(t *testing.T) { + selfAddr := netip.MustParseAddrPort("[fd7a:115c:a1e0::123]:8081") + pkt := makeUDP6PacketBuffer(selfAddr, selfAddr) + wantLen := pkt.Size() + wantProto := pkt.NetworkProtocolNumber + + if !ep.DeliverLoopback(pkt) { + t.Fatal("DeliverLoopback returned false") + } + + select { + case got := <-deliveredCh: + if got.proto != wantProto { + t.Errorf("proto = %d, want %d", got.proto, wantProto) + } + if len(got.data) != wantLen { + t.Errorf("data length = %d, want %d", len(got.data), wantLen) + } + case <-time.After(time.Second): + t.Fatal("timeout waiting for loopback delivery") + } + }) + + t.Run("nil_dispatcher", func(t *testing.T) { + ep2 := newLinkEndpoint(64, 1280, "", groNotSupported) + // Don't attach a dispatcher. + selfAddr := netip.MustParseAddrPort("100.64.1.2:8081") + pkt := makeUDP4PacketBuffer(selfAddr, selfAddr) + if ep2.DeliverLoopback(pkt) { + t.Error("DeliverLoopback should return false with nil dispatcher") + } + // pkt refcount was consumed by DeliverLoopback, so we don't DecRef. + }) +} + +// mockDispatcher implements stack.NetworkDispatcher for testing. +type mockDispatcher struct { + onDeliverNetworkPacket func(tcpip.NetworkProtocolNumber, *stack.PacketBuffer) +} + +func (d *mockDispatcher) DeliverNetworkPacket(proto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { + if d.onDeliverNetworkPacket != nil { + d.onDeliverNetworkPacket(proto, pkt) + } +} + +func (d *mockDispatcher) DeliverLinkPacket(tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {} + +// udp4raw constructs a valid raw IPv4+UDP packet with proper checksums. +func udp4raw(t testing.TB, src, dst netip.Addr, sport, dport uint16, payload []byte) []byte { + t.Helper() + totalLen := header.IPv4MinimumSize + header.UDPMinimumSize + len(payload) + buf := make([]byte, totalLen) + + ip := header.IPv4(buf) + ip.Encode(&header.IPv4Fields{ + TotalLength: uint16(totalLen), + Protocol: uint8(header.UDPProtocolNumber), + TTL: 64, + SrcAddr: tcpip.AddrFrom4Slice(src.AsSlice()), + DstAddr: tcpip.AddrFrom4Slice(dst.AsSlice()), + }) + ip.SetChecksum(^ip.CalculateChecksum()) + + // Build UDP header + payload. + u := header.UDP(buf[header.IPv4MinimumSize:]) + u.Encode(&header.UDPFields{ + SrcPort: sport, + DstPort: dport, + Length: uint16(header.UDPMinimumSize + len(payload)), + }) + copy(buf[header.IPv4MinimumSize+header.UDPMinimumSize:], payload) + + xsum := header.PseudoHeaderChecksum( + header.UDPProtocolNumber, + tcpip.AddrFrom4Slice(src.AsSlice()), + tcpip.AddrFrom4Slice(dst.AsSlice()), + uint16(header.UDPMinimumSize+len(payload)), + ) + u.SetChecksum(^header.UDP(buf[header.IPv4MinimumSize:]).CalculateChecksum(xsum)) + return buf +} + +// TestInjectLoopback verifies that the inject goroutine delivers self-addressed +// packets back into gVisor (via DeliverLoopback) instead of sending them to +// WireGuard outbound. This is a regression test for a bug where self-dial +// packets were sent to WireGuard and silently dropped. +func TestInjectLoopback(t *testing.T) { + selfIP4 := netip.MustParseAddr("100.64.1.2") + + ns := makeNetstack(t, func(impl *Impl) { + impl.ProcessLocalIPs = true + impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool { + return addr == selfIP4 + }) + }) + + // Register gVisor's NIC address so the stack accepts and routes + // packets for this IP. + protocolAddr := tcpip.ProtocolAddress{ + Protocol: header.IPv4ProtocolNumber, + AddressWithPrefix: tcpip.AddrFrom4(selfIP4.As4()).WithPrefix(), + } + if err := ns.ipstack.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { + t.Fatalf("AddProtocolAddress: %v", err) + } + + // Bind a UDP socket on the gVisor stack to receive the loopback packet. + pc, err := gonet.DialUDP(ns.ipstack, &tcpip.FullAddress{ + NIC: nicID, + Addr: tcpip.AddrFrom4(selfIP4.As4()), + Port: 8081, + }, nil, header.IPv4ProtocolNumber) + if err != nil { + t.Fatalf("DialUDP: %v", err) + } + defer pc.Close() + + // Build a valid self-addressed UDP packet from raw bytes and wrap it + // in a gVisor PacketBuffer with headers already pushed, as gVisor's + // outbound path produces. + payload := []byte("loopback test") + raw := udp4raw(t, selfIP4, selfIP4, 12345, 8081, payload) + + pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + ReserveHeaderBytes: header.IPv4MinimumSize + header.UDPMinimumSize, + Payload: buffer.MakeWithData(payload), + }) + copy(pkt.TransportHeader().Push(header.UDPMinimumSize), + raw[header.IPv4MinimumSize:header.IPv4MinimumSize+header.UDPMinimumSize]) + pkt.TransportProtocolNumber = header.UDPProtocolNumber + copy(pkt.NetworkHeader().Push(header.IPv4MinimumSize), raw[:header.IPv4MinimumSize]) + pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber + + if err := ns.linkEP.q.Write(pkt); err != nil { + t.Fatalf("queue.Write: %v", err) + } + + // The inject goroutine should detect the self-addressed packet via + // isSelfDst and deliver it back into gVisor via DeliverLoopback. + pc.SetReadDeadline(time.Now().Add(5 * time.Second)) + buf := make([]byte, 256) + n, _, err := pc.ReadFrom(buf) + if err != nil { + t.Fatalf("ReadFrom: %v (self-addressed packet was not looped back)", err) + } + if got := string(buf[:n]); got != "loopback test" { + t.Errorf("got %q, want %q", got, "loopback test") + } +} diff --git a/wgengine/netstack/netstack_userping.go b/wgengine/netstack/netstack_userping.go index b35a6eca9e11b..d42c8fbe7c271 100644 --- a/wgengine/netstack/netstack_userping.go +++ b/wgengine/netstack/netstack_userping.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !darwin && !ios diff --git a/wgengine/netstack/netstack_userping_apple.go b/wgengine/netstack/netstack_userping_apple.go index 52fb7a24a4c41..a82b81e99e827 100644 --- a/wgengine/netstack/netstack_userping_apple.go +++ b/wgengine/netstack/netstack_userping_apple.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || ios diff --git a/wgengine/netstack/netstack_userping_test.go b/wgengine/netstack/netstack_userping_test.go index a179f74673469..cba298d453698 100644 --- a/wgengine/netstack/netstack_userping_test.go +++ b/wgengine/netstack/netstack_userping_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstack diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index 7eaf43e52a816..77cb4a7b9b451 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debug diff --git a/wgengine/pendopen_omit.go b/wgengine/pendopen_omit.go index 013425d357f26..01d33306b291e 100644 --- a/wgengine/pendopen_omit.go +++ b/wgengine/pendopen_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_debug diff --git a/wgengine/router/callback.go b/wgengine/router/callback.go index c1838539ba2a3..11ce832f4f5e4 100644 --- a/wgengine/router/callback.go +++ b/wgengine/router/callback.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/router/consolidating_router.go b/wgengine/router/consolidating_router.go index 10c4825d8856a..14283330b124c 100644 --- a/wgengine/router/consolidating_router.go +++ b/wgengine/router/consolidating_router.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/router/consolidating_router_test.go b/wgengine/router/consolidating_router_test.go index ba2e4d07a746a..1bf79a29d614d 100644 --- a/wgengine/router/consolidating_router_test.go +++ b/wgengine/router/consolidating_router_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/router/osrouter/ifconfig_windows_test.go b/wgengine/router/osrouter/ifconfig_windows_test.go index b858ef4f60d19..f272a59f899f6 100644 --- a/wgengine/router/osrouter/ifconfig_windows_test.go +++ b/wgengine/router/osrouter/ifconfig_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/osrouter.go b/wgengine/router/osrouter/osrouter.go index 281454b069984..ac4e48c7268c7 100644 --- a/wgengine/router/osrouter/osrouter.go +++ b/wgengine/router/osrouter/osrouter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package osrouter contains OS-specific router implementations. diff --git a/wgengine/router/osrouter/osrouter_test.go b/wgengine/router/osrouter/osrouter_test.go index d0cb3db6968c1..5e81d6297d035 100644 --- a/wgengine/router/osrouter/osrouter_test.go +++ b/wgengine/router/osrouter/osrouter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_freebsd.go b/wgengine/router/osrouter/router_freebsd.go index a142e7a84e14a..c1e1a389b8537 100644 --- a/wgengine/router/osrouter/router_freebsd.go +++ b/wgengine/router/osrouter/router_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_linux.go b/wgengine/router/osrouter/router_linux.go index 7442c045ee079..3c261c9120785 100644 --- a/wgengine/router/osrouter/router_linux.go +++ b/wgengine/router/osrouter/router_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android @@ -86,6 +86,7 @@ type linuxRouter struct { localRoutes map[netip.Prefix]bool snatSubnetRoutes bool statefulFiltering bool + connmarkEnabled bool // whether connmark rules are currently enabled netfilterMode preftype.NetfilterMode netfilterKind string magicsockPortV4 uint16 @@ -370,6 +371,12 @@ func (r *linuxRouter) Close() error { r.unregNetMon() } r.eventClient.Close() + + // Clean up connmark rules + if err := r.nfr.DelConnmarkSaveRule(); err != nil { + r.logf("warning: failed to delete connmark rules: %v", err) + } + if err := r.downInterface(); err != nil { return err } @@ -479,6 +486,35 @@ func (r *linuxRouter) Set(cfg *router.Config) error { r.statefulFiltering = cfg.StatefulFiltering r.updateStatefulFilteringWithDockerWarning(cfg) + // Connmark rules for rp_filter compatibility. + // Always enabled when netfilter is ON to handle all rp_filter=1 scenarios + // (normal operation, exit nodes, subnet routers, and clients using exit nodes). + netfilterOn := cfg.NetfilterMode == netfilterOn + switch { + case netfilterOn == r.connmarkEnabled: + // state already correct, nothing to do. + case netfilterOn: + r.logf("enabling connmark-based rp_filter workaround") + if err := r.nfr.AddConnmarkSaveRule(); err != nil { + r.logf("warning: failed to add connmark rules (rp_filter workaround may not work): %v", err) + errs = append(errs, fmt.Errorf("enabling connmark rules: %w", err)) + } else { + // Only update state on success to keep it in sync with actual rules + r.connmarkEnabled = true + } + default: + r.logf("disabling connmark-based rp_filter workaround") + if err := r.nfr.DelConnmarkSaveRule(); err != nil { + // Deletion errors are only logged, not returned, because: + // 1. Rules may not exist (e.g., first run or after manual deletion) + // 2. Failure to delete is less critical than failure to add + // 3. We still want to update state to attempt re-add on next enable + r.logf("warning: failed to delete connmark rules: %v", err) + } + // Always clear state when disabling, even if delete failed + r.connmarkEnabled = false + } + // Issue 11405: enable IP forwarding on gokrazy. advertisingRoutes := len(cfg.SubnetRoutes) > 0 if getDistroFunc() == distro.Gokrazy && advertisingRoutes { diff --git a/wgengine/router/osrouter/router_linux_test.go b/wgengine/router/osrouter/router_linux_test.go index 68ed8dbb2bb64..bae997e331d55 100644 --- a/wgengine/router/osrouter/router_linux_test.go +++ b/wgengine/router/osrouter/router_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter @@ -124,6 +124,8 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE v6/filter/FORWARD -j ts-forward @@ -132,6 +134,8 @@ v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -m conntrack ! --ctstate ESTABLISHED,RELATED -j DROP v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE `, @@ -160,6 +164,8 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE v6/filter/FORWARD -j ts-forward @@ -167,6 +173,8 @@ v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE `, @@ -192,12 +200,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -225,12 +237,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -255,12 +271,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -310,12 +330,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -342,12 +366,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -367,6 +395,120 @@ ip route add 100.100.100.100/32 dev tailscale0 table 52 ip route add throw 10.0.0.0/8 table 52 ip route add throw 192.168.0.0/24 table 52` + basic, }, + { + name: "subnet routes with connmark for rp_filter", + in: &Config{ + LocalAddrs: mustCIDRs("100.101.102.104/10"), + Routes: mustCIDRs("100.100.100.100/32"), + SubnetRoutes: mustCIDRs("10.0.0.0/16"), + SNATSubnetRoutes: true, + NetfilterMode: netfilterOn, + }, + want: ` +up +ip addr add 100.101.102.104/10 dev tailscale0 +ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic + + `v4/filter/FORWARD -j ts-forward +v4/filter/INPUT -j ts-input +v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v4/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP +v4/filter/ts-forward -o tailscale0 -j ACCEPT +v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT +v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN +v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/nat/POSTROUTING -j ts-postrouting +v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +v6/filter/FORWARD -j ts-forward +v6/filter/INPUT -j ts-input +v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/nat/POSTROUTING -j ts-postrouting +v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +`, + }, + { + name: "subnet routes (connmark always enabled)", + in: &Config{ + LocalAddrs: mustCIDRs("100.101.102.104/10"), + Routes: mustCIDRs("100.100.100.100/32"), + SubnetRoutes: mustCIDRs("10.0.0.0/16"), + SNATSubnetRoutes: true, + NetfilterMode: netfilterOn, + }, + want: ` +up +ip addr add 100.101.102.104/10 dev tailscale0 +ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic + + `v4/filter/FORWARD -j ts-forward +v4/filter/INPUT -j ts-input +v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v4/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP +v4/filter/ts-forward -o tailscale0 -j ACCEPT +v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT +v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN +v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/nat/POSTROUTING -j ts-postrouting +v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +v6/filter/FORWARD -j ts-forward +v6/filter/INPUT -j ts-input +v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/nat/POSTROUTING -j ts-postrouting +v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +`, + }, + { + name: "connmark with stateful filtering", + in: &Config{ + LocalAddrs: mustCIDRs("100.101.102.104/10"), + Routes: mustCIDRs("100.100.100.100/32"), + SubnetRoutes: mustCIDRs("10.0.0.0/16"), + SNATSubnetRoutes: true, + StatefulFiltering: true, + NetfilterMode: netfilterOn, + }, + want: ` +up +ip addr add 100.101.102.104/10 dev tailscale0 +ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic + + `v4/filter/FORWARD -j ts-forward +v4/filter/INPUT -j ts-input +v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v4/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP +v4/filter/ts-forward -o tailscale0 -m conntrack ! --ctstate ESTABLISHED,RELATED -j DROP +v4/filter/ts-forward -o tailscale0 -j ACCEPT +v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT +v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN +v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/nat/POSTROUTING -j ts-postrouting +v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +v6/filter/FORWARD -j ts-forward +v6/filter/INPUT -j ts-input +v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v6/filter/ts-forward -o tailscale0 -m conntrack ! --ctstate ESTABLISHED,RELATED -j DROP +v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/nat/POSTROUTING -j ts-postrouting +v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +`, + }, } bus := eventbus.New() @@ -426,20 +568,24 @@ func newIPTablesRunner(t *testing.T) linuxfw.NetfilterRunner { return &fakeIPTablesRunner{ t: t, ipt4: map[string][]string{ - "filter/INPUT": nil, - "filter/OUTPUT": nil, - "filter/FORWARD": nil, - "nat/PREROUTING": nil, - "nat/OUTPUT": nil, - "nat/POSTROUTING": nil, + "filter/INPUT": nil, + "filter/OUTPUT": nil, + "filter/FORWARD": nil, + "nat/PREROUTING": nil, + "nat/OUTPUT": nil, + "nat/POSTROUTING": nil, + "mangle/PREROUTING": nil, + "mangle/OUTPUT": nil, }, ipt6: map[string][]string{ - "filter/INPUT": nil, - "filter/OUTPUT": nil, - "filter/FORWARD": nil, - "nat/PREROUTING": nil, - "nat/OUTPUT": nil, - "nat/POSTROUTING": nil, + "filter/INPUT": nil, + "filter/OUTPUT": nil, + "filter/FORWARD": nil, + "nat/PREROUTING": nil, + "nat/OUTPUT": nil, + "nat/POSTROUTING": nil, + "mangle/PREROUTING": nil, + "mangle/OUTPUT": nil, }, } } @@ -775,6 +921,38 @@ func (n *fakeIPTablesRunner) DelMagicsockPortRule(port uint16, network string) e return nil } +func (n *fakeIPTablesRunner) AddConnmarkSaveRule() error { + // PREROUTING rule: restore mark from conntrack + prerouteRule := "-m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000" + for _, ipt := range []map[string][]string{n.ipt4, n.ipt6} { + if err := insertRule(n, ipt, "mangle/PREROUTING", prerouteRule); err != nil { + return err + } + } + + // OUTPUT rule: save mark to conntrack for NEW connections + outputRule := "-m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000" + for _, ipt := range []map[string][]string{n.ipt4, n.ipt6} { + if err := insertRule(n, ipt, "mangle/OUTPUT", outputRule); err != nil { + return err + } + } + return nil +} + +func (n *fakeIPTablesRunner) DelConnmarkSaveRule() error { + prerouteRule := "-m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000" + for _, ipt := range []map[string][]string{n.ipt4, n.ipt6} { + deleteRule(n, ipt, "mangle/PREROUTING", prerouteRule) // ignore errors + } + + outputRule := "-m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000" + for _, ipt := range []map[string][]string{n.ipt4, n.ipt6} { + deleteRule(n, ipt, "mangle/OUTPUT", outputRule) // ignore errors + } + return nil +} + func (n *fakeIPTablesRunner) HasIPV6() bool { return true } func (n *fakeIPTablesRunner) HasIPV6NAT() bool { return true } func (n *fakeIPTablesRunner) HasIPV6Filter() bool { return true } diff --git a/wgengine/router/osrouter/router_openbsd.go b/wgengine/router/osrouter/router_openbsd.go index 55b485f0e7a9e..1c7eed52e2e76 100644 --- a/wgengine/router/osrouter/router_openbsd.go +++ b/wgengine/router/osrouter/router_openbsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter @@ -14,6 +14,7 @@ import ( "go4.org/netipx" "tailscale.com/health" "tailscale.com/net/netmon" + "tailscale.com/net/netns" "tailscale.com/types/logger" "tailscale.com/util/eventbus" "tailscale.com/util/set" @@ -32,12 +33,13 @@ func init() { // https://git.zx2c4.com/wireguard-openbsd. type openbsdRouter struct { - logf logger.Logf - netMon *netmon.Monitor - tunname string - local4 netip.Prefix - local6 netip.Prefix - routes set.Set[netip.Prefix] + logf logger.Logf + netMon *netmon.Monitor + tunname string + local4 netip.Prefix + local6 netip.Prefix + routes set.Set[netip.Prefix] + areDefaultRoute bool } func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker, bus *eventbus.Bus) (router.Router, error) { @@ -76,6 +78,10 @@ func inet(p netip.Prefix) string { return "inet" } +func isDefaultRoute(p netip.Prefix) bool { + return p.Bits() == 0 +} + func (r *openbsdRouter) Set(cfg *router.Config) error { if cfg == nil { cfg = &shutdownConfig @@ -219,8 +225,12 @@ func (r *openbsdRouter) Set(cfg *router.Config) error { dst = localAddr6.Addr().String() } routeadd := []string{"route", "-q", "-n", - "add", "-" + inet(route), nstr, - "-iface", dst} + "add", "-" + inet(route), nstr} + if isDefaultRoute(route) { + // 1 is reserved for kernel + routeadd = append(routeadd, "-priority", "2") + } + routeadd = append(routeadd, "-iface", dst) out, err := cmd(routeadd...).CombinedOutput() if err != nil { r.logf("addr add failed: %v: %v\n%s", routeadd, err, out) @@ -235,10 +245,33 @@ func (r *openbsdRouter) Set(cfg *router.Config) error { r.local6 = localAddr6 r.routes = newRoutes + areDefault := false + for route := range newRoutes { + if isDefaultRoute(route) { + areDefault = true + break + } + } + + // Set up or tear down the bypass rtable as needed + if areDefault && !r.areDefaultRoute { + if _, err := netns.SetupBypassRtable(r.logf); err != nil { + r.logf("router: failed to set up bypass rtable: %v", err) + } + r.areDefaultRoute = true + } else if !areDefault && r.areDefaultRoute { + netns.CleanupBypassRtable(r.logf) + r.areDefaultRoute = false + } + return errq } func (r *openbsdRouter) Close() error { + if r.areDefaultRoute { + netns.CleanupBypassRtable(r.logf) + r.areDefaultRoute = false + } cleanUp(r.logf, r.tunname) return nil } diff --git a/wgengine/router/osrouter/router_plan9.go b/wgengine/router/osrouter/router_plan9.go index a5b461a6fff67..1436ee8a2191a 100644 --- a/wgengine/router/osrouter/router_plan9.go +++ b/wgengine/router/osrouter/router_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_userspace_bsd.go b/wgengine/router/osrouter/router_userspace_bsd.go index 70ef2b6bf3ca9..272594d7c427b 100644 --- a/wgengine/router/osrouter/router_userspace_bsd.go +++ b/wgengine/router/osrouter/router_userspace_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || freebsd diff --git a/wgengine/router/osrouter/router_windows.go b/wgengine/router/osrouter/router_windows.go index a1acbe3b67287..ef9eb04a147a7 100644 --- a/wgengine/router/osrouter/router_windows.go +++ b/wgengine/router/osrouter/router_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_windows_test.go b/wgengine/router/osrouter/router_windows_test.go index 119b6a77867f9..abbbdf93b1fcb 100644 --- a/wgengine/router/osrouter/router_windows_test.go +++ b/wgengine/router/osrouter/router_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/runner.go b/wgengine/router/osrouter/runner.go index 7afb7fdc2088f..bdc710a8d369a 100644 --- a/wgengine/router/osrouter/runner.go +++ b/wgengine/router/osrouter/runner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/wgengine/router/router.go b/wgengine/router/router.go index 04cc898876557..6868acb43ee2b 100644 --- a/wgengine/router/router.go +++ b/wgengine/router/router.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package router presents an interface to manipulate the host network diff --git a/wgengine/router/router_fake.go b/wgengine/router/router_fake.go index db35fc9eebe15..6b3bc044aea6d 100644 --- a/wgengine/router/router_fake.go +++ b/wgengine/router/router_fake.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/router/router_test.go b/wgengine/router/router_test.go index fd17b8c5d5297..28750e115a9e3 100644 --- a/wgengine/router/router_test.go +++ b/wgengine/router/router_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 875011a9c3e05..245ce421fbe5a 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine @@ -54,6 +54,7 @@ import ( "tailscale.com/util/execqueue" "tailscale.com/util/mak" "tailscale.com/util/set" + "tailscale.com/util/singleflight" "tailscale.com/util/testenv" "tailscale.com/util/usermetric" "tailscale.com/version" @@ -142,8 +143,9 @@ type userspaceEngine struct { trimmedNodes map[key.NodePublic]bool // set of node keys of peers currently excluded from wireguard config sentActivityAt map[netip.Addr]*mono.Time // value is accessed atomically destIPActivityFuncs map[netip.Addr]func() - lastStatusPollTime mono.Time // last time we polled the engine status - reconfigureVPN func() error // or nil + lastStatusPollTime mono.Time // last time we polled the engine status + reconfigureVPN func() error // or nil + conn25PacketHooks Conn25PacketHooks // or nil mu sync.Mutex // guards following; see lock order comment below netMap *netmap.NetworkMap // or nil @@ -174,6 +176,19 @@ type BIRDClient interface { Close() error } +// Conn25PacketHooks are hooks for Connectors 2025 app connectors. +// They are meant to be wired into to corresponding hooks in the +// [tstun.Wrapper]. They may modify the packet (e.g., NAT), or drop +// invalid app connector traffic. +type Conn25PacketHooks interface { + // HandlePacketsFromTunDevice sends packets originating from the tun device + // for further Connectors 2025 app connectors processing. + HandlePacketsFromTunDevice(*packet.Parsed) filter.Response + // HandlePacketsFromWireguard sends packets originating from WireGuard + // for further Connectors 2025 app connectors processing. + HandlePacketsFromWireGuard(*packet.Parsed) filter.Response +} + // Config is the engine configuration. type Config struct { // Tun is the device used by the Engine to exchange packets with @@ -246,6 +261,10 @@ type Config struct { // TODO(creachadair): As of 2025-03-19 this is optional, but is intended to // become required non-nil. EventBus *eventbus.Bus + + // Conn25PacketHooks, if non-nil, is used to hook packets for Connectors 2025 + // app connector handling logic. + Conn25PacketHooks Conn25PacketHooks } // NewFakeUserspaceEngine returns a new userspace engine for testing. @@ -347,19 +366,20 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } e := &userspaceEngine{ - eventBus: conf.EventBus, - timeNow: mono.Now, - logf: logf, - reqCh: make(chan struct{}, 1), - waitCh: make(chan struct{}), - tundev: tsTUNDev, - router: rtr, - dialer: conf.Dialer, - confListenPort: conf.ListenPort, - birdClient: conf.BIRDClient, - controlKnobs: conf.ControlKnobs, - reconfigureVPN: conf.ReconfigureVPN, - health: conf.HealthTracker, + eventBus: conf.EventBus, + timeNow: mono.Now, + logf: logf, + reqCh: make(chan struct{}, 1), + waitCh: make(chan struct{}), + tundev: tsTUNDev, + router: rtr, + dialer: conf.Dialer, + confListenPort: conf.ListenPort, + birdClient: conf.BIRDClient, + controlKnobs: conf.ControlKnobs, + reconfigureVPN: conf.ReconfigureVPN, + health: conf.HealthTracker, + conn25PacketHooks: conf.Conn25PacketHooks, } if e.birdClient != nil { @@ -433,6 +453,16 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } e.tundev.PreFilterPacketOutboundToWireGuardEngineIntercept = e.handleLocalPackets + if e.conn25PacketHooks != nil { + e.tundev.PreFilterPacketOutboundToWireGuardAppConnectorIntercept = func(p *packet.Parsed, _ *tstun.Wrapper) filter.Response { + return e.conn25PacketHooks.HandlePacketsFromTunDevice(p) + } + + e.tundev.PostFilterPacketInboundFromWireGuardAppConnector = func(p *packet.Parsed, _ *tstun.Wrapper) filter.Response { + return e.conn25PacketHooks.HandlePacketsFromWireGuard(p) + } + } + if buildfeatures.HasDebug && envknob.BoolDefaultTrue("TS_DEBUG_CONNECT_FAILURES") { if e.tundev.PreFilterPacketInboundFromWireGuard != nil { return nil, errors.New("unexpected PreFilterIn already set") @@ -568,6 +598,14 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } e.magicConn.HandleDiscoKeyAdvertisement(peer.Node, pkt) }) + var tsmpRequestGroup singleflight.Group[netip.Addr, struct{}] + eventbus.SubscribeFunc(ec, func(req magicsock.NewDiscoKeyAvailable) { + go tsmpRequestGroup.Do(req.NodeFirstAddr, func() (struct{}, error) { + e.sendTSMPDiscoAdvertisement(req.NodeFirstAddr) + e.logf("wgengine: sending TSMP disco key advertisement to %v", req.NodeFirstAddr) + return struct{}{}, nil + }) + }) e.eventClient = ec e.logf("Engine created.") return e, nil diff --git a/wgengine/userspace_ext_test.go b/wgengine/userspace_ext_test.go index 8e7bbb7a9c5c9..2d41a2df08dd2 100644 --- a/wgengine/userspace_ext_test.go +++ b/wgengine/userspace_ext_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine_test diff --git a/wgengine/userspace_test.go b/wgengine/userspace_test.go index 0a1d2924d593b..b06ea527b27ba 100644 --- a/wgengine/userspace_test.go +++ b/wgengine/userspace_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 9cc4ed3b594c3..f12b1c19e2764 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_debug @@ -22,12 +22,47 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/netmap" + "tailscale.com/util/clientmetric" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wgint" ) +type watchdogEvent string + +const ( + Any watchdogEvent = "Any" + Reconfig watchdogEvent = "Reconfig" + ResetAndStop watchdogEvent = "ResetAndStop" + SetFilter watchdogEvent = "SetFilter" + SetJailedFilter watchdogEvent = "SetJailedFilter" + SetStatusCallback watchdogEvent = "SetStatusCallback" + UpdateStatus watchdogEvent = "UpdateStatus" + RequestStatus watchdogEvent = "RequestStatus" + SetNetworkMap watchdogEvent = "SetNetworkMap" + Ping watchdogEvent = "Ping" + Close watchdogEvent = "Close" + PeerForIPEvent watchdogEvent = "PeerForIP" +) + +var ( + watchdogMetrics = map[watchdogEvent]*clientmetric.Metric{ + Any: clientmetric.NewCounter("watchdog_timeout_any_total"), + Reconfig: clientmetric.NewCounter("watchdog_timeout_reconfig"), + ResetAndStop: clientmetric.NewCounter("watchdog_timeout_resetandstop"), + SetFilter: clientmetric.NewCounter("watchdog_timeout_setfilter"), + SetJailedFilter: clientmetric.NewCounter("watchdog_timeout_setjailedfilter"), + SetStatusCallback: clientmetric.NewCounter("watchdog_timeout_setstatuscallback"), + UpdateStatus: clientmetric.NewCounter("watchdog_timeout_updatestatus"), + RequestStatus: clientmetric.NewCounter("watchdog_timeout_requeststatus"), + SetNetworkMap: clientmetric.NewCounter("watchdog_timeout_setnetworkmap"), + Ping: clientmetric.NewCounter("watchdog_timeout_ping"), + Close: clientmetric.NewCounter("watchdog_timeout_close"), + PeerForIPEvent: clientmetric.NewCounter("watchdog_timeout_peerforipevent"), + } +) + // NewWatchdog wraps an Engine and makes sure that all methods complete // within a reasonable amount of time. // @@ -46,7 +81,7 @@ func NewWatchdog(e Engine) Engine { } type inFlightKey struct { - op string + op watchdogEvent ctr uint64 } @@ -62,12 +97,13 @@ type watchdogEngine struct { inFlightCtr uint64 } -func (e *watchdogEngine) watchdogErr(name string, fn func() error) error { +func (e *watchdogEngine) watchdogErr(event watchdogEvent, fn func() error) error { // Track all in-flight operations so we can print more useful error // messages on watchdog failure e.inFlightMu.Lock() + key := inFlightKey{ - op: name, + op: event, ctr: e.inFlightCtr, } e.inFlightCtr++ @@ -93,7 +129,6 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error { buf := new(strings.Builder) pprof.Lookup("goroutine").WriteTo(buf, 1) e.logf("wgengine watchdog stacks:\n%s", buf.String()) - // Collect the list of in-flight operations for debugging. var ( b []byte @@ -104,64 +139,92 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error { dur := now.Sub(t).Round(time.Millisecond) b = fmt.Appendf(b, "in-flight[%d]: name=%s duration=%v start=%s\n", k.ctr, k.op, dur, t.Format(time.RFC3339Nano)) } + e.recordEvent(event) e.inFlightMu.Unlock() // Print everything as a single string to avoid log // rate limits. e.logf("wgengine watchdog in-flight:\n%s", b) - e.fatalf("wgengine: watchdog timeout on %s", name) + e.fatalf("wgengine: watchdog timeout on %s", event) return nil } } -func (e *watchdogEngine) watchdog(name string, fn func()) { - e.watchdogErr(name, func() error { +func (e *watchdogEngine) recordEvent(event watchdogEvent) { + if watchdogMetrics == nil { + return + } + + mEvent, ok := watchdogMetrics[event] + if ok { + mEvent.Add(1) + } + mAny, ok := watchdogMetrics[Any] + if ok { + mAny.Add(1) + } +} + +func (e *watchdogEngine) watchdog(event watchdogEvent, fn func()) { + e.watchdogErr(event, func() error { fn() return nil }) } func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *dns.Config) error { - return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, routerCfg, dnsCfg) }) + return e.watchdogErr(Reconfig, func() error { return e.wrap.Reconfig(cfg, routerCfg, dnsCfg) }) } + func (e *watchdogEngine) ResetAndStop() (st *Status, err error) { - e.watchdog("ResetAndStop", func() { + e.watchdog(ResetAndStop, func() { st, err = e.wrap.ResetAndStop() }) return st, err } + func (e *watchdogEngine) GetFilter() *filter.Filter { return e.wrap.GetFilter() } + func (e *watchdogEngine) SetFilter(filt *filter.Filter) { - e.watchdog("SetFilter", func() { e.wrap.SetFilter(filt) }) + e.watchdog(SetFilter, func() { e.wrap.SetFilter(filt) }) } + func (e *watchdogEngine) GetJailedFilter() *filter.Filter { return e.wrap.GetJailedFilter() } + func (e *watchdogEngine) SetJailedFilter(filt *filter.Filter) { - e.watchdog("SetJailedFilter", func() { e.wrap.SetJailedFilter(filt) }) + e.watchdog(SetJailedFilter, func() { e.wrap.SetJailedFilter(filt) }) } + func (e *watchdogEngine) SetStatusCallback(cb StatusCallback) { - e.watchdog("SetStatusCallback", func() { e.wrap.SetStatusCallback(cb) }) + e.watchdog(SetStatusCallback, func() { e.wrap.SetStatusCallback(cb) }) } + func (e *watchdogEngine) UpdateStatus(sb *ipnstate.StatusBuilder) { - e.watchdog("UpdateStatus", func() { e.wrap.UpdateStatus(sb) }) + e.watchdog(UpdateStatus, func() { e.wrap.UpdateStatus(sb) }) } + func (e *watchdogEngine) RequestStatus() { - e.watchdog("RequestStatus", func() { e.wrap.RequestStatus() }) + e.watchdog(RequestStatus, func() { e.wrap.RequestStatus() }) } + func (e *watchdogEngine) SetNetworkMap(nm *netmap.NetworkMap) { - e.watchdog("SetNetworkMap", func() { e.wrap.SetNetworkMap(nm) }) + e.watchdog(SetNetworkMap, func() { e.wrap.SetNetworkMap(nm) }) } + func (e *watchdogEngine) Ping(ip netip.Addr, pingType tailcfg.PingType, size int, cb func(*ipnstate.PingResult)) { - e.watchdog("Ping", func() { e.wrap.Ping(ip, pingType, size, cb) }) + e.watchdog(Ping, func() { e.wrap.Ping(ip, pingType, size, cb) }) } + func (e *watchdogEngine) Close() { - e.watchdog("Close", e.wrap.Close) + e.watchdog(Close, e.wrap.Close) } + func (e *watchdogEngine) PeerForIP(ip netip.Addr) (ret PeerForIP, ok bool) { - e.watchdog("PeerForIP", func() { ret, ok = e.wrap.PeerForIP(ip) }) + e.watchdog(PeerForIPEvent, func() { ret, ok = e.wrap.PeerForIP(ip) }) return ret, ok } diff --git a/wgengine/watchdog_omit.go b/wgengine/watchdog_omit.go index 1d175b41a87eb..b4ed4344292e6 100644 --- a/wgengine/watchdog_omit.go +++ b/wgengine/watchdog_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js || ts_omit_debug diff --git a/wgengine/watchdog_test.go b/wgengine/watchdog_test.go index 35fd8f33105e6..8032339573e90 100644 --- a/wgengine/watchdog_test.go +++ b/wgengine/watchdog_test.go @@ -1,10 +1,13 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause +//go:build !js + package wgengine import ( "runtime" + "sync" "testing" "time" @@ -44,3 +47,95 @@ func TestWatchdog(t *testing.T) { e.Close() }) } + +func TestWatchdogMetrics(t *testing.T) { + tests := []struct { + name string + events []watchdogEvent + wantCounts map[watchdogEvent]int64 + }{ + { + name: "single event types", + events: []watchdogEvent{RequestStatus, PeerForIPEvent, Ping}, + wantCounts: map[watchdogEvent]int64{ + RequestStatus: 1, + PeerForIPEvent: 1, + Ping: 1, + }, + }, + { + name: "repeated events", + events: []watchdogEvent{RequestStatus, RequestStatus, Ping, RequestStatus}, + wantCounts: map[watchdogEvent]int64{ + RequestStatus: 3, + Ping: 1, + }, + }, + } + + // For swallowing fatalf calls and stack traces + logf := func(format string, args ...any) {} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + clearMetrics(t) + bus := eventbustest.NewBus(t) + ht := health.NewTracker(bus) + reg := new(usermetric.Registry) + e, err := NewFakeUserspaceEngine(logf, 0, ht, reg, bus) + if err != nil { + t.Fatal(err) + } + e = NewWatchdog(e) + w := e.(*watchdogEngine) + w.maxWait = 1 * time.Microsecond + w.logf = logf + w.fatalf = logf + + var wg sync.WaitGroup + wg.Add(len(tt.events)) + + for _, ev := range tt.events { + blocked := make(chan struct{}) + w.watchdog(ev, func() { + defer wg.Done() + <-blocked + }) + close(blocked) + } + wg.Wait() + + // Check individual event counts + for ev, want := range tt.wantCounts { + m, ok := watchdogMetrics[ev] + if !ok { + t.Fatalf("no metric found for event %q", ev) + } + got := m.Value() + if got != want { + t.Errorf("got %d metric events for %q, want %d", got, ev, want) + } + } + + // Check total count for Any + m, ok := watchdogMetrics[Any] + if !ok { + t.Fatalf("no Any metric found") + } + got := m.Value() + if got != int64(len(tt.events)) { + t.Errorf("got %d metric events for Any, want %d", got, len(tt.events)) + } + }) + } +} + +func clearMetrics(t *testing.T) { + t.Helper() + if watchdogMetrics == nil { + return + } + for _, m := range watchdogMetrics { + m.Set(0) + } +} diff --git a/wgengine/wgcfg/config.go b/wgengine/wgcfg/config.go index 2734f6c6ea969..7828121390fba 100644 --- a/wgengine/wgcfg/config.go +++ b/wgengine/wgcfg/config.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wgcfg has types and a parser for representing WireGuard config. diff --git a/wgengine/wgcfg/config_test.go b/wgengine/wgcfg/config_test.go index 5ac3b7cd56376..b15b8cbf56f8b 100644 --- a/wgengine/wgcfg/config_test.go +++ b/wgengine/wgcfg/config_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/device.go b/wgengine/wgcfg/device.go index ee7eb91c93b66..ba29cfbdca8c0 100644 --- a/wgengine/wgcfg/device.go +++ b/wgengine/wgcfg/device.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/device_test.go b/wgengine/wgcfg/device_test.go index 9138d6e5a0f47..a0443147db80d 100644 --- a/wgengine/wgcfg/device_test.go +++ b/wgengine/wgcfg/device_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/nmcfg/nmcfg.go b/wgengine/wgcfg/nmcfg/nmcfg.go index a42827337d5c6..f99b7b007a564 100644 --- a/wgengine/wgcfg/nmcfg/nmcfg.go +++ b/wgengine/wgcfg/nmcfg/nmcfg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package nmcfg converts a controlclient.NetMap into a wgcfg config. diff --git a/wgengine/wgcfg/parser.go b/wgengine/wgcfg/parser.go index ec3d008f7de97..8fb9214091a42 100644 --- a/wgengine/wgcfg/parser.go +++ b/wgengine/wgcfg/parser.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/parser_test.go b/wgengine/wgcfg/parser_test.go index a5d7ad44f2e39..8c38ec0251b21 100644 --- a/wgengine/wgcfg/parser_test.go +++ b/wgengine/wgcfg/parser_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/wgcfg_clone.go b/wgengine/wgcfg/wgcfg_clone.go index 9f3cabde182f9..5c771a2288fce 100644 --- a/wgengine/wgcfg/wgcfg_clone.go +++ b/wgengine/wgcfg/wgcfg_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/wgengine/wgcfg/writer.go b/wgengine/wgcfg/writer.go index 9cdd31df2e38c..f4981e3e9185b 100644 --- a/wgengine/wgcfg/writer.go +++ b/wgengine/wgcfg/writer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index be78731474bc9..9dd782e4ab44f 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wgengine provides the Tailscale WireGuard engine interface. diff --git a/wgengine/wgint/wgint.go b/wgengine/wgint/wgint.go index 309113df71d41..88c48486e3fc6 100644 --- a/wgengine/wgint/wgint.go +++ b/wgengine/wgint/wgint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wgint provides somewhat shady access to wireguard-go diff --git a/wgengine/wgint/wgint_test.go b/wgengine/wgint/wgint_test.go index 714d2044b1806..3409a7fde2d15 100644 --- a/wgengine/wgint/wgint_test.go +++ b/wgengine/wgint/wgint_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgint diff --git a/wgengine/wglog/wglog.go b/wgengine/wglog/wglog.go index dabd4562ad704..174babb91a933 100644 --- a/wgengine/wglog/wglog.go +++ b/wgengine/wglog/wglog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wglog contains logging helpers for wireguard-go. diff --git a/wgengine/wglog/wglog_test.go b/wgengine/wglog/wglog_test.go index 9e9850f39ef59..2e82f9312a5dc 100644 --- a/wgengine/wglog/wglog_test.go +++ b/wgengine/wglog/wglog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wglog_test diff --git a/wgengine/winnet/winnet.go b/wgengine/winnet/winnet.go index e04e6f5c5b1a0..a5a84b04bd25c 100644 --- a/wgengine/winnet/winnet.go +++ b/wgengine/winnet/winnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/wgengine/winnet/winnet_windows.go b/wgengine/winnet/winnet_windows.go index 283ce5ad17b68..6ce298f8165ab 100644 --- a/wgengine/winnet/winnet_windows.go +++ b/wgengine/winnet/winnet_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winnet diff --git a/wif/wif.go b/wif/wif.go index 557685c448c0b..bb2e760f2c7b7 100644 --- a/wif/wif.go +++ b/wif/wif.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wif deals with obtaining ID tokens from provider VMs diff --git a/words/words.go b/words/words.go index b373ffef6541f..ebac1cc0a571e 100644 --- a/words/words.go +++ b/words/words.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package words contains accessors for some nice words. diff --git a/words/words_test.go b/words/words_test.go index a9691792a5c00..3547411a1e160 100644 --- a/words/words_test.go +++ b/words/words_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package words